Repository: AI4S2S/s2spy

Issue:
Unintuitive behavior when passing weekly frequencies to calendar.
When passing a pandas frequency to the AdventCalendar such as '2W', Pandas uses calendar weeks to generate the intervals. This causes the situation where the anchor date is not always in an interval.

Example below:
```python
import s2s.time
calendar = s2s.time.AdventCalendar(anchor_date=(5, 10), freq='20W')
calendar.map_years(2019, 2020)
```
returns;
```
>>> i_interval                          0                         1
>>> anchor_year                                                    
>>> 2020         (2019-12-22, 2020-05-10]  (2019-08-04, 2019-12-22]
>>> 2019         (2018-12-16, 2019-05-05]  (2018-07-29, 2018-12-16]
```

Expected behavior: raise an error when frequencies such as calendar week are passed.

Discussion:

Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c97e78a..552d693 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -45,10 +45,10 @@ jobs:
       fail-fast: false
     steps:
       - uses: actions/checkout@v3
-      - name: Set up Python 3.9
+      - name: Set up Python 3.10
         uses: actions/setup-python@v3
         with:
-          python-version: 3.9
+          python-version: "3.10"
       - name: Python info
         shell: bash -l {0}
         run: |
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index bc5d691..d62d63c 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -21,7 +21,7 @@ jobs:
       - name: Set up Python
         uses: actions/setup-python@v3
         with:
-          python-version: 3.9
+          python-version: "3.10"
       - name: Python info
         shell: bash -l {0}
         run: |
diff --git a/.prospector.yml b/.prospector.yml
index 1b6e066..a59dca9 100644
--- a/.prospector.yml
+++ b/.prospector.yml
@@ -11,6 +11,7 @@ member-warnings: false
 
 ignore-paths:
   - docs
+  - build
 
 pyroma:
     run: true
@@ -31,4 +32,10 @@ pydocstyle:
 pyflakes:
     disable: [
         F401,  # unused import: already checked by pylint
-    ]
\ No newline at end of file
+    ]
+
+mypy:
+    # see https://github.com/PyCQA/prospector/issues/313
+    run: true
+    options:
+        ignore-missing-imports: true
diff --git a/notebooks/tutorial_resampling_data.ipynb b/notebooks/tutorial_resampling_data.ipynb
index 526db64..4efb00e 100644
--- a/notebooks/tutorial_resampling_data.ipynb
+++ b/notebooks/tutorial_resampling_data.ipynb
@@ -11,27 +11,27 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": 1,
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "2017-10-20    0.690068\n",
-       "2017-11-04    0.620426\n",
-       "2017-11-19    0.896097\n",
-       "2017-12-04    0.991774\n",
-       "2017-12-19    0.491445\n",
+       "2017-10-20    0.763931\n",
+       "2017-11-04    0.249611\n",
+       "2017-11-19    0.923058\n",
+       "2017-12-04    0.183058\n",
+       "2017-12-19    0.376686\n",
        "                ...   \n",
-       "2021-07-31    0.159423\n",
-       "2021-08-15    0.935054\n",
-       "2021-08-30    0.212585\n",
-       "2021-09-14    0.631784\n",
-       "2021-09-29    0.338130\n",
+       "2021-07-31    0.114857\n",
+       "2021-08-15    0.621289\n",
+       "2021-08-30    0.899957\n",
+       "2021-09-14    0.773970\n",
+       "2021-09-29    0.173069\n",
        "Freq: 15D, Length: 97, dtype: float64"
       ]
      },
-     "execution_count": 2,
+     "execution_count": 1,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -60,7 +60,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": 2,
    "metadata": {},
    "outputs": [
     {
@@ -91,28 +91,28 @@
        "  <tbody>\n",
        "    <tr>\n",
        "      <th>2017-10-20</th>\n",
-       "      <td>0.690068</td>\n",
-       "      <td>0.690068</td>\n",
+       "      <td>0.763931</td>\n",
+       "      <td>0.763931</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>2017-11-04</th>\n",
-       "      <td>0.620426</td>\n",
-       "      <td>0.620426</td>\n",
+       "      <td>0.249611</td>\n",
+       "      <td>0.249611</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>2017-11-19</th>\n",
-       "      <td>0.896097</td>\n",
-       "      <td>0.896097</td>\n",
+       "      <td>0.923058</td>\n",
+       "      <td>0.923058</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>2017-12-04</th>\n",
-       "      <td>0.991774</td>\n",
-       "      <td>0.991774</td>\n",
+       "      <td>0.183058</td>\n",
+       "      <td>0.183058</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>2017-12-19</th>\n",
-       "      <td>0.491445</td>\n",
-       "      <td>0.491445</td>\n",
+       "      <td>0.376686</td>\n",
+       "      <td>0.376686</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>...</th>\n",
@@ -121,28 +121,28 @@
        "    </tr>\n",
        "    <tr>\n",
        "      <th>2021-07-31</th>\n",
-       "      <td>0.159423</td>\n",
-       "      <td>0.159423</td>\n",
+       "      <td>0.114857</td>\n",
+       "      <td>0.114857</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>2021-08-15</th>\n",
-       "      <td>0.935054</td>\n",
-       "      <td>0.935054</td>\n",
+       "      <td>0.621289</td>\n",
+       "      <td>0.621289</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>2021-08-30</th>\n",
-       "      <td>0.212585</td>\n",
-       "      <td>0.212585</td>\n",
+       "      <td>0.899957</td>\n",
+       "      <td>0.899957</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>2021-09-14</th>\n",
-       "      <td>0.631784</td>\n",
-       "      <td>0.631784</td>\n",
+       "      <td>0.773970</td>\n",
+       "      <td>0.773970</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>2021-09-29</th>\n",
-       "      <td>0.338130</td>\n",
-       "      <td>0.338130</td>\n",
+       "      <td>0.173069</td>\n",
+       "      <td>0.173069</td>\n",
        "    </tr>\n",
        "  </tbody>\n",
        "</table>\n",
@@ -151,22 +151,22 @@
       ],
       "text/plain": [
        "               data1     data2\n",
-       "2017-10-20  0.690068  0.690068\n",
-       "2017-11-04  0.620426  0.620426\n",
-       "2017-11-19  0.896097  0.896097\n",
-       "2017-12-04  0.991774  0.991774\n",
-       "2017-12-19  0.491445  0.491445\n",
+       "2017-10-20  0.763931  0.763931\n",
+       "2017-11-04  0.249611  0.249611\n",
+       "2017-11-19  0.923058  0.923058\n",
+       "2017-12-04  0.183058  0.183058\n",
+       "2017-12-19  0.376686  0.376686\n",
        "...              ...       ...\n",
-       "2021-07-31  0.159423  0.159423\n",
-       "2021-08-15  0.935054  0.935054\n",
-       "2021-08-30  0.212585  0.212585\n",
-       "2021-09-14  0.631784  0.631784\n",
-       "2021-09-29  0.338130  0.338130\n",
+       "2021-07-31  0.114857  0.114857\n",
+       "2021-08-15  0.621289  0.621289\n",
+       "2021-08-30  0.899957  0.899957\n",
+       "2021-09-14  0.773970  0.773970\n",
+       "2021-09-29  0.173069  0.173069\n",
        "\n",
        "[97 rows x 2 columns]"
       ]
      },
-     "execution_count": 3,
+     "execution_count": 2,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -180,66 +180,40 @@
    "metadata": {},
    "source": [
     "To resample we need to set up an advent calendar with the anchor date and frequency. \n",
-    "(Passing n_targets is optional, default is 1. This means i_interval = 0 is the target period. The marked target periods will show up in map_years in future versions.)"
+    "(Passing n_targets is optional, default is 1. This means i_interval = 0 is the target period.)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 3,
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "anchor_year  i_interval\n",
-       "2020         0             (2020-07-17, 2020-10-15]\n",
-       "             1             (2020-04-18, 2020-07-17]\n",
-       "             2             (2020-01-19, 2020-04-18]\n",
-       "             3             (2019-10-21, 2020-01-19]\n",
-       "2019         0             (2019-07-17, 2019-10-15]\n",
-       "             1             (2019-04-18, 2019-07-17]\n",
-       "             2             (2019-01-18, 2019-04-18]\n",
-       "             3             (2018-10-20, 2019-01-18]\n",
-       "2018         0             (2018-07-17, 2018-10-15]\n",
-       "             1             (2018-04-18, 2018-07-17]\n",
-       "             2             (2018-01-18, 2018-04-18]\n",
-       "             3             (2017-10-20, 2018-01-18]\n",
-       "2017         0             (2017-07-17, 2017-10-15]\n",
-       "             1             (2017-04-18, 2017-07-17]\n",
-       "             2             (2017-01-18, 2017-04-18]\n",
-       "             3             (2016-10-20, 2017-01-18]\n",
-       "2016         0             (2016-07-17, 2016-10-15]\n",
-       "             1             (2016-04-18, 2016-07-17]\n",
-       "             2             (2016-01-19, 2016-04-18]\n",
-       "             3             (2015-10-21, 2016-01-19]\n",
-       "2015         0             (2015-07-17, 2015-10-15]\n",
-       "             1             (2015-04-18, 2015-07-17]\n",
-       "             2             (2015-01-18, 2015-04-18]\n",
-       "             3             (2014-10-20, 2015-01-18]\n",
-       "dtype: interval"
+       "AdventCalendar(month=10, day=15, freq=90d, n_targets=1, max_lag=None)"
       ]
      },
-     "execution_count": 4,
+     "execution_count": 3,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "calendar = s2spy.time.AdventCalendar(anchor_date=(10, 15), freq='90d')\n",
-    "calendar.map_years(2015, 2020).flat"
+    "calendar = s2spy.time.AdventCalendar(anchor=(10, 15), freq='90d')\n",
+    "calendar.map_years(2018, 2020)"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Next we pass the example data to the `resample` method of the calendar.\n",
-    "(Here the target shows up as a column.)"
+    "Next we pass the example data to the `resample` function. This requires a mapped calendar and the input data.\n"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 4,
    "metadata": {},
    "outputs": [
     {
@@ -276,7 +250,7 @@
        "      <td>2018</td>\n",
        "      <td>0</td>\n",
        "      <td>(2018-07-17, 2018-10-15]</td>\n",
-       "      <td>0.472704</td>\n",
+       "      <td>0.478348</td>\n",
        "      <td>True</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -284,7 +258,7 @@
        "      <td>2018</td>\n",
        "      <td>1</td>\n",
        "      <td>(2018-04-18, 2018-07-17]</td>\n",
-       "      <td>0.467270</td>\n",
+       "      <td>0.640158</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -292,7 +266,7 @@
        "      <td>2018</td>\n",
        "      <td>2</td>\n",
        "      <td>(2018-01-18, 2018-04-18]</td>\n",
-       "      <td>0.518639</td>\n",
+       "      <td>0.400760</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -300,7 +274,7 @@
        "      <td>2018</td>\n",
        "      <td>3</td>\n",
        "      <td>(2017-10-20, 2018-01-18]</td>\n",
-       "      <td>0.790256</td>\n",
+       "      <td>0.460397</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -308,7 +282,7 @@
        "      <td>2019</td>\n",
        "      <td>0</td>\n",
        "      <td>(2019-07-17, 2019-10-15]</td>\n",
-       "      <td>0.404145</td>\n",
+       "      <td>0.440918</td>\n",
        "      <td>True</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -316,7 +290,7 @@
        "      <td>2019</td>\n",
        "      <td>1</td>\n",
        "      <td>(2019-04-18, 2019-07-17]</td>\n",
-       "      <td>0.634694</td>\n",
+       "      <td>0.403817</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -324,7 +298,7 @@
        "      <td>2019</td>\n",
        "      <td>2</td>\n",
        "      <td>(2019-01-18, 2019-04-18]</td>\n",
-       "      <td>0.811495</td>\n",
+       "      <td>0.516905</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -332,7 +306,7 @@
        "      <td>2019</td>\n",
        "      <td>3</td>\n",
        "      <td>(2018-10-20, 2019-01-18]</td>\n",
-       "      <td>0.562892</td>\n",
+       "      <td>0.371559</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -340,7 +314,7 @@
        "      <td>2020</td>\n",
        "      <td>0</td>\n",
        "      <td>(2020-07-17, 2020-10-15]</td>\n",
-       "      <td>0.621562</td>\n",
+       "      <td>0.497199</td>\n",
        "      <td>True</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -348,7 +322,7 @@
        "      <td>2020</td>\n",
        "      <td>1</td>\n",
        "      <td>(2020-04-18, 2020-07-17]</td>\n",
-       "      <td>0.632471</td>\n",
+       "      <td>0.370229</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -356,7 +330,7 @@
        "      <td>2020</td>\n",
        "      <td>2</td>\n",
        "      <td>(2020-01-19, 2020-04-18]</td>\n",
-       "      <td>0.722433</td>\n",
+       "      <td>0.374970</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -364,7 +338,7 @@
        "      <td>2020</td>\n",
        "      <td>3</td>\n",
        "      <td>(2019-10-21, 2020-01-19]</td>\n",
-       "      <td>0.275573</td>\n",
+       "      <td>0.310996</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "  </tbody>\n",
@@ -373,27 +347,27 @@
       ],
       "text/plain": [
        "   anchor_year  i_interval                  interval  mean_data  target\n",
-       "0         2018           0  (2018-07-17, 2018-10-15]   0.472704    True\n",
-       "1         2018           1  (2018-04-18, 2018-07-17]   0.467270   False\n",
-       "2         2018           2  (2018-01-18, 2018-04-18]   0.518639   False\n",
-       "3         2018           3  (2017-10-20, 2018-01-18]   0.790256   False\n",
-       "4         2019           0  (2019-07-17, 2019-10-15]   0.404145    True\n",
-       "5         2019           1  (2019-04-18, 2019-07-17]   0.634694   False\n",
-       "6         2019           2  (2019-01-18, 2019-04-18]   0.811495   False\n",
-       "7         2019           3  (2018-10-20, 2019-01-18]   0.562892   False\n",
-       "8         2020           0  (2020-07-17, 2020-10-15]   0.621562    True\n",
-       "9         2020           1  (2020-04-18, 2020-07-17]   0.632471   False\n",
-       "10        2020           2  (2020-01-19, 2020-04-18]   0.722433   False\n",
-       "11        2020           3  (2019-10-21, 2020-01-19]   0.275573   False"
+       "0         2018           0  (2018-07-17, 2018-10-15]   0.478348    True\n",
+       "1         2018           1  (2018-04-18, 2018-07-17]   0.640158   False\n",
+       "2         2018           2  (2018-01-18, 2018-04-18]   0.400760   False\n",
+       "3         2018           3  (2017-10-20, 2018-01-18]   0.460397   False\n",
+       "4         2019           0  (2019-07-17, 2019-10-15]   0.440918    True\n",
+       "5         2019           1  (2019-04-18, 2019-07-17]   0.403817   False\n",
+       "6         2019           2  (2019-01-18, 2019-04-18]   0.516905   False\n",
+       "7         2019           3  (2018-10-20, 2019-01-18]   0.371559   False\n",
+       "8         2020           0  (2020-07-17, 2020-10-15]   0.497199    True\n",
+       "9         2020           1  (2020-04-18, 2020-07-17]   0.370229   False\n",
+       "10        2020           2  (2020-01-19, 2020-04-18]   0.374970   False\n",
+       "11        2020           3  (2019-10-21, 2020-01-19]   0.310996   False"
       ]
      },
-     "execution_count": 5,
+     "execution_count": 4,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "resampled_series = calendar.resample(example_series)\n",
+    "resampled_series = s2spy.time.resample(calendar, example_series)\n",
     "resampled_series"
    ]
   },
