/********************************************************************* * Copyright 2018, University Corporation for Atmospheric Research * See netcdf/COPYRIGHT file for copying and redistribution conditions. * $Id: nctime.c,v 1.9 2010/05/05 22:15:39 dmh Exp $ *********************************************************************/ /* * This code was extracted with permission from the CDMS time * conversion and arithmetic routines developed by Bob Drach, Lawrence * Livermore National Laboratory as part of the cdtime library. Russ * Rew of the UCAR Unidata Program made changes and additions to * support the "-t" option of the netCDF ncdump utility, including a * 366-day climate calendar. * * For the complete time conversion and climate calendar facilities of * the CDMS library, get the original sources from LLNL. */ #include #include #include #include #include #include #include #include #include "nctime.h" #include "utils.h" #include "nccomps.h" #include "dumplib.h" /* for sbuf_... prototypes */ #include "ncdump.h" /* for fspec_t def */ #include "nctime0.h" #include "vardata.h" static struct { size_t nbnds; /* number of bounds variables */ bounds_node_t *first; } bounds_list; extern fspec_t formatting_specs; /* set from command-line options */ /* rkr: added bounds functions to detect bounds variables of time variables */ void bounds_add(char *bounds_name, int ncid, int varid) { bounds_node_t *bnode = emalloc(sizeof(bounds_node_t) + 1); bounds_list.nbnds++; bnode->ncid = ncid; bnode->varid = varid; bnode->bounds_name = strdup(bounds_name); bnode->next = bounds_list.first; bounds_list.first = bnode; } /* Check for optional "calendar" attribute and return specified * calendar type, if present. */ cdCalenType calendar_type(int ncid, int varid) { int stat; ncatt_t catt; static struct { char* attname; cdCalenType type; } calmap[] = { {"gregorian", cdMixed}, {"standard", cdMixed}, /* synonym */ {"proleptic_gregorian", cdStandard}, {"noleap", cdNoLeap}, {"no_leap", cdNoLeap}, {"365_day", cdNoLeap}, /* synonym */ {"allleap", cd366}, {"all_leap", cd366}, /* synonym */ {"366_day", cd366}, /* synonym */ {"360_day", cd360}, {"julian", cdJulian}, {"none", cdClim} /* TODO: test this */ }; #define CF_CAL_ATT_NAME "calendar" int ncals = (sizeof calmap)/(sizeof calmap[0]); cdCalenType 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.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; size_t calstr_len = strlen(calstr); for(itype = 0; itype < ncals; itype++) { if(strncasecmp(calstr, calmap[itype].attname, calstr_len) == 0) { ctype = calmap[itype].type; break; } } free(calstr); } return ctype; } bool_t is_bounds_var(char *varname, int *pargrpidp, int *parvaridp) { bounds_node_t *bp = bounds_list.first; for(; bp; bp = bp->next) { if(NCSTREQ(bp->bounds_name, varname)) { *pargrpidp = bp->ncid; *parvaridp = bp->varid; return true; } } return false; } /* Test if attribute is of form required by cdtime: * since * where * : */ bool_t is_valid_time_unit(const char *units) { char charunits[CD_MAX_RELUNITS]; char basetime_1[CD_MAX_CHARTIME]; char basetime_2[CD_MAX_CHARTIME]; int nconv1, nconv2; bool_t okunit = false; /* Allow ISO-8601 "T" date-time separator as well as blank separator */ nconv1 = sscanf(units,"%s since %[^T]T%s", charunits, basetime_1, basetime_2); nconv2 = sscanf(units,"%s since %s %s", charunits, basetime_1, basetime_2); if (!(nconv1 > 1 || nconv2 > 1)) return false; /* Check for unit compatible with cdtime library, no attempt * to enforce CF-compliance or udunits compliance here ... */ if(!strncasecmp(charunits,"sec",3) || !strcasecmp(charunits,"s")){ okunit = true; } else if(!strncasecmp(charunits,"min",3) || !strcasecmp(charunits,"mn")){ okunit = true; } else if(!strncasecmp(charunits,"hour",4) || !strcasecmp(charunits,"hr")){ okunit = true; } else if(!strncasecmp(charunits,"day",3) || !strcasecmp(charunits,"dy")){ okunit = true; } else if(!strncasecmp(charunits,"week",4) || !strcasecmp(charunits,"wk")){ okunit = true; } else if(!strncasecmp(charunits,"month",5) || !strcasecmp(charunits,"mo")){ okunit = true; } else if(!strncasecmp(charunits,"season",6)){ okunit = true; } else if(!strncasecmp(charunits,"year",4) || !strcasecmp(charunits,"yr")){ okunit = true; } if (!okunit) return false; return true; } /* Return true only if this is a "bounds" attribute */ bool_t is_bounds_att(ncatt_t *attp) { if(attp->type == NC_CHAR && attp->valgp && NCSTREQ((char *)attp->name, "bounds")) { return true; } #ifdef USE_NETCDF4 if(attp->type == NC_STRING && attp->valgp && NCSTREQ((char *)attp->name, "bounds")) { return true; } #endif /* USE_NETCDF4 */ return false; } /* Insert info about a bounds attribute into bounds list, so we can * later determine which variables are bounds variables for which * other variables. att must be a variable "bounds" attribute. */ void insert_bounds_info(int ncid, int varid, ncatt_t *attp) { static bool_t uninitialized = true; if(uninitialized) { bounds_list.nbnds = 0; bounds_list.first = NULL; } assert(is_bounds_att(attp)); bounds_add(attp->valgp, ncid, varid); } void get_timeinfo(int ncid1, int varid1, ncvar_t *vp) { ncatt_t uatt; /* units attribute */ int nc_status; /* return from netcdf calls */ char *units; int ncid = ncid1; int varid = varid1; vp->has_timeval = false; /* by default, turn on if criteria met */ vp->timeinfo = 0; vp->is_bnds_var = false; /* for timeinfo, treat a bounds variable like its "parent" time variable */ if(is_bounds_var(vp->name, &ncid, &varid)) { vp->is_bnds_var = true; } /* 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 || 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; } /* check for calendar attribute (not required even for time vars) */ vp->timeinfo = (timeinfo_t *)emalloc(sizeof(timeinfo_t)); memset((void*)vp->timeinfo,0,sizeof(timeinfo_t)); vp->timeinfo->calendar = calendar_type(ncid, varid); /* Parse relative units, returning the unit and base component time. */ if(cdParseRelunits(vp->timeinfo->calendar, units, &vp->timeinfo->unit, &vp->timeinfo->origin) != 0) { /* error parsing units so just treat as not a time variable */ free(vp->timeinfo); free(units); vp->timeinfo = NULL; return; } /* Currently this gets reparsed for every value, need function * like cdRel2Comp that resuses parsed units? */ vp->timeinfo->units = strdup(units); vp->has_timeval = true; free(units); } return; } /* print_att_times * by Dave Allured, NOAA/PSD/CIRES. * This version supports only primitive attribute types; do not call * for user defined types. Print interpreted, human readable (ISO) * time strings for an attribute of a CF-like time variable. * * Print strings as CDL comments, following the normal non-decoded * numeric values, which were already printed by the calling function. * In the following example, this function prints only the right hand * side, starting at the two slashes: * * time:actual_range = 51133., 76670. ; // "1940-01-01", "2009-12-01" * * This function may be called for ALL primitive attributes. * This function qualifies the attribute for numeric type and * inheriting valid time attributes (has_time). If the attribute * does not qualify, this function prints nothing and safely * returns. * * This function interprets and formats time values with the SAME * methods already used in ncdump -t for data variables. * * This version has special line wrapping rules: * * (1) If the attribute has one or two values, the time strings are * always printed on the same line. * * (2) If the attribute has three or more values, the time strings * are always printed on successive lines, with line wrapping * as needed. * * Assume: Preceding call to pr_att_valgs has already screened * this attribute for valid primitive types for the current netcdf * model (netcdf 3 or 4). */ void print_att_times( int ncid, int varid, /* parent var ID */ const ncatt_t *att /* attribute structure */ ) { nc_type type = att->type; /* local copy */ bool_t wrap; bool_t first_item; ncvar_t var; /* fake var structure for the att values; */ /* will add only the minimum necessary info */ /* For common disqualifications, print nothing and return immediately. */ if (type == NC_CHAR || type == NC_STRING) /* must be numeric */ return; if (varid == NC_GLOBAL) /* time units not defined for global atts */ return; assert (att->len > 0); /* should already be eliminated by caller */ #ifdef USE_NETCDF4 assert ( type == NC_BYTE || type == NC_SHORT || type == NC_INT || type == NC_FLOAT || type == NC_DOUBLE || type == NC_UBYTE || type == NC_USHORT || type == NC_UINT || type == NC_INT64 || type == NC_UINT64 ); #else /* NETCDF3 */ assert ( type == NC_BYTE || type == NC_SHORT || type == NC_INT || type == NC_FLOAT || type == NC_DOUBLE ); #endif /* Get time info from parent variable, and qualify. */ memset((void*)&var,0,sizeof(var)); /* clear the fake var structure */ get_timeinfo(ncid, varid, &var); /* sets has_timeval, timeinfo members */ if (var.has_timeval) { /* no print unless time qualified */ /* Convert each value to ISO date/time string, and print. */ size_t iel; /* attrib index */ const char *valp = (const char *)att->valgp; /* attrib value pointer */ safebuf_t *sb = sbuf_new(); /* allocate new string buffer */ #ifdef NOTUSED int func; /* line wrap control */ int separator = ' '; /* default between data and time */ if(formatting_specs.iso_separator) separator = 'T'; #endif var.type = att->type; /* insert attrib type into fake var */ for (iel = 0; iel < att->len; iel++) { nctime_val_tostring(&var, sb, (void *)valp); /* convert to str. */ valp += att->tinfo->size; /* increment value pointer, by type */ if (iel < att->len - 1) /* add comma, except for final value */ sbuf_cat(sb, ","); first_item = (iel == 0); /* identify start of list */ wrap = (att->len > 2); /* specify line wrap variations: */ /* 1 or 2 values: keep on same line, */ /* more than 2: enable line wrap */ lput2 (sbuf_str(sb), first_item, wrap); /* print string in CDL comment, */ /* with auto newline */ } sbuf_free(sb); /* clean up */ if(var.timeinfo->units) /* clean up from get_timeinfo */ free(var.timeinfo->units); free(var.timeinfo); } }