diff --git a/cdds/cdds/qc/plugins/base/checks.py b/cdds/cdds/qc/plugins/base/checks.py index 535a295d7..62927a579 100644 --- a/cdds/cdds/qc/plugins/base/checks.py +++ b/cdds/cdds/qc/plugins/base/checks.py @@ -95,7 +95,7 @@ def execute(self, netcdf_file: Dataset, attr_dict: Dict[str, Any]) -> None: "experiment": validator.experiment_validator(getattr(netcdf_file, "experiment_id")), "institution": validator.institution_validator(getattr(netcdf_file, "institution_id")), "Conventions": ValidatorFactory.value_in_validator(self.CF_CONVENTIONS), - "creation_date": ValidatorFactory.date_validator("%Y-%m-%dT%H:%M:%SZ"), + "creation_date": ValidatorFactory.date_validator("%Y-%m-%dT%H:%M:%SZ", "gregorian"), "data_specs_version": ValidatorFactory.value_in_validator([self._cache.mip_tables.version]), "license": ValidatorFactory.value_in_validator([self._cache.request.license.strip()]), "mip_era": ValidatorFactory.value_in_validator([self._cache.request.mip_era]), diff --git a/cdds/cdds/tests/test_qc/plugins/base/test_checks.py b/cdds/cdds/tests/test_qc/plugins/base/test_checks.py index e637a522b..570387ffa 100644 --- a/cdds/cdds/tests/test_qc/plugins/base/test_checks.py +++ b/cdds/cdds/tests/test_qc/plugins/base/test_checks.py @@ -6,13 +6,16 @@ from unittest import TestCase from unittest.mock import patch, MagicMock from cdds.common.mip_tables import MipTables +from cdds.common.request import Request -from cdds.qc.plugins.base.checks import VariableAttributesCheckTask +from cdds.qc.plugins.base.checks import VariableAttributesCheckTask, StringAttributesCheckTask from cdds.qc.plugins.base.validators import ControlledVocabularyValidator +from cdds.qc.plugins.cmip6.validators import Cmip6CVValidator from cdds.qc.plugins.base.common import CheckCache from cdds.tests.test_common.common import create_simple_netcdf_file from cdds.tests.test_qc.plugins.constants import (MIP_TABLES_DIR, CV_REPO, TMP_DIR_FOR_NETCDF_TESTS, MINIMAL_CDL, - CORRECT_VARIABLE_METADATA_CDL, INCONSISTENT_VARIABLE_METADATA_CDL) + CORRECT_VARIABLE_METADATA_CDL, INCONSISTENT_VARIABLE_METADATA_CDL, + GLOBAL_ATTRIBUTES_CDL) class TestVariableAttributesCheckTask(TestCase): @@ -43,3 +46,30 @@ def test_variable_inconsistent_metadata(self): self.assertListEqual( self.class_under_test._messages, ["Variable attribute units has value of K instead of W m-2"]) + + +class TestGlobalAttributesCheckTask(TestCase): + + def setUp(self): + self.nc_path = os.path.join(TMP_DIR_FOR_NETCDF_TESTS, "test_file.nc") + mip_tables = MipTables(os.path.join(MIP_TABLES_DIR, "for_functional_tests")) + request = Request({ + "mip_era": "CMIP6", + "license": ("CMIP6 model data produced by the Met Office Hadley Centre.") + }, []) + cache = CheckCache(request, mip_tables, Cmip6CVValidator(CV_REPO)) + self.class_under_test = StringAttributesCheckTask(cache) + + def tearDown(self): + os.remove(self.nc_path) + + def test_creation_date(self): + create_simple_netcdf_file(GLOBAL_ATTRIBUTES_CDL, self.nc_path) + netcdf_file = Dataset(self.nc_path, 'a') + attr_dict = {"table_id": "Amon", "variable_id": "rsut"} + self.class_under_test.execute(netcdf_file, attr_dict) + self.maxDiff = None + self.assertListEqual( + self.class_under_test._messages, + ["Mandatory attribute creation_date: " + "'2022-02-31T21:16:47Z' is not a valid date in a form of %Y-%m-%dT%H:%M:%SZ"]) diff --git a/cdds/cdds/tests/test_qc/plugins/base/test_validators.py b/cdds/cdds/tests/test_qc/plugins/base/test_validators.py index 7f939d1f8..336c4c958 100644 --- a/cdds/cdds/tests/test_qc/plugins/base/test_validators.py +++ b/cdds/cdds/tests/test_qc/plugins/base/test_validators.py @@ -1,4 +1,4 @@ -# (C) British Crown Copyright 2017-2021, Met Office. +# (C) British Crown Copyright 2017-2023, Met Office. # Please see LICENSE.rst for license details. import unittest @@ -66,3 +66,11 @@ def test_int_validator(self): self.assertRaises(ValidationError, validator, -4) self.assertIsNone(zero_validator(0)) self.assertIsNone(negative_validator(-1)) + + def test_date_validator(self): + validator_360day = ValidatorFactory.date_validator("%Y-%m-%dT%H:%M:%SZ") + validator_gregorian = ValidatorFactory.date_validator("%Y-%m-%dT%H:%M:%SZ", "gregorian") + self.assertIsNone(validator_360day("2023-02-30T01:20:05Z")) + self.assertRaises(ValidationError, validator_gregorian, "2023-02-30T01:20:05Z") + self.assertRaises(ValidationError, validator_360day, "2023-07-31T01:20:05Z") + self.assertIsNone(validator_gregorian("2023-07-31T01:20:05Z")) diff --git a/cdds/cdds/tests/test_qc/plugins/constants.py b/cdds/cdds/tests/test_qc/plugins/constants.py index 88077f832..35b580bb1 100644 --- a/cdds/cdds/tests/test_qc/plugins/constants.py +++ b/cdds/cdds/tests/test_qc/plugins/constants.py @@ -1,4 +1,4 @@ -# (C) British Crown Copyright 2017-2021, Met Office. +# (C) British Crown Copyright 2017-2023, Met Office. # Please see LICENSE.rst for license details. import os @@ -107,3 +107,87 @@ time = 45 ; } """ + +GLOBAL_ATTRIBUTES_CDL = """ +netcdf filename { +dimensions: + lat = 1 ; + lon = 1 ; + time = UNLIMITED ; // (1 currently) +variables: + double lat(lat) ; + double lon(lon) ; + float rsut(time, lat, lon) ; + rsut:frequency = "mon" ; + rsut:modeling_realm = "atmos" ; + rsut:standard_name = "toa_outgoing_shortwave_flux" ; + rsut:units = "W m-2" ; + rsut:cell_methods = "area: time: mean" ; + rsut:cell_measures = "area: areacella" ; + rsut:long_name = "TOA Outgoing Shortwave Radiation" ; + rsut:comment = "at the top of the atmosphere" ; + rsut:dimensions = "longitude latitude time" ; + rsut:out_name = "rsut" ; + rsut:type = "real" ; + rsut:positive = "up" ; + rsut:missing_value = 1.e+20 ; + rsut:_FillValue = 1.e+20 ; + rsut:original_name = "foo" ; + double time(time) ; + +// global attributes: + :Conventions = "CF-1.7 CMIP-6.2" ; + :activity_id = "CMIP" ; + :branch_method = "standard" ; + :branch_time_in_child = 0. ; + :branch_time_in_parent = 72000. ; + :creation_date = "2022-02-31T21:16:47Z" ; + :cv_version = "6.2.37.5" ; + :data_specs_version = "01.00.29" ; + :experiment = "all-forcing simulation of the recent past" ; + :experiment_id = "historical" ; + :external_variables = "areacella" ; + :forcing_index = 3 ; + :frequency = "day" ; + :further_info_url = "https://furtherinfo.es-doc.org/CMIP6.MOHC.UKESM1-0-LL.historical.none.r6i1p1f3" ; + :grid = "Native N96 grid; 192 x 144 longitude/latitude" ; + :grid_label = "gn" ; + :history = "" ; + :initialization_index = 1 ; + :institution = "Met Office Hadley Centre, Fitzroy Road, Exeter, Devon, EX1 3PB, UK" ; + :institution_id = "MOHC" ; + :mip_era = "CMIP6" ; + :mo_runid = "u-az515" ; + :nominal_resolution = "250 km" ; + :parent_activity_id = "CMIP" ; + :parent_experiment_id = "piControl" ; + :parent_mip_era = "CMIP6" ; + :parent_source_id = "UKESM1-0-LL" ; + :parent_time_units = "days since 1850-01-01" ; + :parent_variant_label = "r1i1p1f2" ; + :physics_index = 1 ; + :product = "model-output" ; + :realization_index = 6 ; + :realm = "atmos" ; + :source = "UKESM1.0-LL (2018)" ; + :source_id = "UKESM1-0-LL" ; + :source_type = "AOGCM AER BGC CHEM" ; + :sub_experiment = "none" ; + :sub_experiment_id = "none" ; + :table_id = "day" ; + :table_info = "Creation Date:(13 December 2018) MD5:f0588f7f55b5732b17302f8d9d0d7b8c" ; + :title = "UKESM1-0-LL output prepared for CMIP6" ; + :variable_id = "rsut" ; + :variable_name = "rsut" ; + :variant_label = "r6i1p1f3" ; + :license = "CMIP6 model data produced by the Met Office Hadley Centre." ; + :cmor_version = "3.4.0" ; + :tracking_id = "hdl:21.14100/60dc0e7f-c2d2-4d50-b578-5c93bca6ff51" ; + +data: + lat = -89.375 ; + lon = 0.9375 ; + rsut = 213.0 ; + time = 45 ; +} +"""