@@ -408,7 +382,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 5,
    "metadata": {},
    "outputs": [
     {
@@ -446,8 +420,8 @@
        "      <td>2018</td>\n",
        "      <td>0</td>\n",
        "      <td>(2018-07-17, 2018-10-15]</td>\n",
-       "      <td>0.472704</td>\n",
-       "      <td>0.472704</td>\n",
+       "      <td>0.478348</td>\n",
+       "      <td>0.478348</td>\n",
        "      <td>True</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -455,8 +429,8 @@
        "      <td>2018</td>\n",
        "      <td>1</td>\n",
        "      <td>(2018-04-18, 2018-07-17]</td>\n",
-       "      <td>0.467270</td>\n",
-       "      <td>0.467270</td>\n",
+       "      <td>0.640158</td>\n",
+       "      <td>0.640158</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -464,8 +438,8 @@
        "      <td>2018</td>\n",
        "      <td>2</td>\n",
        "      <td>(2018-01-18, 2018-04-18]</td>\n",
-       "      <td>0.518639</td>\n",
-       "      <td>0.518639</td>\n",
+       "      <td>0.400760</td>\n",
+       "      <td>0.400760</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -473,8 +447,8 @@
        "      <td>2018</td>\n",
        "      <td>3</td>\n",
        "      <td>(2017-10-20, 2018-01-18]</td>\n",
-       "      <td>0.790256</td>\n",
-       "      <td>0.790256</td>\n",
+       "      <td>0.460397</td>\n",
+       "      <td>0.460397</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -482,8 +456,8 @@
        "      <td>2019</td>\n",
        "      <td>0</td>\n",
        "      <td>(2019-07-17, 2019-10-15]</td>\n",
-       "      <td>0.404145</td>\n",
-       "      <td>0.404145</td>\n",
+       "      <td>0.440918</td>\n",
+       "      <td>0.440918</td>\n",
        "      <td>True</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -491,8 +465,8 @@
        "      <td>2019</td>\n",
        "      <td>1</td>\n",
        "      <td>(2019-04-18, 2019-07-17]</td>\n",
-       "      <td>0.634694</td>\n",
-       "      <td>0.634694</td>\n",
+       "      <td>0.403817</td>\n",
+       "      <td>0.403817</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -500,8 +474,8 @@
        "      <td>2019</td>\n",
        "      <td>2</td>\n",
        "      <td>(2019-01-18, 2019-04-18]</td>\n",
-       "      <td>0.811495</td>\n",
-       "      <td>0.811495</td>\n",
+       "      <td>0.516905</td>\n",
+       "      <td>0.516905</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -509,8 +483,8 @@
        "      <td>2019</td>\n",
        "      <td>3</td>\n",
        "      <td>(2018-10-20, 2019-01-18]</td>\n",
-       "      <td>0.562892</td>\n",
-       "      <td>0.562892</td>\n",
+       "      <td>0.371559</td>\n",
+       "      <td>0.371559</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -518,8 +492,8 @@
        "      <td>2020</td>\n",
        "      <td>0</td>\n",
        "      <td>(2020-07-17, 2020-10-15]</td>\n",
-       "      <td>0.621562</td>\n",
-       "      <td>0.621562</td>\n",
+       "      <td>0.497199</td>\n",
+       "      <td>0.497199</td>\n",
        "      <td>True</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -527,8 +501,8 @@
        "      <td>2020</td>\n",
        "      <td>1</td>\n",
        "      <td>(2020-04-18, 2020-07-17]</td>\n",
-       "      <td>0.632471</td>\n",
-       "      <td>0.632471</td>\n",
+       "      <td>0.370229</td>\n",
+       "      <td>0.370229</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -536,8 +510,8 @@
        "      <td>2020</td>\n",
        "      <td>2</td>\n",
        "      <td>(2020-01-19, 2020-04-18]</td>\n",
-       "      <td>0.722433</td>\n",
-       "      <td>0.722433</td>\n",
+       "      <td>0.374970</td>\n",
+       "      <td>0.374970</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "    <tr>\n",
@@ -545,8 +519,8 @@
        "      <td>2020</td>\n",
        "      <td>3</td>\n",
        "      <td>(2019-10-21, 2020-01-19]</td>\n",
-       "      <td>0.275573</td>\n",
-       "      <td>0.275573</td>\n",
+       "      <td>0.310996</td>\n",
+       "      <td>0.310996</td>\n",
        "      <td>False</td>\n",
        "    </tr>\n",
        "  </tbody>\n",
@@ -555,18 +529,18 @@
       ],
       "text/plain": [
        "   anchor_year  i_interval                  interval     data1     data2  \\\n",
-       "0         2018           0  (2018-07-17, 2018-10-15]  0.472704  0.472704   \n",
-       "1         2018           1  (2018-04-18, 2018-07-17]  0.467270  0.467270   \n",
-       "2         2018           2  (2018-01-18, 2018-04-18]  0.518639  0.518639   \n",
-       "3         2018           3  (2017-10-20, 2018-01-18]  0.790256  0.790256   \n",
-       "4         2019           0  (2019-07-17, 2019-10-15]  0.404145  0.404145   \n",
-       "5         2019           1  (2019-04-18, 2019-07-17]  0.634694  0.634694   \n",
-       "6         2019           2  (2019-01-18, 2019-04-18]  0.811495  0.811495   \n",
-       "7         2019           3  (2018-10-20, 2019-01-18]  0.562892  0.562892   \n",
-       "8         2020           0  (2020-07-17, 2020-10-15]  0.621562  0.621562   \n",
-       "9         2020           1  (2020-04-18, 2020-07-17]  0.632471  0.632471   \n",
-       "10        2020           2  (2020-01-19, 2020-04-18]  0.722433  0.722433   \n",
-       "11        2020           3  (2019-10-21, 2020-01-19]  0.275573  0.275573   \n",
+       "0         2018           0  (2018-07-17, 2018-10-15]  0.478348  0.478348   \n",
+       "1         2018           1  (2018-04-18, 2018-07-17]  0.640158  0.640158   \n",
+       "2         2018           2  (2018-01-18, 2018-04-18]  0.400760  0.400760   \n",
+       "3         2018           3  (2017-10-20, 2018-01-18]  0.460397  0.460397   \n",
+       "4         2019           0  (2019-07-17, 2019-10-15]  0.440918  0.440918   \n",
+       "5         2019           1  (2019-04-18, 2019-07-17]  0.403817  0.403817   \n",
+       "6         2019           2  (2019-01-18, 2019-04-18]  0.516905  0.516905   \n",
+       "7         2019           3  (2018-10-20, 2019-01-18]  0.371559  0.371559   \n",
+       "8         2020           0  (2020-07-17, 2020-10-15]  0.497199  0.497199   \n",
+       "9         2020           1  (2020-04-18, 2020-07-17]  0.370229  0.370229   \n",
+       "10        2020           2  (2020-01-19, 2020-04-18]  0.374970  0.374970   \n",
+       "11        2020           3  (2019-10-21, 2020-01-19]  0.310996  0.310996   \n",
        "\n",
        "    target  \n",
        "0     True  \n",
@@ -583,13 +557,13 @@
        "11   False  "
       ]
      },
-     "execution_count": 6,
+     "execution_count": 5,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "resampled_dataframe = calendar.resample(example_dataframe)\n",
+    "resampled_dataframe = s2spy.time.resample(calendar, example_dataframe)\n",
     "resampled_dataframe"
    ]
   },
@@ -602,7 +576,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 6,
    "metadata": {},
    "outputs": [
     {
@@ -970,9 +944,9 @@
        "    temperature    (x, y, time) float64 29.11 18.2 22.83 ... 1.746 7.116 3.225\n",
        "    precipitation  (x, y, time) float64 5.25 7.506 3.335 ... 7.359 1.415 8.659\n",
        "Attributes:\n",
-       "    description:  Weather related data.</pre><div class='xr-wrap' style='display:none'><div class='xr-header'><div class='xr-obj-type'>xarray.Dataset</div></div><ul class='xr-sections'><li class='xr-section-item'><input id='section-f5bea966-16a6-4d6b-b80d-a6da76a3c365' class='xr-section-summary-in' type='checkbox' disabled ><label for='section-f5bea966-16a6-4d6b-b80d-a6da76a3c365' class='xr-section-summary'  title='Expand/collapse section'>Dimensions:</label><div class='xr-section-inline-details'><ul class='xr-dim-list'><li><span>x</span>: 2</li><li><span>y</span>: 2</li><li><span class='xr-has-index'>time</span>: 97</li></ul></div><div class='xr-section-details'></div></li><li class='xr-section-item'><input id='section-15f79c16-d0b7-482f-9a8e-8c048c666837' class='xr-section-summary-in' type='checkbox'  checked><label for='section-15f79c16-d0b7-482f-9a8e-8c048c666837' class='xr-section-summary' >Coordinates: <span>(3)</span></label><div class='xr-section-inline-details'></div><div class='xr-section-details'><ul class='xr-var-list'><li class='xr-var-item'><div class='xr-var-name'><span>lon</span></div><div class='xr-var-dims'>(x, y)</div><div class='xr-var-dtype'>float64</div><div class='xr-var-preview xr-preview'>-99.83 -99.32 -99.79 -99.23</div><input id='attrs-0b267206-2eee-4799-ac81-64fa89200fe5' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-0b267206-2eee-4799-ac81-64fa89200fe5' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-86fdb6b0-e3ce-4e1c-b000-0f8d69b825a2' class='xr-var-data-in' type='checkbox'><label for='data-86fdb6b0-e3ce-4e1c-b000-0f8d69b825a2' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[-99.83, -99.32],\n",
-       "       [-99.79, -99.23]])</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span>lat</span></div><div class='xr-var-dims'>(x, y)</div><div class='xr-var-dtype'>float64</div><div class='xr-var-preview xr-preview'>42.25 42.21 42.63 42.59</div><input id='attrs-889f5f9d-c9c0-40c3-982d-e9007546e462' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-889f5f9d-c9c0-40c3-982d-e9007546e462' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-a3a17748-4499-4983-920f-f93d6d95b6d1' class='xr-var-data-in' type='checkbox'><label for='data-a3a17748-4499-4983-920f-f93d6d95b6d1' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[42.25, 42.21],\n",
-       "       [42.63, 42.59]])</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span class='xr-has-index'>time</span></div><div class='xr-var-dims'>(time)</div><div class='xr-var-dtype'>datetime64[ns]</div><div class='xr-var-preview xr-preview'>2017-10-20 ... 2021-09-29</div><input id='attrs-63faf94f-2d99-4856-97fa-53f7d5876c2d' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-63faf94f-2d99-4856-97fa-53f7d5876c2d' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-5e288df3-28a3-42b4-8c4b-d58ab98b37d3' class='xr-var-data-in' type='checkbox'><label for='data-5e288df3-28a3-42b4-8c4b-d58ab98b37d3' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([&#x27;2017-10-20T00:00:00.000000000&#x27;, &#x27;2017-11-04T00:00:00.000000000&#x27;,\n",
+       "    description:  Weather related data.</pre><div class='xr-wrap' style='display:none'><div class='xr-header'><div class='xr-obj-type'>xarray.Dataset</div></div><ul class='xr-sections'><li class='xr-section-item'><input id='section-441267d9-4df1-4eaa-ac79-759d15b4a99c' class='xr-section-summary-in' type='checkbox' disabled ><label for='section-441267d9-4df1-4eaa-ac79-759d15b4a99c' class='xr-section-summary'  title='Expand/collapse section'>Dimensions:</label><div class='xr-section-inline-details'><ul class='xr-dim-list'><li><span>x</span>: 2</li><li><span>y</span>: 2</li><li><span class='xr-has-index'>time</span>: 97</li></ul></div><div class='xr-section-details'></div></li><li class='xr-section-item'><input id='section-d55ac5df-9ecf-4599-b118-478d276101cb' class='xr-section-summary-in' type='checkbox'  checked><label for='section-d55ac5df-9ecf-4599-b118-478d276101cb' class='xr-section-summary' >Coordinates: <span>(3)</span></label><div class='xr-section-inline-details'></div><div class='xr-section-details'><ul class='xr-var-list'><li class='xr-var-item'><div class='xr-var-name'><span>lon</span></div><div class='xr-var-dims'>(x, y)</div><div class='xr-var-dtype'>float64</div><div class='xr-var-preview xr-preview'>-99.83 -99.32 -99.79 -99.23</div><input id='attrs-ced9bf14-d4f7-4ec6-86c5-f42cf77cc53d' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-ced9bf14-d4f7-4ec6-86c5-f42cf77cc53d' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-0ca90cb8-14e9-487a-8a80-192a71317a55' class='xr-var-data-in' type='checkbox'><label for='data-0ca90cb8-14e9-487a-8a80-192a71317a55' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[-99.83, -99.32],\n",
+       "       [-99.79, -99.23]])</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span>lat</span></div><div class='xr-var-dims'>(x, y)</div><div class='xr-var-dtype'>float64</div><div class='xr-var-preview xr-preview'>42.25 42.21 42.63 42.59</div><input id='attrs-994ea888-682e-4aac-bc45-5d173cfdd649' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-994ea888-682e-4aac-bc45-5d173cfdd649' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-2cdb1aa9-6dba-48cd-b8f3-12dde35045f4' class='xr-var-data-in' type='checkbox'><label for='data-2cdb1aa9-6dba-48cd-b8f3-12dde35045f4' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[42.25, 42.21],\n",
+       "       [42.63, 42.59]])</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span class='xr-has-index'>time</span></div><div class='xr-var-dims'>(time)</div><div class='xr-var-dtype'>datetime64[ns]</div><div class='xr-var-preview xr-preview'>2017-10-20 ... 2021-09-29</div><input id='attrs-04699b5d-e3c8-4b2c-8c50-b9e6344a6d79' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-04699b5d-e3c8-4b2c-8c50-b9e6344a6d79' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-a1b91e8b-b2b3-4d07-983e-f94510890732' class='xr-var-data-in' type='checkbox'><label for='data-a1b91e8b-b2b3-4d07-983e-f94510890732' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([&#x27;2017-10-20T00:00:00.000000000&#x27;, &#x27;2017-11-04T00:00:00.000000000&#x27;,\n",
        "       &#x27;2017-11-19T00:00:00.000000000&#x27;, &#x27;2017-12-04T00:00:00.000000000&#x27;,\n",
        "       &#x27;2017-12-19T00:00:00.000000000&#x27;, &#x27;2018-01-03T00:00:00.000000000&#x27;,\n",
        "       &#x27;2018-01-18T00:00:00.000000000&#x27;, &#x27;2018-02-02T00:00:00.000000000&#x27;,\n",
@@ -1020,7 +994,7 @@
        "       &#x27;2021-07-01T00:00:00.000000000&#x27;, &#x27;2021-07-16T00:00:00.000000000&#x27;,\n",
        "       &#x27;2021-07-31T00:00:00.000000000&#x27;, &#x27;2021-08-15T00:00:00.000000000&#x27;,\n",
        "       &#x27;2021-08-30T00:00:00.000000000&#x27;, &#x27;2021-09-14T00:00:00.000000000&#x27;,\n",
-       "       &#x27;2021-09-29T00:00:00.000000000&#x27;], dtype=&#x27;datetime64[ns]&#x27;)</pre></div></li></ul></div></li><li class='xr-section-item'><input id='section-80974c05-7256-4dbd-9949-8ae29a6ff440' class='xr-section-summary-in' type='checkbox'  checked><label for='section-80974c05-7256-4dbd-9949-8ae29a6ff440' class='xr-section-summary' >Data variables: <span>(2)</span></label><div class='xr-section-inline-details'></div><div class='xr-section-details'><ul class='xr-var-list'><li class='xr-var-item'><div class='xr-var-name'><span>temperature</span></div><div class='xr-var-dims'>(x, y, time)</div><div class='xr-var-dtype'>float64</div><div class='xr-var-preview xr-preview'>29.11 18.2 22.83 ... 7.116 3.225</div><input id='attrs-74d1e2c4-add0-43e0-a7a0-cb9c10932607' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-74d1e2c4-add0-43e0-a7a0-cb9c10932607' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-ea83a464-9ac9-4dcd-a7b2-075d79700784' class='xr-var-data-in' type='checkbox'><label for='data-ea83a464-9ac9-4dcd-a7b2-075d79700784' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[[29.11241877, 18.20125767, 22.82990387, 32.92714559,\n",
+       "       &#x27;2021-09-29T00:00:00.000000000&#x27;], dtype=&#x27;datetime64[ns]&#x27;)</pre></div></li></ul></div></li><li class='xr-section-item'><input id='section-8d2d7f19-ecb0-48ea-9097-9e705993811f' class='xr-section-summary-in' type='checkbox'  checked><label for='section-8d2d7f19-ecb0-48ea-9097-9e705993811f' class='xr-section-summary' >Data variables: <span>(2)</span></label><div class='xr-section-inline-details'></div><div class='xr-section-details'><ul class='xr-var-list'><li class='xr-var-item'><div class='xr-var-name'><span>temperature</span></div><div class='xr-var-dims'>(x, y, time)</div><div class='xr-var-dtype'>float64</div><div class='xr-var-preview xr-preview'>29.11 18.2 22.83 ... 7.116 3.225</div><input id='attrs-ff285823-f135-41e0-ae32-2b7d6c46647e' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-ff285823-f135-41e0-ae32-2b7d6c46647e' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-a36589c8-dfa3-430b-9f28-439b77729768' class='xr-var-data-in' type='checkbox'><label for='data-a36589c8-dfa3-430b-9f28-439b77729768' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[[29.11241877, 18.20125767, 22.82990387, 32.92714559,\n",
        "         29.94046392,  7.18177696, 22.60070734, 13.78914233,\n",
        "         14.17424919, 18.28478802, 16.15234857, 26.63418806,\n",
        "         21.0883018 , 15.97340013, 18.55090586, 17.66939462,\n",
@@ -1060,7 +1034,7 @@
        "         18.95069421, 20.14651572,  2.43501273, 13.34477059,\n",
        "         22.0414313 ,  1.41515344, 18.0982438 , -3.04451384,\n",
        "          6.81994525, 15.30904441,  1.74627918,  7.1159141 ,\n",
-       "          3.22531994]]])</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span>precipitation</span></div><div class='xr-var-dims'>(x, y, time)</div><div class='xr-var-dtype'>float64</div><div class='xr-var-preview xr-preview'>5.25 7.506 3.335 ... 1.415 8.659</div><input id='attrs-b802c2e5-f8e9-4e7a-9f8d-4c89d7759da3' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-b802c2e5-f8e9-4e7a-9f8d-4c89d7759da3' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-5fbf765c-b8a9-40c4-b53c-2e69388b1e3a' class='xr-var-data-in' type='checkbox'><label for='data-5fbf765c-b8a9-40c4-b53c-2e69388b1e3a' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[[5.24970442e+00, 7.50595023e+00, 3.33507466e+00, 9.24158767e+00,\n",
+       "          3.22531994]]])</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span>precipitation</span></div><div class='xr-var-dims'>(x, y, time)</div><div class='xr-var-dtype'>float64</div><div class='xr-var-preview xr-preview'>5.25 7.506 3.335 ... 1.415 8.659</div><input id='attrs-1dad454b-a6bc-410c-9a42-ee7d2c7e432a' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-1dad454b-a6bc-410c-9a42-ee7d2c7e432a' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-180b95e4-a62d-4f3b-b8d7-699c583cadb3' class='xr-var-data-in' type='checkbox'><label for='data-180b95e4-a62d-4f3b-b8d7-699c583cadb3' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[[5.24970442e+00, 7.50595023e+00, 3.33507466e+00, 9.24158767e+00,\n",
        "         8.62318547e+00, 4.86902960e-01, 2.53642524e+00, 4.46135513e+00,\n",
        "         1.04627889e+00, 3.48475989e+00, 7.40097526e+00, 6.80514481e+00,\n",
        "         6.22384429e+00, 7.10528403e+00, 2.04923687e+00, 3.41698115e+00,\n",
@@ -1100,7 +1074,7 @@
        "         3.57346880e+00, 9.14970770e+00, 7.31744185e+00, 7.27546991e+00,\n",
        "         2.89913450e+00, 5.77709424e+00, 7.79179433e+00, 7.95590369e+00,\n",
        "         3.44530461e+00, 7.70872757e+00, 7.35893897e+00, 1.41506486e+00,\n",
-       "         8.65945469e+00]]])</pre></div></li></ul></div></li><li class='xr-section-item'><input id='section-919710d0-7005-4a88-9c6c-ee73e89b4ef7' class='xr-section-summary-in' type='checkbox'  checked><label for='section-919710d0-7005-4a88-9c6c-ee73e89b4ef7' class='xr-section-summary' >Attributes: <span>(1)</span></label><div class='xr-section-inline-details'></div><div class='xr-section-details'><dl class='xr-attrs'><dt><span>description :</span></dt><dd>Weather related data.</dd></dl></div></li></ul></div></div>"
+       "         8.65945469e+00]]])</pre></div></li></ul></div></li><li class='xr-section-item'><input id='section-3b74134c-f046-4eb6-9b11-bdb3afcde945' class='xr-section-summary-in' type='checkbox'  checked><label for='section-3b74134c-f046-4eb6-9b11-bdb3afcde945' class='xr-section-summary' >Attributes: <span>(1)</span></label><div class='xr-section-inline-details'></div><div class='xr-section-details'><dl class='xr-attrs'><dt><span>description :</span></dt><dd>Weather related data.</dd></dl></div></li></ul></div></div>"
       ],
       "text/plain": [
        "<xarray.Dataset>\n",
@@ -1117,7 +1091,7 @@
        "    description:  Weather related data."
       ]
      },
