diff --git a/ncdump/CMakeLists.txt b/ncdump/CMakeLists.txt index 39610f8031..00447e9bf6 100644 --- a/ncdump/CMakeLists.txt +++ b/ncdump/CMakeLists.txt @@ -263,6 +263,7 @@ endif() add_sh_test(ncdump tst_ncgen4) add_sh_test(ncdump tst_netcdf4_4) add_sh_test(ncdump tst_nccopy4) + add_sh_test(ncdump tst_calendars_nc4) SET_TESTS_PROPERTIES(ncdump_tst_nccopy4 PROPERTIES DEPENDS "ncdump_run_ncgen_tests;ncdump_tst_output;ncdump_tst_ncgen4;ncdump_sh_tst_fillbug;ncdump_tst_netcdf4_4;ncdump_tst_h_scalar;tst_comp;tst_comp2;tst_nans;tst_opaque_data;tst_create_files;tst_special_atts") SET_TESTS_PROPERTIES(ncdump_tst_nccopy5 PROPERTIES DEPENDS "ncdump_tst_nccopy4") diff --git a/ncdump/Makefile.am b/ncdump/Makefile.am index e601cdca49..183a1e7dab 100644 --- a/ncdump/Makefile.am +++ b/ncdump/Makefile.am @@ -151,7 +151,7 @@ TESTS += tst_output.sh TESTS += tst_nccopy3.sh if USE_HDF5 TESTS += run_back_comp_tests.sh tst_netcdf4_4.sh -TESTS += tst_nccopy4.sh tst_nccopy5.sh +TESTS += tst_nccopy4.sh tst_nccopy5.sh tst_calendars_nc4.sh endif endif endif diff --git a/ncdump/nctime0.c b/ncdump/nctime0.c index 461a798004..f7b3d24788 100644 --- a/ncdump/nctime0.c +++ b/ncdump/nctime0.c @@ -79,13 +79,18 @@ calendar_type(int ncid, int varid) { int ncals = (sizeof calmap)/(sizeof calmap[0]); ctype = cdMixed; /* default mixed Gregorian/Julian ala udunits */ stat = nc_inq_att(ncid, varid, CF_CAL_ATT_NAME, &catt.type, &catt.len); - if(stat == NC_NOERR && catt.type == NC_CHAR && catt.len > 0) { - char *calstr = (char *)emalloc(catt.len + 1); + if(stat == NC_NOERR && (catt.type == NC_CHAR || catt.type == NC_STRING) && catt.len > 0) { + char *calstr; + size_t cf_cal_att_name_len = strlen(CF_CAL_ATT_NAME); + strncpy(catt.name, CF_CAL_ATT_NAME, cf_cal_att_name_len); + catt.name[cf_cal_att_name_len] = '\0'; + catt.tinfo = get_typeinfo(catt.type); + nc_get_att_single_string(ncid, varid, &catt, &calstr); + int itype; - NC_CHECK(nc_get_att(ncid, varid, CF_CAL_ATT_NAME, calstr)); - calstr[catt.len] = '\0'; + int calstr_len = strlen(calstr); for(itype = 0; itype < ncals; itype++) { - if(strncasecmp(calstr, calmap[itype].attname, catt.len) == 0) { + if(strncasecmp(calstr, calmap[itype].attname, calstr_len) == 0) { ctype = calmap[itype].type; break; } @@ -204,10 +209,11 @@ get_timeinfo(int ncid1, int varid1, ncvar_t *vp) { /* time variables must have appropriate units attribute or be a bounds variable */ nc_status = nc_inq_att(ncid, varid, "units", &uatt.type, &uatt.len); - if(nc_status == NC_NOERR && uatt.type == NC_CHAR) { /* TODO: NC_STRING? */ - units = emalloc(uatt.len + 1); - NC_CHECK(nc_get_att(ncid, varid, "units", units)); - units[uatt.len] = '\0'; + if(nc_status == NC_NOERR && (uatt.type == NC_CHAR || uatt.type == NC_STRING)) { + strncpy(uatt.name, "units", 5); + uatt.name[5] = '\0'; + uatt.tinfo = get_typeinfo(uatt.type); + nc_get_att_single_string(ncid, varid, &uatt, &units); if(!is_valid_time_unit(units)) { free(units); return; diff --git a/ncdump/ref_times_nc4.cdl b/ncdump/ref_times_nc4.cdl new file mode 100644 index 0000000000..53e9bd926b --- /dev/null +++ b/ncdump/ref_times_nc4.cdl @@ -0,0 +1,146 @@ +netcdf tst_times_nc4 { +dimensions: + time = 1 ; + bnds = 2 ; + t3 = UNLIMITED ; // (3 currently) +variables: + double t1_days(time) ; + t1_days:units = "days since 1500-1-1" ; + double t1_days_case(time) ; + t1_days_case:units = "DaYs since 1500-1-1" ; + double t1_st_days(time) ; + t1_st_days:calendar = "standard" ; + t1_st_days:units = "days since 1500-01-01 00:00:00" ; + double t1_gr_days(time) ; + t1_gr_days:calendar = "gregorian" ; + t1_gr_days:units = "days since 1500-01-01 00:00:00" ; + double t1_pg_days(time) ; + t1_pg_days:calendar = "proleptic_gregorian" ; + t1_pg_days:units = "days since 1500-01-01 00:00:00" ; + double t1_nl_days(time) ; + t1_nl_days:calendar = "noleap" ; + t1_nl_days:units = "days since 1500-01-01 00:00:00" ; + double t1_365_days(time) ; + t1_365_days:calendar = "365_day" ; + t1_365_days:units = "days since 1500-01-01 00:00:00" ; + double t1_al_days(time) ; + t1_al_days:calendar = "all_leap" ; + t1_al_days:units = "days since 1500-01-01 00:00:00" ; + double t1_366_days(time) ; + t1_366_days:calendar = "366_day" ; + t1_366_days:units = "days since 1500-01-01 00:00:00" ; + double t1_360_days(time) ; + t1_360_days:calendar = "360_day" ; + t1_360_days:units = "days since 1500-01-01 00:00:00" ; + double t1_jl_days(time) ; + t1_jl_days:calendar = "julian" ; + t1_jl_days:units = "days since 1500-01-01 00:00:00" ; + double t2_days(time) ; + string t2_days:units = "days since 2000-6-15 12:00" ; + double t2_st_days(time) ; + string t2_st_days:calendar = "standard" ; + string t2_st_days:units = "days since 2000-06-15 12:00:00" ; + double t2_gr_days(time) ; + string t2_gr_days:calendar = "gregorian" ; + string t2_gr_days:units = "days since 2000-06-15 12:00:00" ; + double t2_pg_days(time) ; + string t2_pg_days:calendar = "proleptic_gregorian" ; + string t2_pg_days:units = "days since 2000-06-15 12:00:00" ; + double t2_pgt_days(time) ; + string t2_pgt_days:calendar = "proleptic_gregorian" ; + string t2_pgt_days:units = "days since 2000-06-15T12:00:00" ; + double t2_nl_days(time) ; + string t2_nl_days:calendar = "noleap" ; + string t2_nl_days:units = "days since 2000-06-15 12:00:00" ; + double t2_365_days(time) ; + string t2_365_days:calendar = "365_day" ; + string t2_365_days:units = "days since 2000-06-15 12:00:00" ; + double t2_al_days(time) ; + string t2_al_days:calendar = "all_leap" ; + string t2_al_days:units = "days since 2000-06-15 12:00:00" ; + double t2_366_days(time) ; + string t2_366_days:calendar = "366_day" ; + string t2_366_days:units = "days since 2000-06-15 12:00:00" ; + double t2_360_days(time) ; + string t2_360_days:calendar = "360_day" ; + string t2_360_days:units = "days since 2000-06-15 12:00:00" ; + double t2_jl_days(time) ; + string t2_jl_days:calendar = "julian" ; + string t2_jl_days:units = "days since 2000-06-15 12:00:00" ; + int t3(t3) ; + t3:units = "days since 1804-1-1" ; + t3:calendar = "gregorian" ; + t3:bounds = "t3_bnds" ; + t3:time1 = 1 ; // "1804-01-02" + t3:time2 = 5, 6 ; // "1804-01-06", "1804-01-07" + t3:time3 = 7.125f, 8.75f ; // "1804-01-08 03", "1804-01-09 18" + t3:time4 = 58.5, 59.5, 60.5 ; + // "1804-02-28 12", "1804-02-29 12", "1804-03-01 12" + t3:time5 = 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120 ; + // "1804-04-10", "1804-04-11", "1804-04-12", "1804-04-13", + // "1804-04-14", "1804-04-15", "1804-04-16", "1804-04-17", + // "1804-04-18", "1804-04-19", "1804-04-20", "1804-04-21", + // "1804-04-22", "1804-04-23", "1804-04-24", "1804-04-25", + // "1804-04-26", "1804-04-27", "1804-04-28", "1804-04-29", + // "1804-04-30" + double t3_bnds(t3, bnds) ; + int t4 ; + t4:units = "days" ; + t4:att1 = 1 ; + t4:att2 = 5, 6 ; + t4:att3 = 7.125f, 8.75f ; +data: + + t1_days = "2009-01-01" ; + + t1_days_case = "2009-01-01" ; + + t1_st_days = "2009-01-01" ; + + t1_gr_days = "2009-01-01" ; + + t1_pg_days = "2009-01-01" ; + + t1_nl_days = "2009-01-01" ; + + t1_365_days = "2009-01-01" ; + + t1_al_days = "2009-01-01" ; + + t1_366_days = "2009-01-01" ; + + t1_360_days = "2009-01-01" ; + + t1_jl_days = "2009-01-01" ; + + t2_days = "2009-01-01" ; + + t2_st_days = "2009-01-01" ; + + t2_gr_days = "2009-01-01" ; + + t2_pg_days = "2009-01-01" ; + + t2_pgt_days = "2009-01-01" ; + + t2_nl_days = "2009-01-01" ; + + t2_365_days = "2009-01-01" ; + + t2_al_days = "2009-01-01" ; + + t2_366_days = "2009-01-01" ; + + t2_360_days = "2009-01-01" ; + + t2_jl_days = "2009-01-01" ; + + t3 = "1804-01-11", "1804-01-12", "1804-01-13" ; + + t3_bnds = + "1804-01-10 12", "1804-01-11 12", + "1804-01-11 12", "1804-01-12 12", + "1804-01-12 12", "1804-01-13 12" ; + + t4 = _ ; +} diff --git a/ncdump/tst_calendars_nc4.cdl b/ncdump/tst_calendars_nc4.cdl new file mode 100644 index 0000000000..1bb292f146 --- /dev/null +++ b/ncdump/tst_calendars_nc4.cdl @@ -0,0 +1,133 @@ +netcdf tst_calendars_nc4 { // test climate calendars and CDL time with -t option +dimensions: + time = 1; + bnds = 2 ; // for cell bounds on t3 time coordinate variable + t3 = unlimited ; +variables: + // Use fixed length string attributes for the t1 set of variables + double t1_days(time); + t1_days:units = "days since 1500-1-1"; + double t1_days_case(time); + t1_days_case:units = "DaYs since 1500-1-1"; + double t1_st_days(time); + t1_st_days:calendar = "standard" ; // mixed julian-gregorian + t1_st_days:units = "days since 1500-01-01 00:00:00"; + double t1_gr_days(time); + t1_gr_days:calendar = "gregorian" ; // same as "standard" + t1_gr_days:units = "days since 1500-01-01 00:00:00"; + double t1_pg_days(time); + t1_pg_days:calendar = "proleptic_gregorian" ; + t1_pg_days:units = "days since 1500-01-01 00:00:00"; + double t1_nl_days(time); + t1_nl_days:calendar = "noleap" ; + t1_nl_days:units = "days since 1500-01-01 00:00:00"; + double t1_365_days(time); + t1_365_days:calendar = "365_day" ; // same as "noleap" + t1_365_days:units = "days since 1500-01-01 00:00:00"; + double t1_al_days(time); + t1_al_days:calendar = "all_leap" ; + t1_al_days:units = "days since 1500-01-01 00:00:00"; + double t1_366_days(time); + t1_366_days:calendar = "366_day" ; // same as "all_leap" + t1_366_days:units = "days since 1500-01-01 00:00:00"; + double t1_360_days(time); + t1_360_days:calendar = "360_day" ; + t1_360_days:units = "days since 1500-01-01 00:00:00"; + double t1_jl_days(time); + t1_jl_days:calendar = "julian" ; + t1_jl_days:units = "days since 1500-01-01 00:00:00"; + + // Use variable length string attributes for the t2 set of variables + double t2_days(time); + string t2_days:units = "days since 2000-6-15 12:00"; + double t2_st_days(time); + string t2_st_days:calendar = "standard" ; // mixed julian-gregorian + string t2_st_days:units = "days since 2000-06-15 12:00:00"; + double t2_gr_days(time); + string t2_gr_days:calendar = "gregorian" ; // same as "standard" + string t2_gr_days:units = "days since 2000-06-15 12:00:00"; + double t2_pg_days(time); + string t2_pg_days:calendar = "proleptic_gregorian" ; + string t2_pg_days:units = "days since 2000-06-15 12:00:00"; + double t2_pgt_days(time); + string t2_pgt_days:calendar = "proleptic_gregorian" ; + string t2_pgt_days:units = "days since 2000-06-15T12:00:00"; + double t2_nl_days(time); + string t2_nl_days:calendar = "noleap" ; + string t2_nl_days:units = "days since 2000-06-15 12:00:00"; + double t2_365_days(time); + string t2_365_days:calendar = "365_day" ; // same as "noleap" + string t2_365_days:units = "days since 2000-06-15 12:00:00"; + double t2_al_days(time); // *** no year, 07-29 12:00 + string t2_al_days:calendar = "all_leap" ; // seems wrong, same as gregorian + string t2_al_days:units = "days since 2000-06-15 12:00:00"; + double t2_366_days(time); // *** no year, 07-29 12:00 + string t2_366_days:calendar = "366_day" ; // omits years, same as "clim"?? + string t2_366_days:units = "days since 2000-06-15 12:00:00"; + double t2_360_days(time); + string t2_360_days:calendar = "360_day" ; // omits years, same as "clim"?? + string t2_360_days:units = "days since 2000-06-15 12:00:00"; + double t2_jl_days(time); + string t2_jl_days:calendar = "julian" ; + string t2_jl_days:units = "days since 2000-06-15 12:00:00"; + +// double t1_none_days(time); +// t1_none_days:calendar = "none" ; +// t1_none_days:units = "days since 1500-01-01 00:00:00"; +// double t2_none_days(time); +// t2_none_days:calendar = "none" ; +// t2_none_days:units = "days since 2000-06-15 12:00:00"; + +// test -t option on numeric attributes of a time-valued variable + int t3(t3) ; + t3:units = "days since 1804-1-1" ; + t3:calendar = "gregorian" ; + t3:bounds = "t3_bnds" ; + t3:time1 = 1 ; + t3:time2 = 5, 6 ; + t3:time3 = 7.125f, 8.75f ; + t3:time4 = 58.5, 59.5, 60.5 ; + t3:time5 = 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120 ; + double t3_bnds(t3, bnds) ; // no attributes, since a cell bounds variable + +// test -t bug fix, time unit without base time should not interpret numeric atts as times + int t4 ; + t4:units = "days" ; + t4:att1 = 1 ; + t4:att2 = 5, 6 ; + t4:att3 = 7.125f, 8.75f ; + +data: + // Should all represent 2009-01-01 00:00:00 + t1_days = 185900; + t1_days_case = 185900; + t1_st_days = 185900; + t1_gr_days = 185900; + t1_pg_days = 185909; + t1_nl_days = 185785; + t1_365_days = 185785; + t1_366_days = 186294; + t1_al_days = 186294; + t1_360_days = 183240; + t1_jl_days = 185913; + + t2_days = 3121.5; + t2_st_days = 3121.5; + t2_gr_days = 3121.5; + t2_pg_days = 3121.5; + t2_pgt_days = 3121.5; + t2_nl_days = 3119.5; + t2_365_days = 3119.5; + t2_366_days = 3127.5; + t2_al_days = 3127.5; + t2_360_days = 3075.5; + t2_jl_days = 3121.5; + +// Not sure what these should represent yet ... +// t1_none_days = 185900; +// t2_none_days = 3121.5; + + t3 = 10, 11, 12; + t3_bnds = 9.5, 10.5, 10.5, 11.5, 11.5, 12.5 ; +} + diff --git a/ncdump/tst_calendars_nc4.sh b/ncdump/tst_calendars_nc4.sh new file mode 100755 index 0000000000..0aa9d4d06c --- /dev/null +++ b/ncdump/tst_calendars_nc4.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +if test "x$srcdir" = x ; then srcdir=`pwd`; fi +. ../test_common.sh + +# This shell script tests ncdump -t option for CF calendar attributes using netcdf4 format + +set -e +echo "" +echo "*** Testing ncdump -t output for times with CF calendar attribute, netcdf4 format" +echo "*** creating netcdf4 file tst_calendars.nc from tst_calendars.cdl..." +${NCGEN} -b -k nc4 -o tst_calendars_nc4.nc $srcdir/tst_calendars_nc4.cdl +echo "*** creating tst_times_nc4.cdl from tst_calendars.nc with ncdump -t ..." +${NCDUMP} -n tst_times_nc4 -t tst_calendars_nc4.nc > tst_times_nc4.cdl +echo "*** comparing tst_times_nc4.cdl with ref_times_nc4.cdl..." +diff -b tst_times_nc4.cdl $srcdir/ref_times_nc4.cdl +echo "" +echo "*** All ncdump test output (netcdf4 format) for -t option with CF calendar atts passed!" + +exit 0 diff --git a/ncdump/utils.c b/ncdump/utils.c index f2b79863ad..30e059189e 100644 --- a/ncdump/utils.c +++ b/ncdump/utils.c @@ -13,6 +13,7 @@ #include #include #include "utils.h" +#include "nccomps.h" #ifndef isascii EXTERNL int isascii(int c); #endif @@ -959,3 +960,38 @@ parseFQN(int ncid, const char* fqn0, VarID* idp) } #endif +/*********************************************************************************/ +void nc_get_att_single_string(const int ncid, const int varid, + const struct ncatt_t *att, char **str_out) { + if (att->type == NC_CHAR) { + // NC_CHAR type attribute + // Use a call to nc_get_att_text which expects to output the attribute value + // into a char * pointing to allocated memory. The number of bytes to allocate + // is the attribute length (which is the number of elements in a vector, 1 for + // scalar) times the size of each element in bytes. The attribute length is + // held in att->len, and the attribute element size is in att->tinfo->size. + *str_out = emalloc((att->len + 1) * att->tinfo->size); + (*str_out)[att->len] = '\0'; + NC_CHECK(nc_get_att_text(ncid, varid, att->name, *str_out)); + } else if (att->type == NC_STRING) { + // NC_STRING type attribute + // Use a call to nc_get_att_string which expects to output the attribute value + // into a vector of char pointers, where each entry points to allocated memory. + // The vector of char pointers needs to be allocated to the length (number of strings) + // times the size of each entry (size of a char *). + char **att_strings = emalloc((att->len + 1) * att->tinfo->size); + NC_CHECK(nc_get_att_string(ncid, varid, att->name, att_strings)); + // str_out needs to be allocated to a size large enough to hold the string that + // the first pointer in att_strings is pointing to. + size_t att_str_len = strlen(att_strings[0]); + *str_out = emalloc((att_str_len + 1) * att->tinfo->size); + (*str_out)[att_str_len] = '\0'; + strncpy(*str_out, att_strings[0], att_str_len); + nc_free_string(att->len, att_strings); + } else { + fprintf(stderr,"nc_get_att_single_string: unknown attribute type: %d\n", att->type); + fprintf(stderr," must use one of: NC_CHAR, NC_STRING\n"); + fflush(stderr); fflush(stdout); + exit(2); + } +} diff --git a/ncdump/utils.h b/ncdump/utils.h index dc2a7aee29..cd413e39ac 100644 --- a/ncdump/utils.h +++ b/ncdump/utils.h @@ -10,6 +10,8 @@ #include "config.h" +struct ncatt_t; + #ifndef NCSTREQ #define NCSTREQ(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0) #endif @@ -181,6 +183,16 @@ extern int nc_next_giter(ncgiter_t *iterp, int *grpid); extern void nc_free_giter(ncgiter_t *iterp); extern int getrootid(int grpid); +/* + * Get attribute value for a single string value from either of NC_CHAR or NC_STRING types. + * This routine assumes that the attribute holds a single string value. If there are more + * than one string, subequent strings after the first one will be ignored. + * + * The caller is responsible for allocating and freeing memory for the str_out parameter. + */ +extern void nc_get_att_single_string(const int ncid, const int varid, + const struct ncatt_t *att, char **str_out); + #ifdef __cplusplus } #endif