diff --git a/data/config/UGridConfig_mpas b/data/config/UGridConfig_mpas index 189eb3a959..73d8046c1b 100644 --- a/data/config/UGridConfig_mpas +++ b/data/config/UGridConfig_mpas @@ -18,7 +18,8 @@ ugrid_metadata_map = [ { key = "lat_face"; val = "latCell"; }, { key = "lon_face"; val = "lonCell"; }, { key = "vert_face"; val = "zCell"; }, // optional - { key = "time"; val = "xtime"; } + { key = "time"; val = "xtime"; }, + { key = "init_time"; val = "initial_time"; } // optional ]; // diff --git a/docs/Users_Guide/appendixH.rst b/docs/Users_Guide/appendixH.rst index 49669c5bad..52a33ea903 100644 --- a/docs/Users_Guide/appendixH.rst +++ b/docs/Users_Guide/appendixH.rst @@ -41,8 +41,11 @@ To correctly parse the UGRID topology, MET needs the following information that - Dimension name for the time coordinate - Required * - time - - Coordinate variable for time + - Coordinate variable for valid time - Required + * - init_time + - Coordinate variable for initialization time (used to compute the lead time) + - Optional * - dim_vert - Dimension name for the vertical coordinate, if present - Optional diff --git a/internal/test_unit/python/unit.py b/internal/test_unit/python/unit.py index 198d0d052d..54cabf3f2d 100755 --- a/internal/test_unit/python/unit.py +++ b/internal/test_unit/python/unit.py @@ -146,6 +146,7 @@ def unit(test_xml, file_log=None, cmd_only=False, noexit=False, memchk=False, ca ret_ok = (cmd_return.returncode == test['retval']) if ret_ok: out_ok = True + result = None for filepath in test['out_pnc']: result = subprocess.run([mpnc, '-v', filepath], @@ -173,7 +174,10 @@ def unit(test_xml, file_log=None, cmd_only=False, noexit=False, memchk=False, ca break except FileNotFoundError: cmd_outs += (f"\nERROR: stat file missing {filepath}\n") - logger.debug(result.stdout) + if result: + logger.debug(result.stdout) + else: + logger.debug(f"No result object, stat file missing {filepath}") out_ok = False break # check stat file has non-header lines diff --git a/internal/test_unit/xml/unit_ugrid.xml b/internal/test_unit/xml/unit_ugrid.xml index 52c3d4b619..255ff94760 100644 --- a/internal/test_unit/xml/unit_ugrid.xml +++ b/internal/test_unit/xml/unit_ugrid.xml @@ -35,8 +35,8 @@ -outdir &OUTPUT_DIR;/grid_stat_ugrid -v 1 - &OUTPUT_DIR;/grid_stat_ugrid/grid_stat_UGRID_MPAS_OUT_TO_GRID_000000L_20120409_120000V.stat - &OUTPUT_DIR;/grid_stat_ugrid/grid_stat_UGRID_MPAS_OUT_TO_GRID_000000L_20120409_120000V_pairs.nc + &OUTPUT_DIR;/grid_stat_ugrid/grid_stat_UGRID_MPAS_OUT_TO_GRID_120000L_20120409_120000V.stat + &OUTPUT_DIR;/grid_stat_ugrid/grid_stat_UGRID_MPAS_OUT_TO_GRID_120000L_20120409_120000V_pairs.nc @@ -53,8 +53,8 @@ -outdir &OUTPUT_DIR;/grid_stat_ugrid -v 1 - &OUTPUT_DIR;/grid_stat_ugrid/grid_stat_UGRID_MPAS_DIAG_000000L_20120409_120000V.stat - &OUTPUT_DIR;/grid_stat_ugrid/grid_stat_UGRID_MPAS_DIAG_000000L_20120409_120000V_pairs.nc + &OUTPUT_DIR;/grid_stat_ugrid/grid_stat_UGRID_MPAS_DIAG_120000L_20120409_120000V.stat + &OUTPUT_DIR;/grid_stat_ugrid/grid_stat_UGRID_MPAS_DIAG_120000L_20120409_120000V_pairs.nc @@ -79,7 +79,7 @@ -outdir &OUTPUT_DIR;/point_stat_ugrid -v 1 - &OUTPUT_DIR;/point_stat_ugrid/point_stat_UGRID_MPAS_DIAG_TEMP_000000L_20120409_120000V.stat + &OUTPUT_DIR;/point_stat_ugrid/point_stat_UGRID_MPAS_DIAG_TEMP_120000L_20120409_120000V.stat @@ -98,7 +98,7 @@ -outdir &OUTPUT_DIR;/point_stat_ugrid -v 1 - &OUTPUT_DIR;/point_stat_ugrid/point_stat_UGRID_MPAS_OUT_TEMP_000000L_20120409_120000V.stat + &OUTPUT_DIR;/point_stat_ugrid/point_stat_UGRID_MPAS_OUT_TEMP_120000L_20120409_120000V.stat @@ -118,7 +118,7 @@ -outdir &OUTPUT_DIR;/point_stat_ugrid -v 1 - &OUTPUT_DIR;/point_stat_ugrid/point_stat_UGRID_MPAS_CFG_OUT_TEMP_000000L_20120409_120000V.stat + &OUTPUT_DIR;/point_stat_ugrid/point_stat_UGRID_MPAS_CFG_OUT_TEMP_120000L_20120409_120000V.stat diff --git a/src/libcode/vx_data2d_ugrid/ugrid_file.cc b/src/libcode/vx_data2d_ugrid/ugrid_file.cc index e2597d3c58..c1fd8efcfd 100644 --- a/src/libcode/vx_data2d_ugrid/ugrid_file.cc +++ b/src/libcode/vx_data2d_ugrid/ugrid_file.cc @@ -23,7 +23,6 @@ #include #include "vx_math.h" -#include "vx_cal.h" #include "vx_log.h" #include "config_util.h" @@ -40,13 +39,13 @@ constexpr char def_config_prefix[] = "UGridConfig_"; constexpr char def_config_prefix2[] = "MET_BASE/config/UGridConfig_"; constexpr double lat_epsilon = 0.00001; -array DIM_KEYS = { +const array DIM_KEYS = { "dim_face", "dim_node", "dim_edge", "dim_time", "dim_vert" }; -array COORD_VAR_KEYS = { +const array COORD_VAR_KEYS = { "time", "lat_face", "lon_face", "vert_face", "lat_edge", - "lon_edge", "lat_node", "lon_node", "cell_id" + "lon_edge", "lat_node", "lon_node", "cell_id", "init_time" }; static double get_nc_var_att_double(const NcVar *nc_var, const char *att_name, @@ -88,7 +87,6 @@ void UGridFile::init_from_scratch() _ncFile = (NcFile *) nullptr; _ncMetaFile = (NcFile *) nullptr; Var = (NcVarInfo *) nullptr; - _time_var_info = (NcVarInfo *)nullptr; _faceDim = (NcDim *)nullptr; _edgeDim = (NcDim *)nullptr; @@ -97,6 +95,9 @@ void UGridFile::init_from_scratch() _tDim = (NcDim *)nullptr; _latVar = (NcVar *)nullptr; _lonVar = (NcVar *)nullptr; + _zVar = (NcVar *)nullptr; + _tVar = (NcVar *)nullptr; + _init_time_var = (NcVar *)nullptr; // Close any existing file @@ -219,18 +220,18 @@ bool UGridFile::open(const char * filepath) bool UGridFile::open_metadata(const char * filepath) { - unixtime ut = 0; const char *method_name = "UGridFile::open_metadata() -> "; // Open the file _ncMetaFile = open_ncfile(filepath); + mlog << Debug(7) << method_name << "open " << filepath << "\n"; + if (IS_INVALID_NC_P(_ncMetaFile)) { close(); exit(1); } - NcDim dim; StringArray dim_names; get_dim_names(_ncMetaFile, &dim_names); @@ -256,152 +257,23 @@ bool UGridFile::open_metadata(const char * filepath) assign_dim_from_metadata(_ncFile, _tDim, DIM_KEYS[3], dim_names); // Time dimension assign_dim_from_metadata(_ncFile, _virtDim, DIM_KEYS[4], dim_names); // Vertical dimension - int max_dim_count = 0; - StringArray var_names; - ConcatString att_value; - auto z_var = (NcVar *)nullptr; - auto valid_time_var = (NcVar *)nullptr; - string time_dim_name = find_metadata_name(DIM_KEYS[3], dim_names); - string vert_dim_name = find_metadata_name(DIM_KEYS[4], dim_names); + metadata_coord_variables(); - StringArray time_names = get_metadata_names(COORD_VAR_KEYS[0]); - StringArray lat_names = get_metadata_names(COORD_VAR_KEYS[1]); - StringArray lon_names = get_metadata_names(COORD_VAR_KEYS[2]); - StringArray z_names = get_metadata_names(COORD_VAR_KEYS[3]); - for (int j=0; j max_dim_count) max_dim_count = dim_count; - - // parse the variable attributes - get_att_str( MetaVar[j], long_name_att_name, MetaVar[j].long_name_att ); - get_att_str( MetaVar[j], units_att_name, MetaVar[j].units_att ); - - if (0 == j && nullptr == _time_var_info) { - valid_time_var = MetaVar[j].var; - _time_var_info = &MetaVar[j]; - } - else if (1 == j && nullptr == _latVar) _latVar = MetaVar[j].var; - else if (2 == j && nullptr == _lonVar) _lonVar = MetaVar[j].var; - else if (3 == j && nullptr == z_var) z_var = MetaVar[j].var; - } - } // for j - - - // Pull out the valid and init times - if (IS_INVALID_NC_P(valid_time_var)) { + InitTime = 0; + if (!metadata_time()) { mlog << Debug(4) << method_name << "could not extract valid time from the " << "time variable from " << filepath << "\n"; - - ValidTime.add(ut); } - else { - int sec_per_unit; - - // Store the dimension for the time variable as the time dimension - ConcatString units; - bool use_bounds_var = false; - int time_dim_count = get_dim_count(valid_time_var); - if (time_dim_count == 1 || time_dim_count == 2) { - NcDim tDim = get_nc_dim(valid_time_var, 0); - if (IS_VALID_NC(tDim)) { - _tDim = new NcDim(tDim); - } - } - - // Parse the units for the time variable. - ut = sec_per_unit = 0; - if (get_var_units(valid_time_var, units) && (time_dim_count < 2)) { - if (units.empty()) { - mlog << Warning << "\n" << method_name - << "the \"time\" variable must contain a \"units\" attribute. " - << "Using valid time of 0\n\n"; - } - else { - mlog << Debug(4) << method_name - << "parsing units for the time variable \"" << units << "\"\n"; - parse_cf_time_string(units.c_str(), ut, sec_per_unit); - } - } - // Determine the number of times present. - int n_times = IS_VALID_NC_P(_tDim) ? get_dim_size(_tDim) - : get_data_size(valid_time_var); - int tim_buf_size = n_times; - vector time_values(tim_buf_size); - if(2 == time_dim_count) { - for(int i=0; i 1 ) { - double latest_time = bad_data_double; - for(int i=0; ivar; + if (info) _zVar = info->var; } // Pull out the vertical levels - if (IS_VALID_NC_P(z_var)) { - - int z_count = get_data_size(z_var); + if (IS_VALID_NC_P(_zVar)) { + int z_count = get_data_size(_zVar); vector z_values(z_count); - if( get_nc_data(z_var, z_values.data()) ) { + if( get_nc_data(_zVar, z_values.data()) ) { for(int i=0; it_slot && k != var->z_slot) length = plane_size - a[k]; + if (k != var->t_slot && k != var->z_slot) length = (int)(plane_size - a[k]); } lengths.add(length); dim_size = v->getDim(k).getSize(); @@ -752,7 +626,7 @@ bool UGridFile::getData(NcVar * v, const LongArray & a, DataPlane & plane) const log_message << " " << (a[idx] == vx_data2d_star ? "*" : std::to_string(a[idx])); } mlog << Debug(6) << method_name << "took " - << (clock()-start_clock)/double(CLOCKS_PER_SEC) << " seconds. " + << (clock()-start_clock)/CLOCKS_PER_SEC << " seconds. " << GET_NC_NAME_P(v) << ": levels: (" << log_message << " )" << " min=" << min_value << ", max_value=" << max_value<< "\n"; @@ -773,7 +647,7 @@ bool UGridFile::getData(const char *var_name, // store the times unixtime valid_ut; - if(info->t_slot >= 0) valid_ut = ValidTime[a[info->t_slot]]; + if(info->t_slot >= 0) valid_ut = ValidTime[(int)a[info->t_slot]]; else valid_ut = ValidTime[0]; // if unset, set the init time to the valid time @@ -790,8 +664,8 @@ bool UGridFile::getData(const char *var_name, plane.set_init(init_ut); plane.set_valid(valid_ut); - plane.set_lead(valid_ut - init_ut); - plane.set_accum(accum_time); + plane.set_lead((int)(valid_ut - init_ut)); + plane.set_accum((int)accum_time); // done @@ -820,7 +694,6 @@ bool UGridFile::get_var_info() { } NcDim dim; - int max_dim_count = 0; ConcatString att_value; StringArray var_names; @@ -835,7 +708,6 @@ bool UGridFile::get_var_info() { int dim_count = GET_NC_DIM_COUNT(v); Var[j].Ndims = dim_count; - if (dim_count > max_dim_count) max_dim_count = dim_count; Var[j].Dims = new NcDim * [dim_count]; @@ -864,6 +736,148 @@ int UGridFile::lead_time() const //////////////////////////////////////////////////////////////////////// +void UGridFile::metadata_coord_variables() { + static const string method_name + = "UGridFile::metadata_coord_variables() => "; + + //Variables at the data file first + StringArray time_names = get_metadata_names(COORD_VAR_KEYS[0]); + StringArray lat_names = get_metadata_names(COORD_VAR_KEYS[1]); + StringArray lon_names = get_metadata_names(COORD_VAR_KEYS[2]); + StringArray z_names = get_metadata_names(COORD_VAR_KEYS[3]); + StringArray init_time_names = get_metadata_names(COORD_VAR_KEYS[9]); + for (int j=0; j time_values(n_times); + if(is_string_time) { // String type: YYYY-MM-DD HH:MM:SS + mlog << Debug(7) << method_name + << "from " << GET_NC_NAME_P(_tVar) << "\n"; + for(int i=0; i _lat(face_count); vector _lon(face_count); - + if (IS_INVALID_NC_P(_latVar)) { mlog << Error << "\n" << method_name << "latitude variable is missing\n\n"; exit(1); diff --git a/src/libcode/vx_data2d_ugrid/ugrid_file.h b/src/libcode/vx_data2d_ugrid/ugrid_file.h index a0d1828d60..cb58947244 100644 --- a/src/libcode/vx_data2d_ugrid/ugrid_file.h +++ b/src/libcode/vx_data2d_ugrid/ugrid_file.h @@ -30,7 +30,7 @@ static const int UG_DIM_COUNT = 5; -static const int UG_META_VAR_COUNT = 9; +static const int UG_META_VAR_COUNT = 10; //////////////////////////////////////////////////////////////////////// @@ -65,8 +65,6 @@ class UGridFile { return 1; } - NcVarInfo *get_time_var_info() const { return _time_var_info; } - // // time // @@ -146,7 +144,9 @@ class UGridFile { netCDF::NcVar *_latVar; netCDF::NcVar *_lonVar; - NcVarInfo *_time_var_info; + netCDF::NcVar *_zVar; + netCDF::NcVar *_tVar; + netCDF::NcVar *_init_time_var; int face_count; @@ -165,6 +165,8 @@ class UGridFile { std::string find_metadata_name(const std::string &key, const StringArray &available_names); StringArray get_metadata_names(const std::string &key); + void metadata_coord_variables(); + bool metadata_time(); void radian_to_degree(std::vector &lat_values, const int lat_count) const; void read_config(const ConcatString &config_filename); void read_netcdf_grid(); diff --git a/src/libcode/vx_nc_util/nc_utils.cc b/src/libcode/vx_nc_util/nc_utils.cc index 5a8b6e8ccb..cf16298f97 100644 --- a/src/libcode/vx_nc_util/nc_utils.cc +++ b/src/libcode/vx_nc_util/nc_utils.cc @@ -1157,12 +1157,11 @@ int get_int_var(NcVar * var, const int index) { //////////////////////////////////////////////////////////////////////// double get_nc_time(NcVar * var, const int index) { - double k; vector start; vector count; const char *method_name = "get_nc_time() -> "; - k = bad_data_double; + double k = bad_data_double; if (IS_VALID_NC_P(var)) { int dim_idx = 0; int dim_size = get_dim_size(var, dim_idx); @@ -1183,19 +1182,25 @@ double get_nc_time(NcVar * var, const int index) { exit(1); } + int buf_len = 512; + int dataType = GET_NC_TYPE_ID_P(var); + int dim_count = get_dim_count(var); + if (dataType == NC_CHAR) { + buf_len = get_dim_size(var, (dim_count-1)); + } + int vi; short vs; float vf; ncbyte vb; long long vl; unixtime ref_ut; - int buf_len = 512; - vector tmp_buf(buf_len); - int dataType = GET_NC_TYPE_ID_P(var); + vector tmp_buf(buf_len+1); start.emplace_back(index); count.emplace_back(1); tmp_buf[0] = 0; + tmp_buf[buf_len] = 0; switch (dataType) { case NC_DOUBLE: @@ -1214,12 +1219,13 @@ double get_nc_time(NcVar * var, const int index) { k = (double)vb; break; case NC_CHAR: - if (2 == get_dim_count(var)) { - buf_len = get_dim_size(var, 1); + if (2 == dim_count) { start.emplace_back(0); count.emplace_back(buf_len); } + else count[0] = buf_len; for (int i=0; igetVar(start, count, tmp_buf.data()); parse_time_string(tmp_buf.data(), ref_ut); k = ref_ut; @@ -3618,60 +3624,82 @@ int get_data_size(NcVar *var) { } //////////////////////////////////////////////////////////////////////// -// Moved from nc_cf_file.cc -// init_time or valid_time from filename` -string init_time_var_name = "forecast_reference_time"; -unixtime get_init_time(NcFile *nc_file) { +unixtime get_init_time(NcVar *time_var) { unixtime init_time = 0; - NcVar init_time_var = get_var(nc_file, init_time_var_name.c_str()); - const char *method_name = "get_init_time(NcFile *, string &) -> "; + const char *method_name = "get_init_time(NcVar *) -> "; - if (IS_INVALID_NC(init_time_var)) { + if (IS_INVALID_NC_P(time_var)) { mlog << Debug(4) << method_name - << "could not extract init time from the " - << "\"" << init_time_var_name << "\" variable.\n"; + << "could not extract init time\n"; + return init_time; } - else { - unixtime ut = 0; + + int data_type = GET_NC_TYPE_ID_P(time_var); + bool is_string_time = (NC_CHAR == data_type || NC_STRING == data_type); + double time_value = get_nc_time(time_var, 0); + init_time = (unixtime)time_value; + if (!is_string_time) { int sec_per_unit = 0; - ConcatString units; + bool no_leap_year = true; + unixtime ref_ut = get_reference_unixtime(time_var, sec_per_unit, + no_leap_year, method_name); + init_time = ref_ut + (unixtime)(sec_per_unit * time_value); + } - // Parse the units for the time variable. - if (get_var_units(&init_time_var, units)) { - if (units.empty()) { - mlog << Warning << "\n" << method_name - << "the \"" << init_time_var_name << "\" variable must contain a \"units\" attribute.\n\n"; - } - else { - parse_cf_time_string(units.c_str(), ut, sec_per_unit); - } - } + mlog << Debug(4) << method_name + << "get InitTime (" << unix_to_yyyymmdd_hhmmss(init_time) + << ") from \"" << GET_NC_NAME_P(time_var) << "\" variable (value=" + << time_value << ").\n"; - double time_value = get_nc_time(&init_time_var,0); - init_time = ut + (unixtime)(sec_per_unit * time_value); + return init_time; +} +//////////////////////////////////////////////////////////////////////// +// Moved from nc_cf_file.cc +// init_time from NetCDF, default variable name is forecast_reference_time +// Can get valid_time id byte or char type with variable name + +const string init_time_var_name = "forecast_reference_time"; + +unixtime get_init_time(NcFile *nc_file, const char *time_var_name) { + unixtime init_time = 0; + const char *method_name = "get_init_time(NcFile *, string &) -> "; + + if (time_var_name == nullptr) time_var_name = init_time_var_name.c_str(); + + NcVar time_var = get_var(nc_file, time_var_name); + if (IS_INVALID_NC(time_var)) { mlog << Debug(4) << method_name - << "get InitTime (" << unix_to_yyyymmdd_hhmmss(init_time) - << ") from \"" << init_time_var_name << "\" variable (value=" << time_value<< ").\n"; + << "could not extract init time from the " + << "\"" << time_var_name << "\" variable.\n"; + } + else { + init_time = get_init_time(&time_var); } return init_time; } //////////////////////////////////////////////////////////////////////// +// retrurn 0 if units attribute is missing unixtime get_reference_unixtime(NcVar *time_var, int &sec_per_unit, - bool &no_leap_year) { + bool &no_leap_year, const char *caller) { unixtime ref_ut = 0; ConcatString time_unit_str; static const char *method_name = "get_reference_unixtime() -> "; + sec_per_unit = 1; if (get_var_units(time_var, time_unit_str)) { - parse_cf_time_string(time_unit_str.c_str(), ref_ut, sec_per_unit); - no_leap_year = (sec_per_day == sec_per_unit) ? get_att_no_leap_year(time_var) : false; - } - else { - sec_per_unit = 1; + if (time_unit_str.empty()) { + mlog << Warning << "\n" << (caller != nullptr ? caller : method_name) + << "the \"" << GET_NC_NAME_P(time_var) + << "\" variable does not have a \"units\" attribute.\n\n"; + } + else { + parse_cf_time_string(time_unit_str.c_str(), ref_ut, sec_per_unit); + } } + no_leap_year = (sec_per_day == sec_per_unit) ? get_att_no_leap_year(time_var) : false; return ref_ut; } @@ -3708,7 +3736,7 @@ bool is_nc_unit_time(const char *units) { //////////////////////////////////////////////////////////////////////// -void parse_cf_time_string(const char *str, unixtime &ref_ut, +bool parse_cf_time_string(const char *str, unixtime &ref_ut, int &sec_per_unit) { static const char *method_name = "parse_cf_time_string() -> "; @@ -3721,7 +3749,7 @@ void parse_cf_time_string(const char *str, unixtime &ref_ut, mlog << Warning << "\n" << method_name << "unexpected NetCDF CF convention time unit \"" << str << "\"\n\n"; - return; + return false; } else { // Tokenize the input string @@ -3756,7 +3784,7 @@ void parse_cf_time_string(const char *str, unixtime &ref_ut, mlog << Warning << "\n" << method_name << "Unsupported time step in the CF convention time unit \"" << str << "\"\n\n"; - return; + return false; } // Parse the reference time @@ -3775,7 +3803,7 @@ void parse_cf_time_string(const char *str, unixtime &ref_ut, << "\"\n\t\t as a reference time of " << unix_to_yyyymmdd_hhmmss(ref_ut) << " and " << sec_per_unit << " second(s) per time step.\n"; - return; + return true; } //////////////////////////////////////////////////////////////////////// @@ -3800,16 +3828,17 @@ void parse_time_string(const char *str, unixtime &ut) { // 2016-01-28T12:00:00Z // 1977-08-07 12:00:00Z StringArray tok; + StringArray hms; + StringArray ymd; tok.parse_delim(str, " _T"); // Parse the reference time - StringArray ymd, hms; ymd.parse_delim(tok[0], "-"); if(tok.n_elements() > 1) hms.parse_delim(tok[1], ":"); else hms.parse_delim("00:00:00", ":"); ut = mdyhms_to_unix(atoi(ymd[1].c_str()), atoi(ymd[2].c_str()), - atoi(ymd[0].c_str()), atoi(hms[0].c_str()), - hms.n_elements() > 1 ? atoi(hms[1].c_str()) : 0, - hms.n_elements() > 2 ? atoi(hms[2].c_str()) : 0); + atoi(ymd[0].c_str()), atoi(hms[0].c_str()), + hms.n_elements() > 1 ? atoi(hms[1].c_str()) : 0, + hms.n_elements() > 2 ? atoi(hms[2].c_str()) : 0); } mlog << Debug(4) << method_name diff --git a/src/libcode/vx_nc_util/nc_utils.h b/src/libcode/vx_nc_util/nc_utils.h index 1817626e74..dae2a76494 100644 --- a/src/libcode/vx_nc_util/nc_utils.h +++ b/src/libcode/vx_nc_util/nc_utils.h @@ -340,16 +340,17 @@ extern int get_index_at_nc_data(netCDF::NcVar *var, double value_min, double extern netCDF::NcFile* open_ncfile(const char * nc_name, bool write = false); // Moved from nc_cf_file.cc -extern unixtime get_init_time(netCDF::NcFile *nc_file); +extern unixtime get_init_time(netCDF::NcVar *var); +extern unixtime get_init_time(netCDF::NcFile *nc_file, const char *time_var_name=nullptr); extern unixtime get_reference_unixtime(netCDF::NcVar *time_var, int &sec_per_unit, - bool &no_leap_year); + bool &no_leap_year, const char *caller=nullptr); extern bool is_nc_unit_time(const char *units); extern bool is_nc_unit_longitude(const char *units); extern bool is_nc_unit_latitude(const char *units); -extern void parse_cf_time_string(const char *str, unixtime &ref_ut, +extern bool parse_cf_time_string(const char *str, unixtime &ref_ut, int &sec_per_unit); extern void parse_time_string(const char *str, unixtime &ut);