-     "execution_count": 7,
+     "execution_count": 6,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -1167,7 +1141,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 7,
    "metadata": {},
    "outputs": [
     {
@@ -1537,9 +1511,9 @@
        "Dimensions without coordinates: x, y\n",
        "Data variables:\n",
        "    temperature    (anchor_year, i_interval, x, y) float64 14.52 17.14 ... 12.32\n",
-       "    precipitation  (anchor_year, i_interval, x, y) float64 3.224 6.534 ... 5.705</pre><div class='xr-wrap' style='display:none'><div class='xr-header'><div class='xr-obj-type'>xarray.Dataset</div></div><ul class='xr-sections'><li class='xr-section-item'><input id='section-85f569bc-957b-4733-b64c-310d4e30ad27' class='xr-section-summary-in' type='checkbox' disabled ><label for='section-85f569bc-957b-4733-b64c-310d4e30ad27' class='xr-section-summary'  title='Expand/collapse section'>Dimensions:</label><div class='xr-section-inline-details'><ul class='xr-dim-list'><li><span class='xr-has-index'>anchor_year</span>: 3</li><li><span class='xr-has-index'>i_interval</span>: 4</li><li><span>x</span>: 2</li><li><span>y</span>: 2</li></ul></div><div class='xr-section-details'></div></li><li class='xr-section-item'><input id='section-54627ece-901f-4141-8e60-f518cdcf6d97' class='xr-section-summary-in' type='checkbox'  checked><label for='section-54627ece-901f-4141-8e60-f518cdcf6d97' class='xr-section-summary' >Coordinates: <span>(7)</span></label><div class='xr-section-inline-details'></div><div class='xr-section-details'><ul class='xr-var-list'><li class='xr-var-item'><div class='xr-var-name'><span>index</span></div><div class='xr-var-dims'>(anchor_year, i_interval)</div><div class='xr-var-dtype'>int64</div><div class='xr-var-preview xr-preview'>0 1 2 3 4 5 6 7 8 9 10 11</div><input id='attrs-1a87111a-9447-47c7-bff1-7707ed61d740' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-1a87111a-9447-47c7-bff1-7707ed61d740' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-12c7311c-d341-416c-94ea-d9dcbf434f24' class='xr-var-data-in' type='checkbox'><label for='data-12c7311c-d341-416c-94ea-d9dcbf434f24' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[ 0,  1,  2,  3],\n",
+       "    precipitation  (anchor_year, i_interval, x, y) float64 3.224 6.534 ... 5.705</pre><div class='xr-wrap' style='display:none'><div class='xr-header'><div class='xr-obj-type'>xarray.Dataset</div></div><ul class='xr-sections'><li class='xr-section-item'><input id='section-6d52c501-5e17-4b53-abc1-078ed304775c' class='xr-section-summary-in' type='checkbox' disabled ><label for='section-6d52c501-5e17-4b53-abc1-078ed304775c' class='xr-section-summary'  title='Expand/collapse section'>Dimensions:</label><div class='xr-section-inline-details'><ul class='xr-dim-list'><li><span class='xr-has-index'>anchor_year</span>: 3</li><li><span class='xr-has-index'>i_interval</span>: 4</li><li><span>x</span>: 2</li><li><span>y</span>: 2</li></ul></div><div class='xr-section-details'></div></li><li class='xr-section-item'><input id='section-370df202-dc76-4d2b-8938-b148d1968057' class='xr-section-summary-in' type='checkbox'  checked><label for='section-370df202-dc76-4d2b-8938-b148d1968057' class='xr-section-summary' >Coordinates: <span>(7)</span></label><div class='xr-section-inline-details'></div><div class='xr-section-details'><ul class='xr-var-list'><li class='xr-var-item'><div class='xr-var-name'><span>index</span></div><div class='xr-var-dims'>(anchor_year, i_interval)</div><div class='xr-var-dtype'>int64</div><div class='xr-var-preview xr-preview'>0 1 2 3 4 5 6 7 8 9 10 11</div><input id='attrs-14063db9-10e6-4727-9ef7-04a219ce94c6' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-14063db9-10e6-4727-9ef7-04a219ce94c6' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-23a26821-07a8-42b3-a00e-a2e5a89a5abf' class='xr-var-data-in' type='checkbox'><label for='data-23a26821-07a8-42b3-a00e-a2e5a89a5abf' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[ 0,  1,  2,  3],\n",
        "       [ 4,  5,  6,  7],\n",
-       "       [ 8,  9, 10, 11]])</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span>interval</span></div><div class='xr-var-dims'>(anchor_year, i_interval)</div><div class='xr-var-dtype'>object</div><div class='xr-var-preview xr-preview'>(2018-07-17, 2018-10-15] ... (20...</div><input id='attrs-627783b7-6885-4165-b8a4-a4df306e7948' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-627783b7-6885-4165-b8a4-a4df306e7948' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-b508c609-d5ab-4af2-aa96-914326337801' class='xr-var-data-in' type='checkbox'><label for='data-b508c609-d5ab-4af2-aa96-914326337801' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[Interval(&#x27;2018-07-17&#x27;, &#x27;2018-10-15&#x27;, closed=&#x27;right&#x27;),\n",
+       "       [ 8,  9, 10, 11]], dtype=int64)</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span>interval</span></div><div class='xr-var-dims'>(anchor_year, i_interval)</div><div class='xr-var-dtype'>object</div><div class='xr-var-preview xr-preview'>(2018-07-17, 2018-10-15] ... (20...</div><input id='attrs-4c26f79c-e675-41e9-85a8-0bc76d2d45b6' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-4c26f79c-e675-41e9-85a8-0bc76d2d45b6' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-cd013903-727a-4a8a-9193-2862795a3f33' class='xr-var-data-in' type='checkbox'><label for='data-cd013903-727a-4a8a-9193-2862795a3f33' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[Interval(&#x27;2018-07-17&#x27;, &#x27;2018-10-15&#x27;, closed=&#x27;right&#x27;),\n",
        "        Interval(&#x27;2018-04-18&#x27;, &#x27;2018-07-17&#x27;, closed=&#x27;right&#x27;),\n",
        "        Interval(&#x27;2018-01-18&#x27;, &#x27;2018-04-18&#x27;, closed=&#x27;right&#x27;),\n",
        "        Interval(&#x27;2017-10-20&#x27;, &#x27;2018-01-18&#x27;, closed=&#x27;right&#x27;)],\n",
@@ -1551,9 +1525,9 @@
        "        Interval(&#x27;2020-04-18&#x27;, &#x27;2020-07-17&#x27;, closed=&#x27;right&#x27;),\n",
        "        Interval(&#x27;2020-01-19&#x27;, &#x27;2020-04-18&#x27;, closed=&#x27;right&#x27;),\n",
        "        Interval(&#x27;2019-10-21&#x27;, &#x27;2020-01-19&#x27;, closed=&#x27;right&#x27;)]],\n",
-       "      dtype=object)</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span>lon</span></div><div class='xr-var-dims'>(x, y)</div><div class='xr-var-dtype'>float64</div><div class='xr-var-preview xr-preview'>-99.83 -99.32 -99.79 -99.23</div><input id='attrs-eb27d48e-748b-4ad5-8581-82dc1dd01df1' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-eb27d48e-748b-4ad5-8581-82dc1dd01df1' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-9b950220-78b3-405d-a340-bea8101b6609' class='xr-var-data-in' type='checkbox'><label for='data-9b950220-78b3-405d-a340-bea8101b6609' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[-99.83, -99.32],\n",
-       "       [-99.79, -99.23]])</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span>lat</span></div><div class='xr-var-dims'>(x, y)</div><div class='xr-var-dtype'>float64</div><div class='xr-var-preview xr-preview'>42.25 42.21 42.63 42.59</div><input id='attrs-b820f629-779c-4c31-ae88-d82b3fd057c5' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-b820f629-779c-4c31-ae88-d82b3fd057c5' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-67e5cb56-24be-4492-b51d-5f11e6e2de3a' class='xr-var-data-in' type='checkbox'><label for='data-67e5cb56-24be-4492-b51d-5f11e6e2de3a' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[42.25, 42.21],\n",
-       "       [42.63, 42.59]])</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span class='xr-has-index'>anchor_year</span></div><div class='xr-var-dims'>(anchor_year)</div><div class='xr-var-dtype'>int64</div><div class='xr-var-preview xr-preview'>2018 2019 2020</div><input id='attrs-6faef08d-26dc-48f9-a80a-5cb45e254eb7' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-6faef08d-26dc-48f9-a80a-5cb45e254eb7' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-e709378f-cad7-41d7-9a1e-855c739e3e8f' class='xr-var-data-in' type='checkbox'><label for='data-e709378f-cad7-41d7-9a1e-855c739e3e8f' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([2018, 2019, 2020])</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span class='xr-has-index'>i_interval</span></div><div class='xr-var-dims'>(i_interval)</div><div class='xr-var-dtype'>int64</div><div class='xr-var-preview xr-preview'>0 1 2 3</div><input id='attrs-ae4490b8-9acb-4517-ab78-74d19a324253' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-ae4490b8-9acb-4517-ab78-74d19a324253' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-2f121496-6c12-4a0c-8a69-8d722b4f0ec4' class='xr-var-data-in' type='checkbox'><label for='data-2f121496-6c12-4a0c-8a69-8d722b4f0ec4' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([0, 1, 2, 3])</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span>target</span></div><div class='xr-var-dims'>(i_interval)</div><div class='xr-var-dtype'>bool</div><div class='xr-var-preview xr-preview'>True False False False</div><input id='attrs-99a055bb-c671-44f7-bebe-0eeffa5466e1' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-99a055bb-c671-44f7-bebe-0eeffa5466e1' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-7fcad944-abb4-4735-8a6a-4b04f401c55f' class='xr-var-data-in' type='checkbox'><label for='data-7fcad944-abb4-4735-8a6a-4b04f401c55f' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([ True, False, False, False])</pre></div></li></ul></div></li><li class='xr-section-item'><input id='section-e46a589a-0fdf-4c70-ba7a-e776e0bcdda7' class='xr-section-summary-in' type='checkbox'  checked><label for='section-e46a589a-0fdf-4c70-ba7a-e776e0bcdda7' class='xr-section-summary' >Data variables: <span>(2)</span></label><div class='xr-section-inline-details'></div><div class='xr-section-details'><ul class='xr-var-list'><li class='xr-var-item'><div class='xr-var-name'><span>temperature</span></div><div class='xr-var-dims'>(anchor_year, i_interval, x, y)</div><div class='xr-var-dtype'>float64</div><div class='xr-var-preview xr-preview'>14.52 17.14 16.07 ... 13.85 12.32</div><input id='attrs-241c1133-f9f4-42e3-9505-91c59f9819bc' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-241c1133-f9f4-42e3-9505-91c59f9819bc' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-919f405a-89af-4cc7-8061-f1dbc2842e5d' class='xr-var-data-in' type='checkbox'><label for='data-919f405a-89af-4cc7-8061-f1dbc2842e5d' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[[[14.51807846, 17.14073688],\n",
+       "      dtype=object)</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span>lon</span></div><div class='xr-var-dims'>(x, y)</div><div class='xr-var-dtype'>float64</div><div class='xr-var-preview xr-preview'>-99.83 -99.32 -99.79 -99.23</div><input id='attrs-5d18999a-11e7-4ecb-af4e-7da58be62020' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-5d18999a-11e7-4ecb-af4e-7da58be62020' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-d867670d-efa0-4f52-b537-f5e6c4e1d11c' class='xr-var-data-in' type='checkbox'><label for='data-d867670d-efa0-4f52-b537-f5e6c4e1d11c' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[-99.83, -99.32],\n",
+       "       [-99.79, -99.23]])</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span>lat</span></div><div class='xr-var-dims'>(x, y)</div><div class='xr-var-dtype'>float64</div><div class='xr-var-preview xr-preview'>42.25 42.21 42.63 42.59</div><input id='attrs-38fa4498-1e03-4f70-830c-0c10eb9b8d38' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-38fa4498-1e03-4f70-830c-0c10eb9b8d38' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-0f4c351f-ac59-4eb4-b8db-7a7899fe8336' class='xr-var-data-in' type='checkbox'><label for='data-0f4c351f-ac59-4eb4-b8db-7a7899fe8336' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[42.25, 42.21],\n",
+       "       [42.63, 42.59]])</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span class='xr-has-index'>anchor_year</span></div><div class='xr-var-dims'>(anchor_year)</div><div class='xr-var-dtype'>int64</div><div class='xr-var-preview xr-preview'>2018 2019 2020</div><input id='attrs-1b788e67-9419-4bf8-aae5-f756359a52dc' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-1b788e67-9419-4bf8-aae5-f756359a52dc' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-d92d4f7d-5466-4476-8da3-33a3ef0df3dc' class='xr-var-data-in' type='checkbox'><label for='data-d92d4f7d-5466-4476-8da3-33a3ef0df3dc' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([2018, 2019, 2020], dtype=int64)</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span class='xr-has-index'>i_interval</span></div><div class='xr-var-dims'>(i_interval)</div><div class='xr-var-dtype'>int64</div><div class='xr-var-preview xr-preview'>0 1 2 3</div><input id='attrs-bd2901d5-e071-4f68-a0c9-67df5c62b5dc' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-bd2901d5-e071-4f68-a0c9-67df5c62b5dc' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-8f806add-f44a-4902-ab17-dcadbe741158' class='xr-var-data-in' type='checkbox'><label for='data-8f806add-f44a-4902-ab17-dcadbe741158' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([0, 1, 2, 3], dtype=int64)</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span>target</span></div><div class='xr-var-dims'>(i_interval)</div><div class='xr-var-dtype'>bool</div><div class='xr-var-preview xr-preview'>True False False False</div><input id='attrs-663e5390-f3fc-40ab-a947-0fae4be215ef' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-663e5390-f3fc-40ab-a947-0fae4be215ef' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-a16fb8b4-d7fb-4ecb-9cb7-8dcc0fe5bcf0' class='xr-var-data-in' type='checkbox'><label for='data-a16fb8b4-d7fb-4ecb-9cb7-8dcc0fe5bcf0' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([ True, False, False, False])</pre></div></li></ul></div></li><li class='xr-section-item'><input id='section-6153fdf2-453e-4206-a6cd-43846a61a697' class='xr-section-summary-in' type='checkbox'  checked><label for='section-6153fdf2-453e-4206-a6cd-43846a61a697' class='xr-section-summary' >Data variables: <span>(2)</span></label><div class='xr-section-inline-details'></div><div class='xr-section-details'><ul class='xr-var-list'><li class='xr-var-item'><div class='xr-var-name'><span>temperature</span></div><div class='xr-var-dims'>(anchor_year, i_interval, x, y)</div><div class='xr-var-dtype'>float64</div><div class='xr-var-preview xr-preview'>14.52 17.14 16.07 ... 13.85 12.32</div><input id='attrs-ec2615c3-b6bc-4837-a9d7-08941a2e50bb' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-ec2615c3-b6bc-4837-a9d7-08941a2e50bb' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-b7a0722d-36b1-4132-bd6c-5eba9efe990f' class='xr-var-data-in' type='checkbox'><label for='data-b7a0722d-36b1-4132-bd6c-5eba9efe990f' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[[[14.51807846, 17.14073688],\n",
        "         [16.06736697, 13.39468062]],\n",
        "\n",
        "        [[18.33493478, 20.80919455],\n",
@@ -1589,7 +1563,7 @@
        "         [13.65735477, 13.02433449]],\n",
        "\n",
        "        [[11.74543481, 15.09573755],\n",
-       "         [13.84770432, 12.32257394]]]])</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span>precipitation</span></div><div class='xr-var-dims'>(anchor_year, i_interval, x, y)</div><div class='xr-var-dtype'>float64</div><div class='xr-var-preview xr-preview'>3.224 6.534 5.071 ... 5.762 5.705</div><input id='attrs-534e2b2b-962e-45d5-aac5-a1460254471e' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-534e2b2b-962e-45d5-aac5-a1460254471e' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-14a97d31-a544-44d3-9563-09cf26f94bcb' class='xr-var-data-in' type='checkbox'><label for='data-14a97d31-a544-44d3-9563-09cf26f94bcb' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[[[3.22395354, 6.53411827],\n",
+       "         [13.84770432, 12.32257394]]]])</pre></div></li><li class='xr-var-item'><div class='xr-var-name'><span>precipitation</span></div><div class='xr-var-dims'>(anchor_year, i_interval, x, y)</div><div class='xr-var-dtype'>float64</div><div class='xr-var-preview xr-preview'>3.224 6.534 5.071 ... 5.762 5.705</div><input id='attrs-a7cce781-335f-4435-99e9-8b8e64614113' class='xr-var-attrs-in' type='checkbox' disabled><label for='attrs-a7cce781-335f-4435-99e9-8b8e64614113' title='Show/Hide attributes'><svg class='icon xr-icon-file-text2'><use xlink:href='#icon-file-text2'></use></svg></label><input id='data-3142930a-b2bd-4c3c-aecd-115f8beb0eab' class='xr-var-data-in' type='checkbox'><label for='data-3142930a-b2bd-4c3c-aecd-115f8beb0eab' title='Show/Hide data repr'><svg class='icon xr-icon-database'><use xlink:href='#icon-database'></use></svg></label><div class='xr-var-attrs'><dl class='xr-attrs'></dl></div><div class='xr-var-data'><pre>array([[[[3.22395354, 6.53411827],\n",
        "         [5.07125996, 3.45648475]],\n",
        "\n",
        "        [[5.59384251, 4.6572203 ],\n",
@@ -1625,7 +1599,7 @@
        "         [5.86267549, 5.35799785]],\n",
        "\n",
        "        [[3.4530543 , 4.08126979],\n",
-       "         [5.76219244, 5.70498641]]]])</pre></div></li></ul></div></li><li class='xr-section-item'><input id='section-ed1d0d28-7622-4294-98f0-d090348ce612' class='xr-section-summary-in' type='checkbox' disabled ><label for='section-ed1d0d28-7622-4294-98f0-d090348ce612' class='xr-section-summary'  title='Expand/collapse section'>Attributes: <span>(0)</span></label><div class='xr-section-inline-details'></div><div class='xr-section-details'><dl class='xr-attrs'></dl></div></li></ul></div></div>"
+       "         [5.76219244, 5.70498641]]]])</pre></div></li></ul></div></li><li class='xr-section-item'><input id='section-84d959d5-3cfb-4420-a221-76da3fd5c328' class='xr-section-summary-in' type='checkbox' disabled ><label for='section-84d959d5-3cfb-4420-a221-76da3fd5c328' class='xr-section-summary'  title='Expand/collapse section'>Attributes: <span>(0)</span></label><div class='xr-section-inline-details'></div><div class='xr-section-details'><dl class='xr-attrs'></dl></div></li></ul></div></div>"
       ],
       "text/plain": [
        "<xarray.Dataset>\n",
@@ -1644,13 +1618,13 @@
        "    precipitation  (anchor_year, i_interval, x, y) float64 3.224 6.534 ... 5.705"
       ]
      },
