Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow ncdump -t to handle variable length string attributes #2584

Merged
merged 5 commits into from
Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ncdump/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
WardF marked this conversation as resolved.
Show resolved Hide resolved

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")
Expand Down
24 changes: 15 additions & 9 deletions ncdump/nctime0.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down
146 changes: 146 additions & 0 deletions ncdump/ref_times_nc4.cdl
Original file line number Diff line number Diff line change
@@ -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 = _ ;
}
133 changes: 133 additions & 0 deletions ncdump/tst_calendars_nc4.cdl
Original file line number Diff line number Diff line change
@@ -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 ;
}

20 changes: 20 additions & 0 deletions ncdump/tst_calendars_nc4.sh
Original file line number Diff line number Diff line change
@@ -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
36 changes: 36 additions & 0 deletions ncdump/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <assert.h>
#include <ctype.h>
#include "utils.h"
#include "nccomps.h"
#ifndef isascii
EXTERNL int isascii(int c);
#endif
Expand Down Expand Up @@ -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);
}
}
Loading