SDAP-351: Fix reading of data with cftime.datetime.Gregorian type (#46)

* Generalized datetime fix so all calendars are supported

* Added unit tests for 4 calendar types (standard, missing attr, julian, gregorian
diff --git a/granule_ingester/granule_ingester/processors/reading_processors/GridMultiVariableReadingProcessor.py b/granule_ingester/granule_ingester/processors/reading_processors/GridMultiVariableReadingProcessor.py
index dc28c19..c36b8d2 100644
--- a/granule_ingester/granule_ingester/processors/reading_processors/GridMultiVariableReadingProcessor.py
+++ b/granule_ingester/granule_ingester/processors/reading_processors/GridMultiVariableReadingProcessor.py
@@ -74,7 +74,7 @@
                     "Time slices must have length 1, but '{dim}' has length {dim_len}.".format(dim=self.time,
                                                                                                dim_len=time_slice_len))
 
-            if isinstance(ds[self.time][time_slice.start].item(), cftime.DatetimeJulian):
+            if isinstance(ds[self.time][time_slice.start].item(), cftime.datetime):
                 ds[self.time] = ds.indexes[self.time].to_datetimeindex()
             new_tile.time = int(ds[self.time][time_slice.start].item() / 1e9)
 
diff --git a/granule_ingester/granule_ingester/processors/reading_processors/GridReadingProcessor.py b/granule_ingester/granule_ingester/processors/reading_processors/GridReadingProcessor.py
index 725ba35..97f282c 100644
--- a/granule_ingester/granule_ingester/processors/reading_processors/GridReadingProcessor.py
+++ b/granule_ingester/granule_ingester/processors/reading_processors/GridReadingProcessor.py
@@ -47,7 +47,7 @@
                 raise RuntimeError(
                     "Time slices must have length 1, but '{dim}' has length {dim_len}.".format(dim=self.time,
                                                                                                dim_len=time_slice_len))
-            if isinstance(ds[self.time][time_slice.start].item(), cftime.DatetimeJulian):
+            if isinstance(ds[self.time][time_slice.start].item(), cftime.datetime):
                 ds[self.time] = ds.indexes[self.time].to_datetimeindex()
             new_tile.time = int(ds[self.time][time_slice.start].item() / 1e9)
 
diff --git a/granule_ingester/tests/granules/20181231090000-JPL-L4_GHRSST-SSTfnd-MUR25-GLOB-v02.0-fv04.2.nc b/granule_ingester/tests/granules/20181231090000-JPL-L4_GHRSST-SSTfnd-MUR25-GLOB-v02.0-fv04.2.nc
new file mode 100644
index 0000000..31ca293
--- /dev/null
+++ b/granule_ingester/tests/granules/20181231090000-JPL-L4_GHRSST-SSTfnd-MUR25-GLOB-v02.0-fv04.2.nc
Binary files differ
diff --git a/granule_ingester/tests/granules/20190630_d-ACRI-L4-CHL-MULTI_4KM-GLO-REP.nc b/granule_ingester/tests/granules/20190630_d-ACRI-L4-CHL-MULTI_4KM-GLO-REP.nc
new file mode 100644
index 0000000..6fd6cd3
--- /dev/null
+++ b/granule_ingester/tests/granules/20190630_d-ACRI-L4-CHL-MULTI_4KM-GLO-REP.nc
Binary files differ
diff --git a/granule_ingester/tests/granules/OISSS_L4_multimission_global_7d_v1.0_2021-03-12.nc b/granule_ingester/tests/granules/OISSS_L4_multimission_global_7d_v1.0_2021-03-12.nc
new file mode 100644
index 0000000..283bfe4
--- /dev/null
+++ b/granule_ingester/tests/granules/OISSS_L4_multimission_global_7d_v1.0_2021-03-12.nc
Binary files differ
diff --git a/granule_ingester/tests/reading_processors/test_GridMultiBandReadingProcessor.py b/granule_ingester/tests/reading_processors/test_GridMultiBandReadingProcessor.py
index 607dd8b..3c47718 100644
--- a/granule_ingester/tests/reading_processors/test_GridMultiBandReadingProcessor.py
+++ b/granule_ingester/tests/reading_processors/test_GridMultiBandReadingProcessor.py
@@ -165,27 +165,71 @@
         self.assertRaises(RuntimeError, GridMultiVariableReadingProcessor, [], 'lat', 'lon', time='time')
         return
 
-class TestReadGPMData(unittest.TestCase):
-    def test_generate_tile(self):
-        granule_path = path.join(path.dirname(__file__), '../granules/3B-DAY-E.MS.MRG.3IMERG.20070101-S000000-E235959.V06.nc4')
+
+class TestCalendars(unittest.TestCase):
+    """
+    Test that various calendars can be ingested into SDAP without error.
+    """
+    def assert_time_data(self, granule_name, time_var, lat_var, lon_var, data_vars):
+        granule_path = path.join(
+            path.dirname(__file__),
+            '../granules/',
+            granule_name
+        )
         dimensions_to_slices = {
-            'time': slice(0, 1),
-            'lat': slice(0, 30),
-            'lon': slice(0, 30)
+            time_var: slice(0, 1),
+            lat_var: slice(0, 30),
+            lon_var: slice(0, 30)
         }
 
-        tile  = nexusproto.NexusTile()
+        tile = nexusproto.NexusTile()
 
         with xr.open_dataset(granule_path, decode_cf=True) as ds:
             reading_processor = GridMultiVariableReadingProcessor(
-                ['HQprecipitation', 'HQprecipitation_cnt'],
-                'lat',
-                'lon',
-                time='time'
+                data_vars,
+                lat_var,
+                lon_var,
+                time=time_var
             )
             tile = reading_processor._generate_tile(ds, dimensions_to_slices, tile)
             assert tile.tile.grid_multi_variable_tile.time
 
+    def test_julian_calendar_tile(self):
+        self.assert_time_data(
+            granule_name='3B-DAY-E.MS.MRG.3IMERG.20070101-S000000-E235959.V06.nc4',
+            time_var='time',
+            lat_var='lat',
+            lon_var='lon',
+            data_vars=['HQprecipitation', 'HQprecipitation_cnt']
+        )
+
+    def test_gregorian_calendar_tile(self):
+        self.assert_time_data(
+            granule_name='20190630_d-ACRI-L4-CHL-MULTI_4KM-GLO-REP.nc',
+            time_var='time',
+            lat_var='lat',
+            lon_var='lon',
+            data_vars=['CHL', 'CHL_error']
+        )
+
+    def test_standard_calendar_tile(self):
+        self.assert_time_data(
+            granule_name='OISSS_L4_multimission_global_7d_v1.0_2021-03-12.nc',
+            time_var='time',
+            lat_var='latitude',
+            lon_var='longitude',
+            data_vars=['sss', 'sss_uncertainty']
+        )
+
+    def test_missing_calendar_tile(self):
+        self.assert_time_data(
+            granule_name='20181231090000-JPL-L4_GHRSST-SSTfnd-MUR25-GLOB-v02.0-fv04.2.nc',
+            time_var='time',
+            lat_var='lat',
+            lon_var='lon',
+            data_vars=['analysed_sst', 'analysis_error']
+        )
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/granule_ingester/tests/reading_processors/test_GridReadingProcessor.py b/granule_ingester/tests/reading_processors/test_GridReadingProcessor.py
index 6cfa835..ef78394 100644
--- a/granule_ingester/tests/reading_processors/test_GridReadingProcessor.py
+++ b/granule_ingester/tests/reading_processors/test_GridReadingProcessor.py
@@ -347,22 +347,71 @@
         self.assertRaises(RuntimeError, GridReadingProcessor, [], 'lat', 'lon', time='time')
         return
 
-class TestReadGPMData(unittest.TestCase):
-    def test_generate_tile(self):
-        granule_path = path.join(path.dirname(__file__), '../granules/3B-DAY-E.MS.MRG.3IMERG.20070101-S000000-E235959.V06.nc4')
+class TestCalendars(unittest.TestCase):
+    """
+    Test that various calendars can be ingested into SDAP without error.
+    """
+
+    def assert_time_data(self, granule_name, time_var, lat_var, lon_var, data_var):
+        granule_path = path.join(
+            path.dirname(__file__),
+            '../granules/',
+            granule_name
+        )
         dimensions_to_slices = {
-            'time': slice(0, 1),
-            'lat': slice(0, 30),
-            'lon': slice(0, 30)
+            time_var: slice(0, 1),
+            lat_var: slice(0, 30),
+            lon_var: slice(0, 30)
         }
 
-        tile  = nexusproto.NexusTile()
+        tile = nexusproto.NexusTile()
 
         with xr.open_dataset(granule_path, decode_cf=True) as ds:
-            reading_processor = GridReadingProcessor(['HQprecipitation'], 'lat', 'lon', time='time')
+            reading_processor = GridReadingProcessor(
+                [data_var],
+                lat_var,
+                lon_var,
+                time=time_var
+            )
             tile = reading_processor._generate_tile(ds, dimensions_to_slices, tile)
             assert tile.tile.grid_tile.time
 
+    def test_julian_calendar_tile(self):
+        self.assert_time_data(
+            granule_name='3B-DAY-E.MS.MRG.3IMERG.20070101-S000000-E235959.V06.nc4',
+            time_var='time',
+            lat_var='lat',
+            lon_var='lon',
+            data_var='HQprecipitation'
+        )
+
+    def test_gregorian_calendar_tile(self):
+        self.assert_time_data(
+            granule_name='20190630_d-ACRI-L4-CHL-MULTI_4KM-GLO-REP.nc',
+            time_var='time',
+            lat_var='lat',
+            lon_var='lon',
+            data_var='CHL'
+        )
+
+    def test_standard_calendar_tile(self):
+        self.assert_time_data(
+            granule_name='OISSS_L4_multimission_global_7d_v1.0_2021-03-12.nc',
+            time_var='time',
+            lat_var='latitude',
+            lon_var='longitude',
+            data_var='sss'
+        )
+
+    def test_missing_calendar_tile(self):
+        self.assert_time_data(
+            granule_name='20181231090000-JPL-L4_GHRSST-SSTfnd-MUR25-GLOB-v02.0-fv04.2.nc',
+            time_var='time',
+            lat_var='lat',
+            lon_var='lon',
+            data_var='analysed_sst'
+        )
+
 
 if __name__ == '__main__':
     unittest.main()