-     "execution_count": 8,
+     "execution_count": 7,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "ds_r = calendar.resample(ds)\n",
+    "ds_r = s2spy.time.resample(calendar, ds)\n",
     "ds_r"
    ]
   },
@@ -1664,7 +1638,7 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 3.9.13 ('s2spy')",
+   "display_name": "Python 3.10.5 ('ai4s2s')",
    "language": "python",
    "name": "python3"
   },
@@ -1678,12 +1652,12 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.13"
+   "version": "3.10.5"
   },
   "orig_nbformat": 4,
   "vscode": {
    "interpreter": {
-    "hash": "4c8901add0872cf59113c57929fb99881a7f0f46837d8b99712d7db552e5b9e5"
+    "hash": "a87ac8a6238ac8fffcd070769ea0ffc634a0ba702c52b706c3554dda7777b3fd"
    }
   }
  },
diff --git a/notebooks/tutorial_time.ipynb b/notebooks/tutorial_time.ipynb
index ce20d21..149fa99 100644
--- a/notebooks/tutorial_time.ipynb
+++ b/notebooks/tutorial_time.ipynb
@@ -9,7 +9,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 16,
+   "execution_count": 1,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -18,6 +18,25 @@
     "import s2spy.time"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "AdventCalendar(month=12, day=31, freq=7d, n_targets=1, max_lag=None)\n"
+     ]
+    }
+   ],
+   "source": [
+    "calendar = s2spy.time.AdventCalendar(anchor=(12, 31), freq=\"7d\")\n",
+    "calendar\n",
+    "print(calendar)"
+   ]
+  },
   {
    "cell_type": "markdown",
    "metadata": {},
@@ -27,25 +46,22 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
+   "execution_count": 3,
    "metadata": {},
    "outputs": [
     {
      "data": {
-      "text/html": [
-       "AdventCalendar(month=11, day=30, freq=90d)"
-      ],
       "text/plain": [
-       "AdventCalendar(month=11, day=30, freq=90d)"
+       "AdventCalendar(month=11, day=30, freq=90d, n_targets=1, max_lag=None)"
       ]
      },
-     "execution_count": 17,
+     "execution_count": 3,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "calendar = s2spy.time.AdventCalendar(anchor_date=(11, 30), freq='90d', n_targets=1)\n",
+    "calendar = s2spy.time.AdventCalendar(anchor=(11, 30), freq='90d', n_targets=1)\n",
     "calendar"
    ]
   },
@@ -58,7 +74,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 18,
+   "execution_count": 4,
    "metadata": {},
    "outputs": [
     {
@@ -117,14 +133,14 @@
        "2020         (2020-03-05, 2020-06-03]  (2019-12-06, 2020-03-05]  "
       ]
      },
-     "execution_count": 18,
+     "execution_count": 4,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "intervals = calendar.map_years(2020, 2020)\n",
-    "intervals"
+    "calendar = calendar.map_years(2020, 2020)\n",
+    "calendar.show()"
    ]
   },
   {
@@ -136,7 +152,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 19,
+   "execution_count": 5,
    "metadata": {},
    "outputs": [
     {
@@ -213,14 +229,14 @@
        "2020         (2020-03-05, 2020-06-03]  (2019-12-06, 2020-03-05]  "
       ]
      },
-     "execution_count": 19,
+     "execution_count": 5,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "intervals = calendar.map_years(2020, 2022)\n",
-    "intervals"
+    "calendar = calendar.map_years(2020, 2022)\n",
+    "calendar.show()"
    ]
   },
   {
@@ -232,7 +248,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
+   "execution_count": 6,
    "metadata": {},
    "outputs": [
     {
@@ -254,7 +270,7 @@
        "dtype: interval"
       ]
      },
-     "execution_count": 20,
+     "execution_count": 6,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -273,7 +289,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 21,
+   "execution_count": 7,
    "metadata": {},
    "outputs": [
     {
@@ -312,6 +328,13 @@
        "  </thead>\n",
        "  <tbody>\n",
        "    <tr>\n",
+       "      <th>2022</th>\n",
+       "      <td>(2022-09-01, 2022-11-30]</td>\n",
+       "      <td>(2022-06-03, 2022-09-01]</td>\n",
+       "      <td>(2022-03-05, 2022-06-03]</td>\n",
+       "      <td>(2021-12-05, 2022-03-05]</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
        "      <th>2021</th>\n",
        "      <td>(2021-09-01, 2021-11-30]</td>\n",
        "      <td>(2021-06-03, 2021-09-01]</td>\n",
@@ -325,20 +348,6 @@
        "      <td>(2020-03-05, 2020-06-03]</td>\n",
        "      <td>(2019-12-06, 2020-03-05]</td>\n",
        "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2019</th>\n",
-       "      <td>(2019-09-01, 2019-11-30]</td>\n",
-       "      <td>(2019-06-03, 2019-09-01]</td>\n",
-       "      <td>(2019-03-05, 2019-06-03]</td>\n",
-       "      <td>(2018-12-05, 2019-03-05]</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2018</th>\n",
-       "      <td>(2018-09-01, 2018-11-30]</td>\n",
-       "      <td>(2018-06-03, 2018-09-01]</td>\n",
-       "      <td>(2018-03-05, 2018-06-03]</td>\n",
-       "      <td>(2017-12-05, 2018-03-05]</td>\n",
-       "    </tr>\n",
        "  </tbody>\n",
        "</table>\n",
        "</div>"
@@ -346,20 +355,18 @@
       "text/plain": [
        "i_interval                 (target) 0                         1  \\\n",
        "anchor_year                                                       \n",
+       "2022         (2022-09-01, 2022-11-30]  (2022-06-03, 2022-09-01]   \n",
        "2021         (2021-09-01, 2021-11-30]  (2021-06-03, 2021-09-01]   \n",
        "2020         (2020-09-01, 2020-11-30]  (2020-06-03, 2020-09-01]   \n",
-       "2019         (2019-09-01, 2019-11-30]  (2019-06-03, 2019-09-01]   \n",
-       "2018         (2018-09-01, 2018-11-30]  (2018-06-03, 2018-09-01]   \n",
        "\n",
        "i_interval                          2                         3  \n",
        "anchor_year                                                      \n",
+       "2022         (2022-03-05, 2022-06-03]  (2021-12-05, 2022-03-05]  \n",
        "2021         (2021-03-05, 2021-06-03]  (2020-12-05, 2021-03-05]  \n",
-       "2020         (2020-03-05, 2020-06-03]  (2019-12-06, 2020-03-05]  \n",
-       "2019         (2019-03-05, 2019-06-03]  (2018-12-05, 2019-03-05]  \n",
-       "2018         (2018-03-05, 2018-06-03]  (2017-12-05, 2018-03-05]  "
+       "2020         (2020-03-05, 2020-06-03]  (2019-12-06, 2020-03-05]  "
       ]
      },
-     "execution_count": 21,
+     "execution_count": 7,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -371,17 +378,21 @@
     "# generate input data\n",
     "test_data = pd.Series(var, index=time_index)\n",
     "# map year to data\n",
-    "intervals = calendar.map_to_data(test_data)\n",
-    "intervals"
+    "calendar = calendar.map_to_data(test_data)\n",
+    "calendar.show()"
    ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
   }
  ],
  "metadata": {
-  "interpreter": {
-   "hash": "e7604e8ec5f09e490e10161e37a4725039efd3ab703d81b1b8a1e00d6741866c"
-  },
   "kernelspec": {
-   "display_name": "Python 3.8.5 ('base')",
+   "display_name": "Python 3.10.5 ('ai4s2s')",
    "language": "python",
    "name": "python3"
   },
@@ -395,9 +406,14 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.8.5"
+   "version": "3.10.5"
   },
-  "orig_nbformat": 4
+  "orig_nbformat": 4,
+  "vscode": {
+   "interpreter": {
+    "hash": "a87ac8a6238ac8fffcd070769ea0ffc634a0ba702c52b706c3554dda7777b3fd"
+   }
+  }
  },
  "nbformat": 4,
  "nbformat_minor": 2
diff --git a/s2spy/__init__.py b/s2spy/__init__.py
index b372190..edb7084 100644
--- a/s2spy/__init__.py
+++ b/s2spy/__init__.py
@@ -16,3 +16,5 @@ logging.getLogger(__name__).addHandler(logging.NullHandler())
 __author__ = "Yang Liu"
 __email__ = "y.liu@esciencecenter.nl"
 __version__ = "0.1.0"
