Repository: ESMValGroup/ESMValCore

Issue:
Dataset problem:  GFDL-CM2p1 sit has invalid time bounds
I have found a sit file for CMIP5 GFDL-CM2p1 that has time bounds of about 1e9  with times of abot 40000. Iris breaks up at the checker, when changing the time units:

```yaml
- {dataset: GFDL-CM2p1, project: CMIP5, exp: historical, ensemble: r1i1p1, start_year: 2001, end_year: 2004}
```

Pull request with the fix is coming soon

Discussion:

Patch:
diff --git a/esmvalcore/cmor/_fixes/cmip5/gfdl_cm2p1.py b/esmvalcore/cmor/_fixes/cmip5/gfdl_cm2p1.py
index ad98dacae..e8c5008a5 100644
--- a/esmvalcore/cmor/_fixes/cmip5/gfdl_cm2p1.py
+++ b/esmvalcore/cmor/_fixes/cmip5/gfdl_cm2p1.py
@@ -1,5 +1,7 @@
 """Fixes for GFDL CM2p1 model."""
 from copy import deepcopy
+import numpy as np
+import cftime
 
 from ..fix import Fix
 from ..cmip5.gfdl_esm2g import AllVars as BaseAllVars
@@ -56,6 +58,48 @@ class Sftof(Fix):
         return cube
 
 
+class Sit(Fix):
+    """Fixes for sit"""
+
+    def fix_metadata(self, cubes):
+        """
+        Fix metadata.
+
+        Fixes bad bounds
+
+        Parameters
+        ----------
+        cube: iris.cube.Cube
+
+        Returns
+        -------
+        iris.cube.Cube
+
+        """
+        cube = self.get_cube_from_list(cubes)
+        time = cube.coord('time')
+        if self._fix_required(time):
+            times = time.units.num2date(time.points)
+            starts = [
+                cftime.DatetimeJulian(c.year, c.month, 1)
+                for c in times
+            ]
+            ends = [
+                cftime.DatetimeJulian(c.year, c.month + 1, 1)
+                if c.month < 12
+                else cftime.DatetimeJulian(c.year + 1, 1, 1)
+                for c in times
+            ]
+            time.bounds = time.units.date2num(np.stack([starts, ends], -1))
+        return cubes
+
+    def _fix_required(self, time):
+        return (
+            self.vardef.frequency == 'mon' and
+            not (time.bounds[-1, 0] < time.points[-1] < time.bounds[-1, 1])
+        )
+
+
 class Tos(Fix):
     """Fixes for tos"""
 


Test patch:
diff --git a/tests/integration/cmor/_fixes/cmip5/test_gfdl_cm2p1.py b/tests/integration/cmor/_fixes/cmip5/test_gfdl_cm2p1.py
index 4540645c9..eabead476 100644
--- a/tests/integration/cmor/_fixes/cmip5/test_gfdl_cm2p1.py
+++ b/tests/integration/cmor/_fixes/cmip5/test_gfdl_cm2p1.py
@@ -1,11 +1,14 @@
 """Test GDL-CM2P1 fixes."""
 import unittest
+from unittest import mock
 
 from cf_units import Unit
+import iris
 from iris.cube import Cube
 
 from esmvalcore.cmor.fix import Fix
-from esmvalcore.cmor._fixes.cmip5.gfdl_cm2p1 import Sftof, AllVars, Areacello
+from esmvalcore.cmor._fixes.cmip5.gfdl_cm2p1 import (Sftof, AllVars,
+                                                     Areacello, Sit)
 
 
 class TestSftof(unittest.TestCase):
@@ -29,7 +32,8 @@ class TestSftof(unittest.TestCase):
 
 
 class TestAreacello(unittest.TestCase):
-    """Test sftof fixes."""
+    """Test areacello fixes."""
+
     def setUp(self):
         """Prepare tests."""
         self.cube = Cube([1.0], var_name='areacello', units='m-2')
@@ -53,3 +57,64 @@ class TestAreacello(unittest.TestCase):
         cube = self.fix.fix_metadata((self.cube, ))[0]
         self.assertEqual(cube.data[0], 1.0)
         self.assertEqual(cube.units, Unit('m2'))
+
+
+class TestSit(unittest.TestCase):
+    """Test sit fixes."""
+
+    def setUp(self):
+        """Prepare tests."""
+        self.cube = Cube([1.0, 2.0], var_name='sit', units='m')
+        self.cube.add_dim_coord(
+            iris.coords.DimCoord(
+                points=[45000.5, 45031.5],
+                var_name='time',
+                standard_name='time',
+                long_name='time',
+                units='days since 1850-01-01',
+                bounds=[[1e8, 1.1e8], [1.1e8, 1.2e8]]
+            ),
+            0
+        )
+        self.var_info_mock = mock.Mock()
+        self.var_info_mock.frequency = 'mon'
+        self.fix = Sit(self.var_info_mock)
+
+    def test_get(self):
+        """Test fix get"""
+        self.assertListEqual(
+            Fix.get_fixes('CMIP5', 'GFDL-CM2P1', 'OImon', 'sit'),
+            [Sit(self.var_info_mock), AllVars(None)])
+
+    def test_fix_metadata_day_do_nothing(self):
+        """Test data fix."""
+        self.var_info_mock.frequency = 'day'
+        fix = Sit(self.var_info_mock)
+        cube = fix.fix_metadata((self.cube,))[0]
+        time = cube.coord('time')
+        self.assertEqual(time.bounds[0, 0], 1e8)
+        self.assertEqual(time.bounds[0, 1], 1.1e8)
+        self.assertEqual(time.bounds[1, 0], 1.1e8)
+        self.assertEqual(time.bounds[1, 1], 1.2e8)
+
+    def test_fix_metadata(self):
+        """Test data fix."""
+        fix = Sit(self.var_info_mock)
+        cube = fix.fix_metadata((self.cube,))[0]
+        time = cube.coord('time')
+        self.assertEqual(time.bounds[0, 0], 44984)
+        self.assertEqual(time.bounds[0, 1], 45015)
+        self.assertEqual(time.bounds[1, 0], 45015)
+        self.assertEqual(time.bounds[1, 1], 45045)
+
+    def test_fix_metadata_not_needed(self):
+        """Test data fix."""
+        fix = Sit(self.var_info_mock)
+        cube = fix.fix_metadata((self.cube,))[0]
+        time = cube.coord('time')
+        new_bounds = [[44985., 45014.], [45016., 45044.]]
+        time.bounds = new_bounds
+        self.assertEqual(time.bounds[0, 0], 44985)
+        self.assertEqual(time.bounds[0, 1], 45014)
+        self.assertEqual(time.bounds[1, 0], 45016)
+        self.assertEqual(time.bounds[1, 1], 45044)