+
+__all__ = ["dimensionality", "time", "traintest", "RGDR"]
diff --git a/s2spy/_base_calendar.py b/s2spy/_base_calendar.py
new file mode 100644
index 0000000..4f1f880
--- /dev/null
+++ b/s2spy/_base_calendar.py
@@ -0,0 +1,228 @@
+"""BaseCalendar is a template for specific implementations of different calendars.
+
+The BaseCalendar includes most methods required for all calendar operations, except for
+a set of abstract methods (e.g., __init__, _get_anchor, ...). These will have to be
+customized for each specific calendar.
+"""
+from abc import ABC
+from abc import abstractmethod
+from typing import Union
+import numpy as np
+import pandas as pd
+import xarray as xr
+from . import utils
+
+
+PandasData = (pd.Series, pd.DataFrame)
+XArrayData = (xr.DataArray, xr.Dataset)
+
+
+class BaseCalendar(ABC):
+    """Base calendar class which serves as a template for specific implementations."""
+
+    _mapping = None
+
+    @abstractmethod
+    def __init__(self, anchor, freq, n_targets: int = 1, max_lag: int = None):
+        """For initializing calendars, the following four variables will be required."""
+        self.n_targets = n_targets
+        self.max_lag = max_lag
+        self.anchor = anchor
+        self.freq = freq
+
+    @abstractmethod
+    def _get_anchor(self, year: int) -> pd.Timestamp:
+        """Method to generate an anchor timestamp for your specific calendar.
+
+        The method should return the exact timestamp of the end of the anchor_year's
+        0 interval, e.g., for the AdventCalendar:
+        pd.Timestamp(year, self.month, self.day)
+
+        Args:
+            year (int): anchor year for which the anchor timestamp should be generated
+
+        Returns:
+            pd.Timestamp: Timestamp at the end of the anchor_years interval 0.
+        """
+        return pd.Timestamp()
+
+    def _map_year(self, year: int) -> pd.Series:
+        """Internal routine to return a concrete IntervalIndex for the given year.
+
+        Since our calendars are used to study periodic events, they are first
+        instantiated without specific year(s). This method adds a specific year
+        to the calendar and returns an intervalindex, mapping the
+        Calendar to the given year.
+
+        Intended for internal use, in conjunction with map_years or map_to_data.
+
+        Args:
+            year: The year for which the Calendar will be realized
+
+        Returns:
+            Pandas Series filled with Intervals of the calendar's frequency, counting
+            backwards from the calendar's achor.
+        """
+        year_intervals = pd.interval_range(
+            end=self._get_anchor(year),
+            periods=self._get_nintervals(),
+            freq=self.freq,
+        )
+
+        year_intervals = pd.Series(year_intervals[::-1], name=str(year))
+        year_intervals.index.name = "i_interval"
+        return year_intervals
+
+    def _get_nintervals(self) -> int:
+        """Calculates the number of intervals that should be generated by _map year.
+
+        Returns:
+            int: Number of intervals for one anchor year.
+        """
+        periods_per_year = pd.Timedelta("365days") / pd.to_timedelta(self.freq)
+        return (
+            (self.max_lag + self.n_targets) if self.max_lag else int(periods_per_year)
+        )
+
+    def _get_skip_nyears(self) -> int:
+        """Determine how many years need to be skipped to avoid overlapping data.
+
+        Required to prevent information leakage between anchor years.
+
+        Returns:
+            int: Number of years that need to be skipped.
+        """
+        nintervals = self._get_nintervals()
+        periods_per_year = pd.Timedelta("365days") / pd.to_timedelta(self.freq)
+
+        return (
+            (np.ceil(nintervals / periods_per_year).astype(int) - 1)
+            if self.max_lag
+            else 0
+        )
+
+    def map_years(self, start: int, end: int):
+        """Adds a start and end year mapping to the calendar.
+
+        If the start and end years are the same, the intervals for only that single
+        year are returned by calendar.get_intervals().
+
+        Args:
+            start: The first year for which the calendar will be realized
+            end: The last year for which the calendar will be realized
+
+        Returns:
+            The calendar mapped to the input start and end year.
+        """
+        self._first_year = start
+        self._last_year = end
+        self._mapping = "years"
+        return self
+
+    def map_to_data(
+        self,
+        input_data: Union[pd.Series, pd.DataFrame, xr.Dataset, xr.DataArray],
+    ):
+        """Map the calendar to input data period.
+
+        Stores the first and last intervals of the input data to the calendar, so that
+        the intervals can cover the data to the greatest extent.
+
+        Args:
+            input_data: Input data for datetime mapping. Its index must be either
+                pandas.DatetimeIndex, or an xarray `time` coordinate with datetime
+                data.
+
+        Returns:
+            The calendar mapped to the input data period.
+        """
+        utils.check_timeseries(input_data)
+
+        # check the datetime order of input data
+        if isinstance(input_data, PandasData):
+            self._first_timestamp = input_data.index.min()
+            self._last_timestamp = input_data.index.max()
+        else:
+            self._first_timestamp = pd.Timestamp(input_data.time.min().values)
+            self._last_timestamp = pd.Timestamp(input_data.time.max().values)
+
+        self._mapping = "data"
+
+        return self
+
+    def _set_year_range_from_timestamps(self):
+        map_first_year = self._first_timestamp.year
+        map_last_year = self._last_timestamp.year
+
+        # ensure that the input data could always cover the advent calendar
+        # last date check
+        if self._map_year(map_last_year).iloc[0].right > self._last_timestamp:
+            map_last_year -= 1
+        # first date check
+        if self._map_year(map_first_year).iloc[-1].left < self._first_timestamp:
+            map_first_year += 1
+
+        # map year(s) and generate year realized advent calendar
+        if map_last_year >= map_first_year:
+            self._first_year = map_first_year
+            self._last_year = map_last_year
+        else:
+            raise ValueError(
+                "The input data could not cover the target advent calendar."
+            )
+
+        return self
+
+    def _label_targets(self, intervals: pd.DataFrame) -> pd.DataFrame:
+        """Adds target labels to the header row of the intervals.
+
+        Args:
+            intervals (pd.Dataframe): Dataframe with intervals.
+
+        Returns:
+            pd.Dataframe: Dataframe with target periods labelled.
+        """
+        return intervals.rename(
+            columns={i: f"(target) {i}" for i in range(self.n_targets)}
+        )
+
+    def get_intervals(self) -> pd.DataFrame:
+        """Method to retrieve updated intervals from the Calendar object."""
+        if self._mapping is None:
+            raise ValueError(
+                "Cannot retrieve intervals without map_years or "
+                "map_to_data having configured the calendar."
+            )
+        if self._mapping == "data":
+            self._set_year_range_from_timestamps()
+
+        year_range = range(
+            self._last_year, self._first_year - 1, -(self._get_skip_nyears() + 1)
+        )
+
+        intervals = pd.concat([self._map_year(year) for year in year_range], axis=1).T
+
+        intervals.index.name = "anchor_year"
+        return intervals
+
+    def show(self) -> pd.DataFrame:
+        """Displays the intervals the Calendar will generate for the current setup.
+
+        Returns:
+            pd.Dataframe: Dataframe containing the calendar intervals, with the target
+                periods labelled.
+        """
+        return self._label_targets(self.get_intervals())
+
+    def __repr__(self) -> str:
+        """String representation of the Calendar."""
+        props = ", ".join(
+            [f"{k}={v}" for k, v in self.__dict__.items() if not k.startswith("_")]
+        )
+        calendar_name = self.__class__.__name__
+        return f"{calendar_name}({props})"
+
+    @property
+    def flat(self) -> pd.DataFrame:
+        """Returns the flattened intervals."""
+        return self.get_intervals().stack()
diff --git a/s2spy/_resample.py b/s2spy/_resample.py
new file mode 100644
index 0000000..b61130f
--- /dev/null
+++ b/s2spy/_resample.py
@@ -0,0 +1,243 @@
+import warnings
+from typing import Union
+import numpy as np
+import pandas as pd
+import xarray as xr
+from . import utils
+
+
+PandasData = (pd.Series, pd.DataFrame)
+XArrayData = (xr.DataArray, xr.Dataset)
+
+
+def mark_target_period(
+    calendar, input_data: Union[pd.DataFrame, xr.Dataset]
+) -> Union[pd.DataFrame, xr.Dataset]:
+    """Mark interval periods that fall within the given number of target periods.
+
+    Pass a pandas Series/DataFrame with an 'i_interval' column, or an xarray
+    DataArray/Dataset with an 'i_interval' coordinate axis. It will return an
+    object with an added column in the Series/DataFrame or an
+    added coordinate axis in the DataSet called 'target'. This is a boolean
+    indicating whether the index time interval is a target period or not. This is
+    determined by the instance variable 'n_targets'.
+
+    Args:
+        input_data: Input data for resampling. For a Pandas object, one of its
+        columns must be called 'i_interval'. An xarray object requires a coordinate
+        axis named 'i_interval' containing an interval counter for every period.
+
+    Returns:
+        Input data with boolean marked target periods, similar data format as
+            given inputs.
+    """
+    if isinstance(input_data, PandasData):
+        input_data["target"] = np.zeros(input_data.index.size, dtype=bool)
+        input_data["target"] = input_data["target"].where(
+            input_data["i_interval"] >= calendar.n_targets, other=True
+        )
+
+    else:
+        # input data is xr.Dataset
+        target = input_data["i_interval"] < calendar.n_targets
+        input_data = input_data.assign_coords(coords={"target": target})
+
+    return input_data
+
+
+def resample_bins_constructor(
+    intervals: Union[pd.Series, pd.DataFrame]
+) -> pd.DataFrame:
+    """Restructures the interval object into a tidy DataFrame.
+
+    Args:
+        intervals: the output interval `pd.Series` or `pd.DataFrame` from the
+            `map_to_data` function.
+
+    Returns:
+        Pandas DataFrame with 'anchor_year', 'i_interval', and 'interval' as
+            columns.
+    """
+    # Make a tidy dataframe where the intervals are linked to the anchor year
+    if isinstance(intervals, pd.DataFrame):
+        bins = intervals.copy()
+        bins.index.rename("anchor_year", inplace=True)
+        bins = bins.melt(
+            var_name="i_interval", value_name="interval", ignore_index=False
+        )
+        bins = bins.sort_values(by=["anchor_year", "i_interval"])
+    else:
+        # Massage the dataframe into the same tidy format for a single year
+        bins = pd.DataFrame(intervals)
+        bins = bins.melt(
+            var_name="anchor_year", value_name="interval", ignore_index=False
+        )
+        bins.index.rename("i_interval", inplace=True)
+    bins = bins.reset_index()
+
+    return bins
+
+
+def resample_pandas(
+    calendar, input_data: Union[pd.Series, pd.DataFrame]
+) -> pd.DataFrame:
+    """Internal function to handle resampling of Pandas data.
+
+    Args:
+        input_data (pd.Series or pd.DataFrame): Data provided by the user to the
+            `resample` function
+
+    Returns:
+        pd.DataFrame: DataFrame containing the intervals and data resampled to
+            these intervals.
+    """
+    bins = resample_bins_constructor(calendar.get_intervals())
+
+    interval_index = pd.IntervalIndex(bins["interval"])
+    interval_groups = interval_index.get_indexer(input_data.index)
+    interval_means = input_data.groupby(interval_groups).mean()
+
+    # drop the -1 index, as it represents data outside of all intervals
+    interval_means = interval_means.loc[0:]
+
+    if isinstance(input_data, pd.DataFrame):
+        for name in input_data.keys():
+            bins[name] = interval_means[name].values
+    else:
+        name = "mean_data" if input_data.name is None else input_data.name
+        bins[name] = interval_means.values
+
+    return bins
+
+
+def resample_xarray(
+    calendar, input_data: Union[xr.DataArray, xr.Dataset]
+) -> xr.Dataset:
+    """Internal function to handle resampling of xarray data.
+
+    Args:
+        input_data (xr.DataArray or xr.Dataset): Data provided by the user to the
+            `resample` function
+
+    Returns:
+        xr.Dataset: Dataset containing the intervals and data resampled to
+            these intervals.
+    """
+    bins = resample_bins_constructor(calendar.get_intervals())
+
+    # Create the indexer to connect the input data with the intervals
+    interval_index = pd.IntervalIndex(bins["interval"])
+    interval_groups = interval_index.get_indexer(input_data["time"])
+    interval_means = input_data.groupby(
+        xr.IndexVariable("time", interval_groups)
+    ).mean()
+    interval_means = interval_means.rename({"time": "index"})
+
+    # drop the indices below 0, as it represents data outside of all intervals
+    interval_means = interval_means.sel(index=slice(0, None))
+
+    # Turn the bins dataframe into an xarray object and merge the data means into it
+    bins = bins.to_xarray()
+    if isinstance(interval_means, xr.DataArray) and interval_means.name is None:
+        interval_means = interval_means.rename("mean_values")
+    bins = xr.merge([bins, interval_means])
+
+    bins["anchor_year"] = bins["anchor_year"].astype(int)
+
+    # Turn the anchor year and interval count into coordinates
+    bins = bins.assign_coords(
+        {"anchor_year": bins["anchor_year"], "i_interval": bins["i_interval"]}
+    )
+    # Also make the intervals themselves a coordinate so they are not lost when
+    #   grabbing a variable from the resampled dataset.
+    bins = bins.set_coords("interval")
+
+    # Reshaping the dataset to have the anchor_year and i_interval as dimensions.
+    #   set anchor_year or i_interval as the main dimension
+    #   (otherwise index is kept as dimension)
+    bins = bins.swap_dims({"index": "anchor_year"})
+    bins = bins.set_index(ai=("anchor_year", "i_interval"))
+    bins = bins.unstack()
+    bins = bins.transpose("anchor_year", "i_interval", ...)
+
+    return bins
+
+
+def resample(
+    mapped_calendar,
+    input_data: Union[pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset],
+) -> Union[pd.DataFrame, xr.Dataset]:
+    """Resample input data to the calendar frequency.
+
+    Pass a pandas Series/DataFrame with a datetime axis, or an
+    xarray DataArray/Dataset with a datetime coordinate called 'time'.
+    It will return the same object with the datetimes resampled onto
+    the Calendar's Index by binning the data into the Calendar's intervals
+    and calculating the mean of each bin.
+
+    Note: this function is intended for upscaling operations, which means
+    the calendar frequency is larger than the original frequency of input data (e.g.
+    `freq` is "7days" and the input is daily data). It supports downscaling
+    operations but the user need to be careful since the returned values may contain
+    "NaN".
+
+    Args:
+        mapped_calendar: Calendar object with either a map_year or map_to_data mapping.
+        input_data: Input data for resampling. For a Pandas object its index must be
+            either a pandas.DatetimeIndex. An xarray object requires a dimension
+            named 'time' containing datetime values.
+
+    Raises:
+        UserWarning: If the calendar frequency is smaller than the frequency of
+            input data
+
+    Returns:
+        Input data resampled based on the calendar frequency, similar data format as
+            given inputs.
+
+    Example:
+        Assuming the input data is pd.DataFrame containing random values with index
+        from 2021-11-11 to 2021-11-01 at daily frequency.
+
+        >>> import s2spy.time
+        >>> import pandas as pd
+        >>> import numpy as np
+        >>> cal = s2spy.time.AdventCalendar(freq='180d')
+        >>> time_index = pd.date_range('20191201', '20211231', freq='1d')
+        >>> var = np.arange(len(time_index))
+        >>> input_data = pd.Series(var, index=time_index)
+        >>> cal = cal.map_to_data(input_data)
+        >>> bins = s2spy.time.resample(cal, input_data)
+        >>> bins # doctest: +NORMALIZE_WHITESPACE
+            anchor_year  i_interval                  interval  mean_data  target
+        0        2020           0  (2020-06-03, 2020-11-30]      275.5    True
+        1        2020           1  (2019-12-06, 2020-06-03]       95.5   False
+        2        2021           0  (2021-06-03, 2021-11-30]      640.5    True
+        3        2021           1  (2020-12-05, 2021-06-03]      460.5   False
+
+    """
+    if mapped_calendar.get_intervals() is None:
+        raise ValueError("Generate a calendar map before calling resample")
+
+    utils.check_timeseries(input_data)
+
+    if isinstance(input_data, PandasData):
+        # raise a warning for upscaling
+        # target frequency must be larger than the (absolute) input frequency
+        if input_data.index.freq:
+            input_freq = input_data.index.freq
+            input_freq = input_freq if input_freq.n > 0 else -input_freq
+            if pd.Timedelta(mapped_calendar.freq) < input_freq:
+                warnings.warn(
+                    """Target frequency is smaller than the original frequency.
+                    The resampled data will contain NaN values, as there is no data
+                    available within all intervals."""
+                )
+
+        resampled_data = resample_pandas(mapped_calendar, input_data)
+
+    else:
+        resampled_data = resample_xarray(mapped_calendar, input_data)
+
+    # mark target periods before returning the resampled data
+    return mark_target_period(mapped_calendar, resampled_data)
diff --git a/s2spy/rgdr/_rgdr.py b/s2spy/rgdr/_rgdr.py
index ff89f25..55b258e 100644
--- a/s2spy/rgdr/_rgdr.py
+++ b/s2spy/rgdr/_rgdr.py
@@ -66,7 +66,7 @@ def spherical_area(latitude: float, dlat: float, dlon: float = None) -> float:
     return spherical_area * surface_area_earth_km2
 
 
-def cluster_area(ds: xr.Dataset, cluster_label: float) -> float:
+def cluster_area(ds: xr.Dataset, cluster_label: float) -> np.ndarray:
     """Determines the total area of a cluster. Requires the input dataset to have the
     variables `area` and `cluster_labels`.
 
@@ -125,7 +125,7 @@ def weighted_groupby(ds: xr.Dataset, groupby: str, weight: str, method: str = "m
 
     # find stacked dim name:
     group_dims = list(groups)[0][1].dims  # Get ds of first group
-    stacked_dims = [dim for dim in group_dims.keys() if "stacked_" in dim]
+    stacked_dims = [dim for dim in group_dims.keys() if "stacked_" in dim] # type: ignore
 
     reduced_data = [
         getattr(g.weighted(g[weight]), method)(dim=stacked_dims) for _, g in groups
diff --git a/s2spy/time.py b/s2spy/time.py
index 8ae4a72..288c4d5 100644
--- a/s2spy/time.py
+++ b/s2spy/time.py
@@ -9,27 +9,26 @@ attention to the treatment of adjacent cycles, we avoid information leakage
 between train and test sets.
 
 Example:
-
     >>> import s2spy.time
     >>>
     >>> # Countdown the weeks until New Year's Eve
-    >>> calendar = s2spy.time.AdventCalendar(anchor_date=(12, 31), freq="7d")
+    >>> calendar = s2spy.time.AdventCalendar(anchor=(12, 31), freq="7d")
     >>> calendar
-    AdventCalendar(month=12, day=31, freq=7d)
-    >>> print(calendar)
-    52 periods of 7d leading up to 12/31.
+    AdventCalendar(month=12, day=31, freq=7d, n_targets=1, max_lag=None)
 
     >>> # Get the 180-day periods leading up to New Year's eve for the year 2020
-    >>> calendar = s2spy.time.AdventCalendar(anchor_date=(12, 31), freq='180d')
-    >>> calendar.map_years(2020, 2020) # doctest: +NORMALIZE_WHITESPACE
+    >>> calendar = s2spy.time.AdventCalendar(anchor=(12, 31), freq='180d')
+    >>> calendar = calendar.map_years(2020, 2020)
+    >>> calendar.show() # doctest: +NORMALIZE_WHITESPACE
     i_interval                 (target) 0                         1
     anchor_year
     2020         (2020-07-04, 2020-12-31]  (2020-01-06, 2020-07-04]
 
     >>> # Get the 180-day periods leading up to New Year's eve for 2020 - 2022 inclusive.
-    >>> calendar = s2spy.time.AdventCalendar(anchor_date=(12, 31), freq='180d')
+    >>> calendar = s2spy.time.AdventCalendar(anchor=(12, 31), freq='180d')
+    >>> calendar = calendar.map_years(2020, 2022)
     >>> # note the leap year:
-    >>> calendar.map_years(2020, 2022) # doctest: +NORMALIZE_WHITESPACE
+    >>> calendar.show() # doctest: +NORMALIZE_WHITESPACE
     i_interval                 (target) 0                         1
     anchor_year
     2022         (2022-07-04, 2022-12-31]  (2022-01-05, 2022-07-04]
@@ -48,24 +47,35 @@ Example:
     dtype: interval
 
 """
-import warnings
+import calendar as pycalendar
+import re
+from typing import Optional
 from typing import Tuple
-from typing import Union
 import numpy as np
 import pandas as pd
 import xarray as xr
+from ._base_calendar import BaseCalendar
+from ._resample import resample  # pylint: disable=unused-import
 
 
 PandasData = (pd.Series, pd.DataFrame)
 XArrayData = (xr.DataArray, xr.Dataset)
 
+month_mapping_dict = {
+    **{v.upper(): k for k, v in enumerate(pycalendar.month_abbr)},
+    **{v.upper(): k for k, v in enumerate(pycalendar.month_name)},
+}
+
 
-class AdventCalendar:
+class AdventCalendar(BaseCalendar):
     """Countdown time to anticipated anchor date or period of interest."""
 
     def __init__(
-        self, anchor_date: Tuple[int, int] = (11, 30), freq: str = "7d",
-        n_targets: int = 1, max_lag: int = None
+        self,
+        anchor: Tuple[int, int] = (11, 30),
+        freq: str = "7d",
+        n_targets: int = 1,
+        max_lag: Optional[int] = None,
     ) -> None:
         """Instantiate a basic calendar with minimal configuration.
 
@@ -74,7 +84,7 @@ class AdventCalendar:
         cycle time of one year.
 
         Args:
-            anchor_date: Tuple of the form (month, day). Effectively the origin
+            anchor: Tuple of the form (month, day). Effectively the origin
                 of the calendar. It will countdown until this date.
             freq: Frequency of the calendar.
             n_targets: integer specifying the number of target intervals in a period.
@@ -90,430 +100,219 @@ class AdventCalendar:
             eve.
 
             >>> import s2spy.time
-            >>> calendar = s2spy.time.AdventCalendar(anchor_date=(12, 31), freq="7d")
+            >>> calendar = s2spy.time.AdventCalendar(anchor=(12, 31), freq="7d")
             >>> calendar
-            AdventCalendar(month=12, day=31, freq=7d)
-            >>> print(calendar)
-            "52 periods of 7d leading up to 12/31."
+            AdventCalendar(month=12, day=31, freq=7d, n_targets=1, max_lag=None)
 
         """
-        self.month = anchor_date[0]
-        self.day = anchor_date[1]
+        if not re.fullmatch(r"\d*d", freq):
+            raise ValueError("Please input a frequency in the form of '10d'")
+        self.month = anchor[0]
+        self.day = anchor[1]
         self.freq = freq
-        self._n_intervals = pd.Timedelta("365days") // pd.to_timedelta(freq)
-        self._n_targets = n_targets
-        self._traintest = None
-        self._intervals = None
-
-        periods_per_year = pd.Timedelta("365days") / pd.to_timedelta(freq)
-        # Determine the amount of intervals, and number of anchor years to skip
-        if max_lag:
-            self._n_intervals = max_lag + self._n_targets
-            self._skip_years = np.ceil(self._n_intervals /
-                                       periods_per_year).astype(int) - 1
-        else:
-            self._n_intervals = int(periods_per_year)
-            self._skip_years = 0
-
-    def _map_year(self, year: int) -> pd.Series:
-        """Internal routine to return a concrete IntervalIndex for the given year.
-
-        Since the AdventCalendar represents a periodic event, it is first
-        instantiated without a specific year. This method adds a specific year
-        to the calendar and returns an intervalindex, applying the
-        AdvenctCalendar to this specific year.
+        self.n_targets = n_targets
+        self.max_lag = max_lag
+
+    def _get_anchor(self, year: int) -> pd.Timestamp:
+        """Generates a timestamp for the end of interval 0 in year.
 
         Args:
-            year: The year for which the AdventCalendar will be realized
+            year (int): anchor year for which the anchor timestamp should be generated
 
         Returns:
-            Pandas Series filled with Intervals of the calendar's frequency, counting
-            backwards from the calendar's anchor_date.
+            pd.Timestamp: Timestamp at the end of the anchor_years interval 0.
         """
-        anchor = pd.Timestamp(year, self.month, self.day)
-        intervals = pd.interval_range(
-            end=anchor, periods=self._n_intervals, freq=self.freq
-        )
-        intervals = pd.Series(intervals[::-1], name=str(year))
-        intervals.index.name = "i_interval"
-        return intervals
+        return pd.Timestamp(year, self.month, self.day)
 
-    def map_years(self, start: int = 1979, end: int = 2020) -> pd.DataFrame:
-        """Return a periodic IntervalIndex for the given years.
-        If the start and end years are the same, the Intervals for only that single
-        year are returned.
 
-        Args:
-            start: The first year for which the calendar will be realized
-            end: The last year for which the calendar will be realized
+class MonthlyCalendar(BaseCalendar):
+    """Countdown time to anticipated anchor month, in steps of whole months."""
 
-        Returns:
-            Pandas DataFrame filled with Intervals of the calendar's frequency,
-            counting backwards from the calendar's anchor_date.
+    def __init__(
+        self,
+        anchor: str = "Dec",
+        freq: str = "1M",
+        n_targets: int = 1,
+        max_lag: Optional[int] = None,
+    ) -> None:
+        """Instantiate a basic monthly calendar with minimal configuration.
+
+        Set up the calendar with given freq ending exactly on the anchor month.
+        The index will extend back in time as many periods as fit within the
+        cycle time of one year.
+
+        Args:
+            anchor: Str in the form 'January' or 'Jan'. Effectively the origin
+                of the calendar. It will countdown up to this month.
+            freq: Frequency of the calendar, in the form '1M', '2M', etc.
+            n_targets: integer specifying the number of target intervals in a period.
+            max_lag: Maximum number of lag periods after the target period. If `None`,
+                the maximum lag will be determined by how many fit in each anchor year.
+                If a maximum lag is provided, the intervals can either only cover part
+                of the year, or extend over multiple years. In case of a large max_lag
+                number where the intervals extend over multiple years, anchor years will
+                be skipped to avoid overlapping intervals.
 
         Example:
+            Instantiate a calendar counting down the quarters (3 month periods) until
+            december.
 
             >>> import s2spy.time
-            >>> calendar = s2spy.time.AdventCalendar(anchor_date=(12, 31), freq='180d')
-            >>> # note the leap year:
-            >>> calendar.map_years(2020, 2022) # doctest: +NORMALIZE_WHITESPACE
-            i_interval                 (target) 0                         1
-            anchor_year
-            2022         (2022-07-04, 2022-12-31]  (2022-01-05, 2022-07-04]
-            2021         (2021-07-04, 2021-12-31]  (2021-01-05, 2021-07-04]
-            2020         (2020-07-04, 2020-12-31]  (2020-01-06, 2020-07-04]
-
-            >>> # To get a stacked representation:
-            >>> calendar.map_years(2020, 2022).flat
-            anchor_year  i_interval
-            2022         0             (2022-07-04, 2022-12-31]
-                         1             (2022-01-05, 2022-07-04]
-            2021         0             (2021-07-04, 2021-12-31]
-                         1             (2021-01-05, 2021-07-04]
-            2020         0             (2020-07-04, 2020-12-31]
-                         1             (2020-01-06, 2020-07-04]
-            dtype: interval
+            >>> calendar = s2spy.time.MonthlyCalendar(anchor='Dec', freq="3M")
+            >>> calendar
+            MonthlyCalendar(month=12, freq=3M, n_targets=1, max_lag=None)
 
         """
-        year_range = range(end, start - 1, -(self._skip_years + 1))
+        if not re.fullmatch(r"\d*M", freq):
+            raise ValueError("Please input a frequency in the form of '2M'")
+        self.month = month_mapping_dict[anchor.upper()]
+        self.freq = freq
+        self.n_targets = n_targets
+        self.max_lag = max_lag
 
-        self._intervals = pd.concat(
-            [self._map_year(year) for year in year_range], axis=1
-        ).T
+    def _get_anchor(self, year: int) -> pd.Timestamp:
+        """Generates a timestamp for the end of interval 0 in year.
 
-        self._intervals.index.name = "anchor_year"
+        Args:
+            year (int): anchor year for which the anchor timestamp should be generated
 
-        return self
+        Returns:
+            pd.Timestamp: Timestamp at the end of the anchor_years interval 0.
+        """
+        return pd.Timestamp(year, self.month, 1) + pd.tseries.offsets.MonthEnd(0)
 
-    def map_to_data(
-        self, input_data: Union[pd.Series, pd.DataFrame, xr.Dataset, xr.DataArray],
-    ) -> Union[pd.DataFrame, xr.Dataset]:
-        """Map the calendar to input data period.
+    def _get_nintervals(self) -> int:
+        """Calculates the number of intervals that should be generated by _map year.
 
-        Get datetime range from input data and generate corresponding interval index.
-        This method guarantees that the generated interval (calendar) indices would be
-        covered by the input
-        data.
-        Args:
-            input_data: Input data for datetime mapping. Its index must be either
-                pandas.DatetimeIndex, or an xarray `time` coordinate with datetime
-                data.
         Returns:
-            Pandas DataFrame or xarray Dataset filled with Intervals of the calendar's
-            frequency. (see also ``map_years``)
+            int: Number of intervals for one anchor year.
         """
-        # check the datetime order of input data
-        if isinstance(input_data, PandasData):
-            first_timestamp = input_data.index.min()
-            last_timestamp = input_data.index.max()
-            map_last_year = last_timestamp.year
-            map_first_year = first_timestamp.year
-        elif isinstance(input_data, XArrayData):
-            first_timestamp = input_data.time.min()
-            last_timestamp = input_data.time.max()
-            map_last_year = last_timestamp.dt.year.values
-            map_first_year = first_timestamp.dt.year.values
-        else:
-            raise ValueError(
-                "incompatible input data format, please pass a pandas or xarray object"
-            )
-
-        # ensure that the input data could always cover the advent calendar
-        # last date check
-        if self._map_year(map_last_year).iloc[0].right > last_timestamp:
-            map_last_year -= 1
-        # first date check
-        if self._map_year(map_first_year).iloc[-1].left < first_timestamp:
-            map_first_year += 1
-
-        # map year(s) and generate year realized advent calendar
-        if map_last_year >= map_first_year:
-            self.map_years(map_first_year, map_last_year)
-        else:
-            raise ValueError(
-                "The input data could not cover the target advent calendar."
-            )
-
-        return self
-
-    def _resample_bins_constructor(
-        self, intervals: Union[pd.Series, pd.DataFrame]
-    ) -> pd.DataFrame:
-        """
-        Restructures the interval object into a tidy DataFrame.
+        periods_per_year = 12 / int(self.freq.replace("M", ""))
+        return (
+            (self.max_lag + self.n_targets) if self.max_lag else int(periods_per_year)
+        )
 
-        Args:
-            intervals: the output interval `pd.Series` or `pd.DataFrame` from the
-                `map_to_data` function.
+    def _get_skip_nyears(self) -> int:
+        """Determine how many years need to be skipped to avoid overlapping data.
+
+        Required to prevent information leakage between anchor years.
 
         Returns:
-            Pandas DataFrame with 'anchor_year', 'i_interval', and 'interval' as
-                columns.
+            int: Number of years that need to be skipped.
         """
-        # Make a tidy dataframe where the intervals are linked to the anchor year
-        if isinstance(intervals, pd.DataFrame):
-            bins = intervals.copy()
-            bins.index.rename("anchor_year", inplace=True)
-            bins = bins.melt(
-                var_name="i_interval", value_name="interval", ignore_index=False
-            )
-            bins = bins.sort_values(by=["anchor_year", "i_interval"])
-        else:
-            # Massage the dataframe into the same tidy format for a single year
-            bins = pd.DataFrame(intervals)
-            bins = bins.melt(
-                var_name="anchor_year", value_name="interval", ignore_index=False
-            )
-            bins.index.rename("i_interval", inplace=True)
-        bins = bins.reset_index()
-
-        return bins
-
-    def _resample_pandas(
-        self, input_data: Union[pd.Series, pd.DataFrame]
-    ) -> pd.DataFrame:
-        """Internal function to handle resampling of Pandas data.
+        nmonths = int(self.freq.replace("M", ""))
+        return (np.ceil(nmonths / 12) - 1) if self.max_lag else 0
+
+    def _interval_as_month(self, interval):
+        """Turns an interval with pandas Timestamp values to a formatted string.
+
+        The string will contain the years and months, for a more intuitive
+        representation to the user.
 
         Args:
-            input_data (pd.Series or pd.DataFrame): Data provided by the user to the
-                `resample` function
+            interval (pd.Interval): Pandas interval.
 
         Returns:
-            pd.DataFrame: DataFrame containing the intervals and data resampled to
-                these intervals.
+            str: String in the form of '(2020 Jan, 2020 Feb]'
         """
+        left = interval.left.strftime("%Y %b")
+        right = interval.right.strftime("%Y %b")
+        return f"({left}, {right}]"
 
-        self.map_to_data(input_data)
-        bins = self._resample_bins_constructor(self._intervals)
+    def show(self) -> pd.DataFrame:
+        """Displays the intervals the Calendar will generate for the current setup.
 
-        interval_index = pd.IntervalIndex(bins["interval"])
-        interval_groups = interval_index.get_indexer(input_data.index)
-        interval_means = input_data.groupby(interval_groups).mean()
+        Returns:
+            pd.Dataframe: Dataframe containing the calendar intervals, with the target
+                periods labelled.
+        """
+        return self._label_targets(self.get_intervals()).applymap(
+            self._interval_as_month
+        )
 
-        # drop the -1 index, as it represents data outside of all intervals
-        interval_means = interval_means.loc[0:]
 
-        if isinstance(input_data, pd.DataFrame):
-            for name in input_data.keys():
-                bins[name] = interval_means[name].values
-        else:
-            name = "mean_data" if input_data.name is None else input_data.name
-            bins[name] = interval_means.values
+class WeeklyCalendar(BaseCalendar):
+    """Countdown time to anticipated anchor week number, in steps of calendar weeks."""
 
-        return bins
+    def __init__(
+        self,
+        anchor: int,
+        freq: str = "1W",
+        n_targets: int = 1,
+        max_lag: Optional[int] = None,
+    ) -> None:
+        """Instantiate a basic week number calendar with minimal configuration.
 
-    def _resample_xarray(
-        self, input_data: Union[xr.DataArray, xr.Dataset]
-    ) -> xr.Dataset:
-        """Internal function to handle resampling of xarray data.
+        Set up the calendar with given freq ending exactly on the anchor week.
+        The index will extend back in time as many weeks as fit within the
+        cycle time of one year (i.e. 52).
+        Note that the difference between this calendar and the AdventCalendar revolves
+        around the use of calendar weeks (Monday - Sunday), instead of 7-day periods.
 
         Args:
-            input_data (xr.DataArray or xr.Dataset): Data provided by the user to the
-                `resample` function
+            anchor: Int denoting the week number. Effectively the origin of the calendar.
+                It will countdown until this week.
+            freq: Frequency of the calendar, e.g. '2W'.
+            n_targets: integer specifying the number of target intervals in a period.
+            max_lag: Maximum number of lag periods after the target period. If `None`,
+                the maximum lag will be determined by how many fit in each anchor year.
+                If a maximum lag is provided, the intervals can either only cover part
+                of the year, or extend over multiple years. In case of a large max_lag
+                number where the intervals extend over multiple years, anchor years will
+                be skipped to avoid overlapping intervals.
 
-        Returns:
-            xr.Dataset: Dataset containing the intervals and data resampled to
-                these intervals."""
-
-        self.map_to_data(input_data)
-        bins = self._resample_bins_constructor(self._intervals)
-
-        # Create the indexer to connect the input data with the intervals
-        interval_index = pd.IntervalIndex(bins["interval"])
-        interval_groups = interval_index.get_indexer(input_data["time"])
-        interval_means = input_data.groupby(
-            xr.IndexVariable("time", interval_groups)
-        ).mean()
-        interval_means = interval_means.rename({"time": "index"})
-
-        # drop the indices below 0, as it represents data outside of all intervals
-        interval_means = interval_means.sel(index=slice(0, None))
-
-        # Turn the bins dataframe into an xarray object and merge the data means into it
-        bins = bins.to_xarray()
-        if isinstance(input_data, xr.Dataset):
-            bins = xr.merge([bins, interval_means])
-        else:
-            if interval_means.name is None:
-                interval_means = interval_means.rename("mean_values")
-            bins = xr.merge([bins, interval_means])
-        bins["anchor_year"] = bins["anchor_year"].astype(int)
-
-        # Turn the anchor year and interval count into coordinates
-        bins = bins.assign_coords(
-            {"anchor_year": bins["anchor_year"], "i_interval": bins["i_interval"]}
-        )
-        # Also make the intervals themselves a coordinate so they are not lost when
-        #   grabbing a variable from the resampled dataset.
-        bins = bins.set_coords('interval')
-
-        # Reshaping the dataset to have the anchor_year and i_interval as dimensions.
-        #   set anchor_year or i_interval as the main dimension
-        #   (otherwise index is kept as dimension)
-        bins = bins.swap_dims({'index': 'anchor_year'})
-        bins = bins.set_index(ai=('anchor_year', 'i_interval'))
-        bins = bins.unstack()
-        bins = bins.transpose("anchor_year", "i_interval", ...)
-
-        return bins
-
-    def resample(
-        self, input_data: Union[pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset]
-    ) -> Union[pd.DataFrame, xr.Dataset]:
-        """Resample input data to the calendar frequency.
-
-        Pass a pandas Series/DataFrame with a datetime axis, or an
-        xarray DataArray/Dataset with a datetime coordinate called 'time'.
-        It will return the same object with the datetimes resampled onto
-        the Calendar's Index by binning the data into the Calendar's intervals
-        and calculating the mean of each bin.
-
-        Note: this function is intended for upscaling operations, which means
-        the calendar frequency is larger than the original frequency of input data (e.g.
-        `freq` is "7days" and the input is daily data). It supports downscaling
-        operations but the user need to be careful since the returned values may contain
-        "NaN".
+        Example:
+            Instantiate a calendar counting down the weeks until week number 40.
 
-        Args:
-            input_data: Input data for resampling. For a Pandas object its index must be
-                either a pandas.DatetimeIndex. An xarray object requires a dimension
-                named 'time' containing datetime values.
+            >>> import s2spy.time
+            >>> calendar = s2spy.time.WeeklyCalendar(anchor=40, freq="1W")
+            >>> calendar
+            WeeklyCalendar(week=40, freq=1W, n_targets=1, max_lag=None)
 
-        Raises:
-            UserWarning: If the calendar frequency is smaller than the frequency of
-                input data
+        """
+        if not re.fullmatch(r"\d*W", freq):
+            raise ValueError("Please input a frequency in the form of '4W'")
 
-        Returns:
-            Input data resampled based on the calendar frequency, similar data format as
-                given inputs.
+        self.week = anchor
+        self.freq = freq
+        self.n_targets = n_targets
+        self.max_lag = max_lag
 
-        Example:
-            Assuming the input data is pd.DataFrame containing random values with index
-            from 2021-11-11 to 2021-11-01 at daily frequency.
+    def _get_anchor(self, year: int) -> pd.Timestamp:
+        """Generates a timestamp for the end of interval 0 in year.
 
-            >>> import s2spy.time
-            >>> import pandas as pd
-            >>> import numpy as np
-            >>> cal = s2spy.time.AdventCalendar(freq='180d')
-            >>> time_index = pd.date_range('20191201', '20211231', freq='1d')
-            >>> var = np.arange(len(time_index))
-            >>> input_data = pd.Series(var, index=time_index)
-            >>> bins = cal.resample(input_data)
-            >>> bins
-              anchor_year  i_interval                  interval  mean_data  target
-            0        2020           0  (2020-06-03, 2020-11-30]      275.5    True
-            1        2020           1  (2019-12-06, 2020-06-03]       95.5   False
-            2        2021           0  (2021-06-03, 2021-11-30]      640.5    True
-            3        2021           1  (2020-12-05, 2021-06-03]      460.5   False
+        Args:
+            year (int): anchor year for which the anchor timestamp should be generated
 
+        Returns:
+            pd.Timestamp: Timestamp at the end of the anchor_years interval 0.
         """
-        if not isinstance(input_data, PandasData + XArrayData):
-            raise ValueError("The input data is neither a pandas or xarray object")
-
-        if isinstance(input_data, PandasData):
-            if not isinstance(input_data.index, pd.DatetimeIndex):
-                raise ValueError("The input data does not have a datetime index.")
-
-            # raise a warning for upscaling
-            # target frequency must be larger than the (absolute) input frequency
-            if input_data.index.freq:
-                input_freq = input_data.index.freq
-                input_freq = input_freq if input_freq.n > 0 else -input_freq
-                if pd.Timedelta(self.freq) < input_freq:
-                    warnings.warn(
-                        """Target frequency is smaller than the original frequency.
-                        The resampled data will contain NaN values, as there is no data
-                        available within all intervals."""
-                    )
-
-            resampled_data = self._resample_pandas(input_data)
-
-        # Data must be xarray
-        else:
-            if "time" not in input_data.dims:
-                raise ValueError(
-                    "The input DataArray/Dataset does not contain a `time` dimension"
-                )
-            if not xr.core.common.is_np_datetime_like(input_data["time"].dtype):
-                raise ValueError("The `time` dimension is not of a datetime format")
-
-            resampled_data = self._resample_xarray(input_data)
-
-        # mark target periods before returning the resampled data
-        return self._mark_target_period(resampled_data)
-
-    def _label_targets(self, intervals):
-        return intervals.rename(
-            columns={i: f'(target) {i}' for i in range(self._n_targets)})
-
-    def __str__(self):
-        return f"{self._n_intervals} periods of {self.freq} leading up to {self.month}/{self.day}."
-
-    def __repr__(self):
-        if self._intervals is not None:
-            return repr(self._label_targets(self._intervals))
-
-        props = ", ".join(
-            [f"{k}={v}" for k, v in self.__dict__.items() if not k.startswith("_")]
-        )
-        return f"AdventCalendar({props})"
+        # Day 0 of a weeknumber is sunday. Weeks start on monday (strftime functionality)
+        return pd.to_datetime(f"{year}-{self.week}-0", format="%Y-%W-%w")
 
-    def _repr_html_(self):
-        """For jupyter notebook to load html compatiable version of __repr__."""
-        if self._intervals is not None:
-            # pylint: disable=protected-access
-            return self._label_targets(self._intervals)._repr_html_()
+    def _interval_as_weeknr(self, interval: pd.Interval) -> str:
+        """Turns an interval with pandas Timestamp values to a formatted string.
 
-        props = ", ".join(
-            [f"{k}={v}" for k, v in self.__dict__.items() if not k.startswith("_")]
-        )
-        return f"AdventCalendar({props})"
-
-    @property
-    def flat(self):
-        if self._intervals is not None:
-            return self._intervals.stack()
-        raise ValueError("The calendar is not initialized with intervals yet."
-                         "use `map_years` or `map_to_data` methods to set it up.")
-
-    def discard(self, max_lag):  # or "set_max_lag"
-        """Only keep indices up to the given max lag."""
-        # or think of a nicer way to discard unneeded info
-        raise NotImplementedError
-
-    def _mark_target_period(
-        self, input_data: Union[pd.DataFrame, xr.Dataset]
-    ) -> Union[pd.DataFrame, xr.Dataset]:
-        """Mark interval periods that fall within the given number of target periods.
-
-        Pass a pandas Series/DataFrame with an 'i_interval' column, or an xarray
-        DataArray/Dataset with an 'i_interval' coordinate axis. It will return an
-        object with an added column in the Series/DataFrame or an
-        added coordinate axis in the DataSet called 'target'. This is a boolean
-        indicating whether the index time interval is a target period or not. This is
-        determined by the instance variable 'n_targets'.
+        The string will contain the years and week numbers, for a more intuitive
+        representation to the user.
 
         Args:
-            input_data: Input data for resampling. For a Pandas object, one of its
-            columns must be called 'i_interval'. An xarray object requires a coordinate
-            axis named 'i_interval' containing an interval counter for every period.
+            interval (pd.Interval): Pandas interval.
 
         Returns:
-            Input data with boolean marked target periods, similar data format as
-                given inputs.
+            str: String in the form of '(2020-50, 2020-51]'
         """
-        if isinstance(input_data, PandasData):
-            input_data['target'] = np.zeros(input_data.index.size, dtype=bool)
-            input_data['target'] = input_data['target'].where(
-               input_data['i_interval'] >= self._n_targets, other=True)
-
-        else:
-            #input data is xr.Dataset
-            target = input_data['i_interval'] < self._n_targets
-            input_data = input_data.assign_coords(coords={'target': target})
+        left = interval.left.strftime("%Y-W%W")
+        right = interval.right.strftime("%Y-W%W")
+        return f"({left}, {right}]"
 
-        return input_data
+    def show(self) -> pd.DataFrame:
+        """Displays the intervals the Calendar will generate for the current setup.
 
-    def get_lagged_indices(self, lag=1):  # noqa
-        """Return indices shifted backward by given lag."""
-        raise NotImplementedError
+        Returns:
+            pd.Dataframe: Dataframe containing the calendar intervals, with the target
+                periods labelled.
+        """
+        return self._label_targets(self.get_intervals()).applymap(
+            self._interval_as_weeknr
+        )
diff --git a/s2spy/utils.py b/s2spy/utils.py
new file mode 100644
index 0000000..4f88764
--- /dev/null
+++ b/s2spy/utils.py
@@ -0,0 +1,38 @@
+import pandas as pd
+import xarray as xr
+
+
+PandasData = (pd.Series, pd.DataFrame)
+XArrayData = (xr.DataArray, xr.Dataset)
+
+
+def check_timeseries(data) -> None:
+    """Utility function to check input data.
+
+    Checks if:
+     - Input data is pd.Dataframe/pd.Series/xr.Dataset/xr.DataArray.
+     - Input data has a time index (pd), or a dim named `time` containing datetime
+       values
+    """
+    if not isinstance(data, PandasData + XArrayData):
+        raise ValueError("The input data is neither a pandas or xarray object")
+    if isinstance(data, PandasData):
+        check_time_dim_pandas(data)
+    elif isinstance(data, XArrayData):
+        check_time_dim_xarray(data)
+
+
+def check_time_dim_xarray(data) -> None:
+    """Utility function to check if xarray data has a time dimensions with time data."""
+    if "time" not in data.dims:
+        raise ValueError(
+            "The input DataArray/Dataset does not contain a `time` dimension"
+        )
+    if not xr.core.common.is_np_datetime_like(data["time"].dtype):
+        raise ValueError("The `time` dimension is not of a datetime format")
+
+
+def check_time_dim_pandas(data) -> None:
+    """Utility function to check if pandas data has an index with time data."""
+    if not isinstance(data.index, pd.DatetimeIndex):
+        raise ValueError("The input data does not have a datetime index.")
diff --git a/setup.cfg b/setup.cfg
index a4b9330..af69c11 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -48,15 +48,16 @@ install_requires =
 dev =
     bump2version
     coverage [toml]
-    prospector[with_pyroma]
     isort
+    mypy
+    myst_parser
+    prospector[with_pyroma]
     pytest
     pytest-cov
     sphinx
     sphinx_rtd_theme
     sphinx-autoapi
     tox
-    myst_parser
 publishing =
     twine
     wheel


Test patch:
diff --git a/s2spy/traintest.py b/s2spy/traintest.py
index a513369..16173be 100644
--- a/s2spy/traintest.py
+++ b/s2spy/traintest.py
@@ -3,6 +3,7 @@
 A collection of train/test splitting approaches for cross-validation.
 """
 from typing import Optional
+from typing import Type
 from typing import Union
 import numpy as np
 import pandas as pd
@@ -10,11 +11,8 @@ import xarray as xr
 from sklearn.model_selection._split import BaseCrossValidator
 
 
-SplitterClass = BaseCrossValidator
-
-
 def split_groups(
-    splitter: SplitterClass,
+    splitter: Type[BaseCrossValidator],
     data: Union[xr.Dataset, pd.DataFrame],
     key: Optional[str] = "anchor_year",
 ):
diff --git a/tests/test_dimensionality.py b/tests/test_dimensionality.py
index 054a731..77d4505 100644
--- a/tests/test_dimensionality.py
+++ b/tests/test_dimensionality.py
@@ -7,8 +7,8 @@ import s2spy.dimensionality
 class TestDimensionality:
     def test_RGDR(self):
         with pytest.raises(NotImplementedError):
-            s2spy.dimensionality.rgdr('series')
-    
+            s2spy.dimensionality.rgdr("series")
+
     def test_PCA(self):
         with pytest.raises(NotImplementedError):
             s2spy.dimensionality.pca()
diff --git a/tests/test_rgdr/test_rgdr.py b/tests/test_rgdr/test_rgdr.py
index bf646ba..155b431 100644
--- a/tests/test_rgdr/test_rgdr.py
+++ b/tests/test_rgdr/test_rgdr.py
@@ -6,6 +6,7 @@ import xarray as xr
 from s2spy import RGDR
 from s2spy.rgdr import _rgdr
 from s2spy.time import AdventCalendar
+from s2spy.time import resample
 
 
 # pylint: disable=protected-access
@@ -104,7 +105,7 @@ test_file_path = "./tests/test_rgdr/test_data"
 class TestMapRegions:
     @pytest.fixture(autouse=True)
     def dummy_calendar(self):
-        return AdventCalendar(anchor_date=(8, 31), freq="30d")
+        return AdventCalendar(anchor=(8, 31), freq="30d")
 
     @pytest.fixture(autouse=True)
     def example_target(self):
@@ -120,8 +121,9 @@ class TestMapRegions:
 
     @pytest.fixture(autouse=True)
     def example_corr(self, dummy_calendar, example_target, example_field):
-        field = dummy_calendar.resample(example_field)
-        target = dummy_calendar.resample(example_target)
+        cal = dummy_calendar.map_to_data(example_field)
+        field = resample(cal, example_field)
+        target = resample(cal, example_target)
 
         field["corr"], field["p_val"] = _rgdr.correlation(
             field.sst, target.ts.sel(i_interval=0), corr_dim="anchor_year"
diff --git a/tests/test_time.py b/tests/test_time.py
index a103647..7690288 100644
--- a/tests/test_time.py
+++ b/tests/test_time.py
@@ -5,9 +5,9 @@ import pandas as pd
 import pytest
 import xarray as xr
 from s2spy.time import AdventCalendar
-
-
-# pylint: disable=protected-access
+from s2spy.time import MonthlyCalendar
+from s2spy.time import WeeklyCalendar
+from s2spy.time import resample
 
 
 def interval(start, end):
@@ -17,9 +17,10 @@ def interval(start, end):
 
 class TestAdventCalendar:
     """Test AdventCalendar methods."""
+
     @pytest.fixture(autouse=True)
     def dummy_calendar(self):
-        cal = AdventCalendar(anchor_date=(12, 31), freq="240d")
+        cal = AdventCalendar(anchor=(12, 31), freq="240d")
         cal.map_years(2021, 2021)
         return cal
 
@@ -29,46 +30,115 @@ class TestAdventCalendar:
 
     def test_repr(self):
         cal = AdventCalendar()
-        assert repr(cal) == "AdventCalendar(month=11, day=30, freq=7d)"
+        assert repr(cal) == (
+            "AdventCalendar(month=11, day=30, freq=7d, n_targets=1, max_lag=None)"
+        )
 
-    def test_repr_with_intervals(self, dummy_calendar):
-        expected_calendar_repr = ('i_interval (target) 0\n'
-                                  'anchor_year \n'
-                                  '2021 (2021-05-05, 2021-12-31]')
+    def test_show(self, dummy_calendar):
+        expected_calendar_repr = (
+            "i_interval (target) 0\n anchor_year \n 2021 (2021-05-05, 2021-12-31]"
+        )
         expected_calendar_repr = expected_calendar_repr.replace(" ", "")
-        assert repr(dummy_calendar).replace(" ", "") == expected_calendar_repr
+        assert repr(dummy_calendar.show()).replace(" ", "") == expected_calendar_repr
 
-    def test_str(self):
-        cal = AdventCalendar()
-        assert str(cal) == "52 periods of 7d leading up to 11/30."
+    def test_flat(self):
+        expected = np.array([interval("2021-05-05", "2021-12-31")])
+        cal = AdventCalendar(anchor=(12, 31), freq="240d")
+        cal.map_years(2021, 2021)
+        assert np.array_equal(cal.flat, expected)
 
-    def test_discard(self):
+    def test_no_intervals(self):
         cal = AdventCalendar()
+        with pytest.raises(ValueError):
+            cal.get_intervals()
 
-        with pytest.raises(NotImplementedError):
-            cal.discard(max_lag=5)
+    def test_incorrect_freq(self):
+        with pytest.raises(ValueError):
+            AdventCalendar(freq='2W')
 
-    def test_get_lagged_indices(self):
-        cal = AdventCalendar()
+class TestMonthlyCalendar:
+    """Test MonthlyCalendar methods."""
+
+    @pytest.fixture(autouse=True)
+    def dummy_calendar(self):
+        cal = MonthlyCalendar(anchor='Dec', freq="8M")
+        cal.map_years(2021, 2021)
+        return cal
+
+    def test_init(self):
+        cal = MonthlyCalendar(anchor='Dec', freq="2M")
+        assert isinstance(cal, MonthlyCalendar)
+
+    def test_repr(self):
+        cal = MonthlyCalendar(anchor='Dec', freq="2M")
+        assert repr(cal) == (
+            "MonthlyCalendar(month=12, freq=2M, n_targets=1, max_lag=None)"
+        )
 
-        with pytest.raises(NotImplementedError):
-            cal.get_lagged_indices()
+    def test_show(self, dummy_calendar):
+        expected_calendar_repr = (
+            "i_interval (target) 0\n anchor_year \n 2021 (2021 Apr, 2021 Dec]"
+        )
+        expected_calendar_repr = expected_calendar_repr.replace(" ", "")
+        assert repr(dummy_calendar.show()).replace(" ", "") == expected_calendar_repr
 
     def test_flat(self, dummy_calendar):
-        expected = np.array([interval("2021-05-05", "2021-12-31")])
-        cal = AdventCalendar(anchor_date=(12, 31), freq="240d")
+        expected = np.array([interval("2021-04-30", "2021-12-31")])
+        assert np.array_equal(dummy_calendar.flat, expected)
+
+    def test_no_intervals(self):
+        cal = MonthlyCalendar()
+        with pytest.raises(ValueError):
+            cal.get_intervals()
+
+    def test_incorrect_freq(self):
+        with pytest.raises(ValueError):
+            MonthlyCalendar(freq='2d')
+
+class TestWeeklyCalendar:
+    """Test WeeklyCalendar methods."""
+
+    @pytest.fixture(autouse=True)
+    def dummy_calendar(self):
+        cal = WeeklyCalendar(anchor=48, freq="30W")
         cal.map_years(2021, 2021)
+        return cal
+
+    def test_init(self):
+        cal = WeeklyCalendar(anchor=48, freq="30W")
+        assert isinstance(cal, WeeklyCalendar)
+
+    def test_repr(self):
+        cal = WeeklyCalendar(anchor=48, freq="30W")
+        assert repr(cal) == (
+            "WeeklyCalendar(week=48, freq=30W, n_targets=1, max_lag=None)"
+        )
+
+    def test_show(self, dummy_calendar):
+        expected_calendar_repr = (
+            "i_interval (target) 0\n anchor_year \n 2021 (2021-W18, 2021-W48]"
+        )
+        expected_calendar_repr = expected_calendar_repr.replace(" ", "")
+        assert repr(dummy_calendar.show()).replace(" ", "") == expected_calendar_repr
+
+    def test_flat(self, dummy_calendar):
+        expected = np.array([interval("2021-05-09", "2021-12-05")])
         assert np.array_equal(dummy_calendar.flat, expected)
 
-    def test_flat_no_intervals(self):
-        cal = AdventCalendar()
+    def test_no_intervals(self):
+        cal = WeeklyCalendar(anchor=40)
         with pytest.raises(ValueError):
-            cal.flat # pylint: disable=pointless-statement
+            cal.get_intervals()
+
+    def test_incorrect_freq(self):
+        with pytest.raises(ValueError):
+            WeeklyCalendar(anchor=40, freq='2d')
 
 class TestMap:
     """Test map to year(s)/data methods"""
+
     def test_map_years(self):
-        cal = AdventCalendar(anchor_date=(12, 31), freq="180d")
+        cal = AdventCalendar(anchor=(12, 31), freq="180d")
         cal.map_years(2020, 2021)
         expected = np.array(
             [
@@ -82,58 +152,64 @@ class TestMap:
                 ],
             ]
         )
-        assert np.array_equal(cal._intervals, expected)
+        assert np.array_equal(cal.get_intervals(), expected)
 
     def test_map_years_single(self):
-        cal = AdventCalendar(anchor_date=(12, 31), freq="180d")
+        cal = AdventCalendar(anchor=(12, 31), freq="180d")
         cal.map_years(2020, 2020)
-        expected = np.array([
+        expected = np.array(
             [
-                interval("2020-07-04", "2020-12-31"),
-                interval("2020-01-06", "2020-07-04"),
+                [
+                    interval("2020-07-04", "2020-12-31"),
+                    interval("2020-01-06", "2020-07-04"),
+                ]
             ]
-        ])
-        assert np.array_equal(cal._intervals, expected)
+        )
+        assert np.array_equal(cal.get_intervals(), expected)
 
     def test_map_to_data_edge_case_last_year(self):
         # test the edge value when the input could not cover the anchor date
-        cal = AdventCalendar(anchor_date=(10, 15), freq='180d')
+        cal = AdventCalendar(anchor=(10, 15), freq="180d")
         # single year covered
-        time_index = pd.date_range('20191020', '20211001', freq='60d')
+        time_index = pd.date_range("20191020", "20211001", freq="60d")
         test_data = np.random.random(len(time_index))
         timeseries = pd.Series(test_data, index=time_index)
         cal.map_to_data(timeseries)
-        expected = np.array([
+        expected = np.array(
             [
-                interval("2020-04-18", "2020-10-15"),
-                interval("2019-10-21", "2020-04-18"),
+                [
+                    interval("2020-04-18", "2020-10-15"),
+                    interval("2019-10-21", "2020-04-18"),
+                ]
             ]
-        ])
-        assert np.array_equal(cal._intervals, expected)
+        )
+        assert np.array_equal(cal.get_intervals(), expected)
 
     def test_map_to_data_single_year_coverage(self):
         # test the single year coverage
-        cal = AdventCalendar(anchor_date=(12, 31), freq='180d')
+        cal = AdventCalendar(anchor=(12, 31), freq="180d")
         # multiple years covered
-        time_index = pd.date_range('20210101', '20211231', freq='7d')
+        time_index = pd.date_range("20210101", "20211231", freq="7d")
         test_data = np.random.random(len(time_index))
         timeseries = pd.Series(test_data, index=time_index)
         cal.map_to_data(timeseries)
 
-        expected = np.array([
+        expected = np.array(
             [
-                interval("2021-07-04", "2021-12-31"),
-                interval("2021-01-05", "2021-07-04"),
+                [
+                    interval("2021-07-04", "2021-12-31"),
+                    interval("2021-01-05", "2021-07-04"),
+                ]
             ]
-        ])
+        )
 
-        assert np.array_equal(cal._intervals, expected)
+        assert np.array_equal(cal.get_intervals(), expected)
 
     def test_map_to_data_edge_case_first_year(self):
         # test the edge value when the input covers the anchor date
-        cal = AdventCalendar(anchor_date=(10, 15), freq='180d')
+        cal = AdventCalendar(anchor=(10, 15), freq="180d")
         # multiple years covered
-        time_index = pd.date_range('20191010', '20211225', freq='60d')
+        time_index = pd.date_range("20191010", "20211225", freq="60d")
         test_data = np.random.random(len(time_index))
         timeseries = pd.Series(test_data, index=time_index)
         cal.map_to_data(timeseries)
@@ -151,42 +227,42 @@ class TestMap:
             ]
         )
 
-        assert np.array_equal(cal._intervals, expected)
+        assert np.array_equal(cal.get_intervals(), expected)
 
-    def test_map_to_data_value_error(self):
-        # test when the input data is not sufficient to cover one year
-        cal = AdventCalendar(anchor_date=(10, 15), freq='180d')
-        with pytest.raises(ValueError):
-            time_index = pd.date_range('20201020', '20211001', freq='60d')
-            test_data = np.random.random(len(time_index))
-            timeseries = pd.Series(test_data, index=time_index)
-            cal.map_to_data(timeseries)
+    # def test_map_to_data_value_error(self):
+    #     # test when the input data is not sufficient to cover one year
+    #     cal = AdventCalendar(anchor=(10, 15), freq='180d')
+    #     with pytest.raises(ValueError):
+    #         time_index = pd.date_range('20201020', '20211001', freq='60d')
+    #         test_data = np.random.random(len(time_index))
+    #         timeseries = pd.Series(test_data, index=time_index)
+    #         cal.map_to_data(timeseries)
 
     def test_map_to_data_input_time_backward(self):
         # test when the input data has reverse order time index
-        cal = AdventCalendar(anchor_date=(10, 15), freq='180d')
-        time_index = pd.date_range('20201010', '20211225', freq='60d')
+        cal = AdventCalendar(anchor=(10, 15), freq="180d")
+        time_index = pd.date_range("20201010", "20211225", freq="60d")
         test_data = np.random.random(len(time_index))
         timeseries = pd.Series(test_data, index=time_index[::-1])
         cal.map_to_data(timeseries)
 
-        expected = np.array([
+        expected = np.array(
             [
-                interval("2021-04-18", "2021-10-15"),
-                interval("2020-10-20", "2021-04-18"),
+                [
+                    interval("2021-04-18", "2021-10-15"),
+                    interval("2020-10-20", "2021-04-18"),
+                ]
             ]
-        ])
+        )
 
-        assert np.array_equal(cal._intervals, expected)
+        assert np.array_equal(cal.get_intervals(), expected)
 
     def test_map_to_data_xarray_input(self):
         # test when the input data has reverse order time index
-        cal = AdventCalendar(anchor_date=(10, 15), freq='180d')
-        time_index = pd.date_range('20201010', '20211225', freq='60d')
+        cal = AdventCalendar(anchor=(10, 15), freq="180d")
+        time_index = pd.date_range("20201010", "20211225", freq="60d")
         test_data = np.random.random(len(time_index))
-        dataarray = xr.DataArray(
-            data=test_data,
-            coords={'time': time_index})
+        dataarray = xr.DataArray(data=test_data, coords={"time": time_index})
         cal.map_to_data(dataarray)
 
         expected = np.array(
@@ -196,39 +272,60 @@ class TestMap:
             ]
         )
 
-        assert np.all(cal._intervals == expected)
+        assert np.all(cal.get_intervals() == expected)
+
+    def test_missing_time_dim(self):
+        cal = AdventCalendar(anchor=(10, 15), freq="180d")
+        time_index = pd.date_range("20191020", "20211001", freq="60d")
+        test_data = np.random.random(len(time_index))
+        dataframe = pd.DataFrame(test_data, index=time_index)
+        dataset = dataframe.to_xarray()
+        with pytest.raises(ValueError):
+            cal.map_to_data(dataset)
+
+    def test_non_time_dim(self):
+        cal = AdventCalendar(anchor=(10, 15), freq="180d")
+        time_index = pd.date_range("20191020", "20211001", freq="60d")
+        test_data = np.random.random(len(time_index))
+        dataframe = pd.DataFrame(test_data, index=time_index)
+        dataset = dataframe.to_xarray().rename({"index": "time"})
+        dataset["time"] = np.arange(dataset["time"].size)
+        with pytest.raises(ValueError):
+            cal.map_to_data(dataset)
 
     # Note: add more test cases for different number of target periods!
-    max_lag_edge_cases = [(73, ['2019'], 74),
-                          (72, ['2019', '2018'], 73)]
+    max_lag_edge_cases = [(73, ["2019"], 74), (72, ["2019", "2018"], 73)]
     # Test the edge cases of max_lag; where the max_lag just fits in exactly 365 days,
     # and where the max_lag just causes the calendar to skip a year
     @pytest.mark.parametrize("max_lag,expected_index,expected_size", max_lag_edge_cases)
     def test_max_lag_skip_years(self, max_lag, expected_index, expected_size):
-        calendar = AdventCalendar(anchor_date=(12, 31), freq="5d", max_lag=max_lag)
+        calendar = AdventCalendar(anchor=(12, 31), freq="5d", max_lag=max_lag)
         calendar = calendar.map_years(2018, 2019)
 
-        np.testing.assert_array_equal(calendar._intervals.index.values, expected_index)
-        assert calendar._intervals.iloc[0].size == expected_size
+        np.testing.assert_array_equal(
+            calendar.get_intervals().index.values, expected_index
+        )
+        assert calendar.get_intervals().iloc[0].size == expected_size
 
 
 class TestResample:
     """Test resample methods."""
+
     # Define all required inputs as fixtures:
     @pytest.fixture(autouse=True)
     def dummy_calendar(self):
-        return AdventCalendar(anchor_date=(10, 15), freq="180d")
+        return AdventCalendar(anchor=(10, 15), freq="180d")
 
-    @pytest.fixture(autouse=True, params=[1,2,3])
+    @pytest.fixture(autouse=True, params=[1, 2, 3])
     def dummy_calendar_targets(self, request):
-        return AdventCalendar(anchor_date=(5, 10), freq="100D", n_targets=request.param)
+        return AdventCalendar(anchor=(5, 10), freq="100d", n_targets=request.param)
 
     @pytest.fixture(params=["20151020", "20191020"])
     def dummy_series(self, request):
         time_index = pd.date_range(request.param, "20211001", freq="60d")
         test_data = np.random.random(len(time_index))
         expected = np.array([test_data[4:7].mean(), test_data[1:4].mean()])
-        series = pd.Series(test_data, index=time_index, name='data1')
+        series = pd.Series(test_data, index=time_index, name="data1")
         return series, expected
 
     @pytest.fixture
@@ -240,76 +337,73 @@ class TestResample:
     def dummy_dataarray(self, dummy_series):
         series, expected = dummy_series
         dataarray = series.to_xarray()
-        dataarray = dataarray.rename({'index': 'time'})
+        dataarray = dataarray.rename({"index": "time"})
         return dataarray, expected
 
     @pytest.fixture
     def dummy_dataset(self, dummy_dataframe):
         dataframe, expected = dummy_dataframe
-        dataset = dataframe.to_xarray().rename({'index': 'time'})
+        dataset = dataframe.to_xarray().rename({"index": "time"})
         return dataset, expected
 
     # Tests start here:
+    def test_non_mapped_calendar(self, dummy_calendar):
+        with pytest.raises(ValueError):
+            resample(dummy_calendar, None)
+
     def test_nontime_index(self, dummy_calendar, dummy_series):
         series, _ = dummy_series
+        cal = dummy_calendar.map_to_data(series)
         series = series.reset_index()
         with pytest.raises(ValueError):
-            dummy_calendar.resample(series)
+            resample(cal, series)
 
     def test_series(self, dummy_calendar, dummy_series):
         series, expected = dummy_series
-        resampled_data = dummy_calendar.resample(series)
-        np.testing.assert_allclose(
-            resampled_data['data1'].iloc[:2], expected)
+        cal = dummy_calendar.map_to_data(series)
+        resampled_data = resample(cal, series)
+        np.testing.assert_allclose(resampled_data["data1"].iloc[:2], expected)
 
     def test_unnamed_series(self, dummy_calendar, dummy_series):
         series, expected = dummy_series
         series.name = None
-        resampled_data = dummy_calendar.resample(series)
-        np.testing.assert_allclose(
-            resampled_data["mean_data"].iloc[:2], expected)
+        cal = dummy_calendar.map_to_data(series)
+        resampled_data = resample(cal, series)
+        np.testing.assert_allclose(resampled_data["mean_data"].iloc[:2], expected)
 
     def test_dataframe(self, dummy_calendar, dummy_dataframe):
         dataframe, expected = dummy_dataframe
-        resampled_data = dummy_calendar.resample(dataframe)
+        cal = dummy_calendar.map_to_data(dataframe)
+        resampled_data = resample(cal, dataframe)
         np.testing.assert_allclose(resampled_data["data1"].iloc[:2], expected)
 
     def test_dataarray(self, dummy_calendar, dummy_dataarray):
         dataarray, expected = dummy_dataarray
-        resampled_data = dummy_calendar.resample(dataarray)
+        cal = dummy_calendar.map_to_data(dataarray)
+        resampled_data = resample(cal, dataarray)
         testing_vals = resampled_data["data1"].isel(anchor_year=0)
         np.testing.assert_allclose(testing_vals, expected)
 
     def test_dataset(self, dummy_calendar, dummy_dataset):
         dataset, expected = dummy_dataset
-        resampled_data = dummy_calendar.resample(dataset)
+        cal = dummy_calendar.map_to_data(dataset)
+        resampled_data = resample(cal, dataset)
         testing_vals = resampled_data["data1"].isel(anchor_year=0)
         np.testing.assert_allclose(testing_vals, expected)
 
-    def test_missing_time_dim(self, dummy_calendar, dummy_dataset):
-        dataset, _ = dummy_dataset
-        with pytest.raises(ValueError):
-            dummy_calendar.resample(dataset.rename({'time': 'index'}))
-
-    def test_non_time_dim(self, dummy_calendar, dummy_dataset):
-        dataset, _ = dummy_dataset
-        dataset['time'] = np.arange(dataset['time'].size)
-        with pytest.raises(ValueError):
-            dummy_calendar.resample(dataset)
-
     def test_target_period_dataframe(self, dummy_calendar_targets, dummy_dataframe):
         df, _ = dummy_dataframe
-        resampled_data = dummy_calendar_targets.resample(df)
+        calendar = dummy_calendar_targets.map_to_data(df)
+        resampled_data = resample(calendar, df)
         expected = np.zeros(resampled_data.index.size, dtype=bool)
-        # pylint: disable=protected-access
-        for i in range(dummy_calendar_targets._n_targets):
+        for i in range(calendar.n_targets):
             expected[i::3] = True
-        np.testing.assert_array_equal(resampled_data['target'].values, expected)
+        np.testing.assert_array_equal(resampled_data["target"].values, expected)
 
     def test_target_period_dataset(self, dummy_calendar_targets, dummy_dataset):
         ds, _ = dummy_dataset
-        resampled_data = dummy_calendar_targets.resample(ds)
+        calendar = dummy_calendar_targets.map_to_data(ds)
+        resampled_data = resample(calendar, ds)
         expected = np.zeros(3, dtype=bool)
-        # pylint: disable=protected-access
-        expected[:dummy_calendar_targets._n_targets] = True
-        np.testing.assert_array_equal(resampled_data['target'].values, expected)
+        expected[: dummy_calendar_targets.n_targets] = True
+        np.testing.assert_array_equal(resampled_data["target"].values, expected)
diff --git a/tests/test_traintest.py b/tests/test_traintest.py
index 7fc2241..4f7e60a 100644
--- a/tests/test_traintest.py
+++ b/tests/test_traintest.py
@@ -6,14 +6,14 @@ import pytest
 from sklearn.model_selection import KFold
 import s2spy.traintest
 from s2spy.time import AdventCalendar
+from s2spy.time import resample
 
 
 class TestTrainTest:
     # Define all required inputs as fixtures:
     @pytest.fixture(autouse=True)
     def dummy_calendar(self):
-        cal = AdventCalendar(anchor_date=(10, 15), freq="180d")
-        return cal.map_years(2019, 2021)
+        return AdventCalendar(anchor=(10, 15), freq="180d")
 
     @pytest.fixture(autouse=True)
     def dummy_dataframe(self):
@@ -36,30 +36,35 @@ class TestTrainTest:
         return dummy_dataframe_short.to_xarray().rename({"index": "time"})
 
     def test_kfold_df(self, dummy_calendar, dummy_dataframe):
-        df = dummy_calendar.resample(dummy_dataframe)
+        mapped_calendar = dummy_calendar.map_to_data(dummy_dataframe)
+        df = resample(mapped_calendar, dummy_dataframe)
         df = s2spy.traintest.split_groups(KFold(n_splits=2), df)
         expected_group = ["test", "test", "train", "train"]
         assert np.array_equal(df["split_0"].values, expected_group)
 
     def test_kfold_ds(self, dummy_calendar, dummy_dataset):
-        ds = dummy_calendar.resample(dummy_dataset)
+        mapped_calendar = dummy_calendar.map_to_data(dummy_dataset)
+        ds = resample(mapped_calendar, dummy_dataset)
         ds = s2spy.traintest.split_groups(KFold(n_splits=2), ds)
         expected_group = ["test", "train"]
         assert np.array_equal(ds.traintest.values[0], expected_group)
 
     def test_kfold_df_short(self, dummy_calendar, dummy_dataframe_short):
         "Should fail as there is only a single anchor year: no splits can be made"
-        df = dummy_calendar.resample(dummy_dataframe_short)
+        mapped_calendar = dummy_calendar.map_to_data(dummy_dataframe_short)
+        df = resample(mapped_calendar, dummy_dataframe_short)
         with pytest.raises(ValueError):
             df = s2spy.traintest.split_groups(KFold(n_splits=2), df)
 
     def test_kfold_ds_short(self, dummy_calendar, dummy_dataset_short):
         "Should fail as there is only a single anchor year: no splits can be made"
-        ds = dummy_calendar.resample(dummy_dataset_short)
+        mapped_calendar = dummy_calendar.map_to_data(dummy_dataset_short)
+        ds = resample(mapped_calendar, dummy_dataset_short)
         with pytest.raises(ValueError):
             ds = s2spy.traintest.split_groups(KFold(n_splits=2), ds)
 
     def test_alternative_key(self, dummy_calendar, dummy_dataset):
-        ds = dummy_calendar.resample(dummy_dataset)
-        ds = s2spy.traintest.split_groups(KFold(n_splits=2), ds, key='i_interval')
-        assert 'i_interval' in ds.traintest.dims
\ No newline at end of file
+        mapped_calendar = dummy_calendar.map_to_data(dummy_dataset)
+        ds = resample(mapped_calendar, dummy_dataset)
+        ds = s2spy.traintest.split_groups(KFold(n_splits=2), ds, key="i_interval")
+        assert "i_interval" in ds.traintest.dims