1 /*********************************************************************
2  *   Copyright 2008, University Corporation for Atmospheric Research
3  *   See netcdf/COPYRIGHT file for copying and redistribution conditions.
4  *   $Id: nctime.c,v 1.9 2010/05/05 22:15:39 dmh Exp $
5  *********************************************************************/
6 
7 /*
8  * This code was extracted with permission from the CDMS time
9  * conversion and arithmetic routines developed by Bob Drach, Lawrence
10  * Livermore National Laboratory as part of the cdtime library.  Russ
11  * Rew of the UCAR Unidata Program made changes and additions to
12  * support the "-t" option of the netCDF ncdump utility, including a
13  * 366-day climate calendar.
14  *
15  * For the complete time conversion and climate calendar facilities of
16  * the CDMS library, get the original sources from LLNL.
17  */
18 
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <ctype.h>
22 #include <math.h>
23 #include <string.h>
24 #include <stdarg.h>
25 #include <assert.h>
26 #include <netcdf.h>
27 #include "utils.h"
28 #include "nccomps.h"
29 #include "dumplib.h"		/* for sbuf_... prototypes */
30 #include "ncdump.h"		/* for fspec_t def */
31 #include "nctime0.h"
32 #include "vardata.h"
33 
34 
35 static struct {
36     size_t nbnds;		/* number of bounds variables */
37     bounds_node_t *first;
38 } bounds_list;
39 
40 extern fspec_t formatting_specs; /* set from command-line options */
41 
42 /* rkr: added bounds functions to detect bounds variables of time variables */
43 void
bounds_add(char * bounds_name,int ncid,int varid)44 bounds_add(char *bounds_name, int ncid, int varid) {
45     bounds_node_t *bnode = emalloc(sizeof(bounds_node_t) + 1);
46     bounds_list.nbnds++;
47     bnode->ncid = ncid;
48     bnode->varid = varid;
49     bnode->bounds_name = strdup(bounds_name);
50     bnode->next = bounds_list.first;
51     bounds_list.first = bnode;
52 }
53 
54 /* Check for optional "calendar" attribute and return specified
55  * calendar type, if present. */
56 cdCalenType
calendar_type(int ncid,int varid)57 calendar_type(int ncid, int varid) {
58     int ctype;
59     int stat;
60     ncatt_t catt;
61     static struct {
62 	char* attname;
63 	int type;
64     } calmap[] = {
65 	{"gregorian", cdMixed},
66 	{"standard", cdMixed}, /* synonym */
67 	{"proleptic_gregorian", cdStandard},
68 	{"noleap", cdNoLeap},
69 	{"no_leap", cdNoLeap},
70 	{"365_day", cdNoLeap},	/* synonym */
71 	{"allleap", cd366},
72 	{"all_leap", cd366},	/* synonym */
73 	{"366_day", cd366},	/* synonym */
74 	{"360_day", cd360},
75 	{"julian", cdJulian},
76 	{"none", cdClim}	/* TODO: test this */
77     };
78 #define CF_CAL_ATT_NAME "calendar"
79     int ncals = (sizeof calmap)/(sizeof calmap[0]);
80     ctype = cdMixed;  /* default mixed Gregorian/Julian ala udunits */
81     stat = nc_inq_att(ncid, varid, CF_CAL_ATT_NAME, &catt.type, &catt.len);
82     if(stat == NC_NOERR && catt.type == NC_CHAR && catt.len > 0) {
83 	char *calstr = (char *)emalloc(catt.len + 1);
84 	int itype;
85 	NC_CHECK(nc_get_att(ncid, varid, CF_CAL_ATT_NAME, calstr));
86 	calstr[catt.len] = '\0';
87 	for(itype = 0; itype < ncals; itype++) {
88 	    if(strncmp(calstr, calmap[itype].attname, catt.len) == 0) {
89 		ctype = calmap[itype].type;
90 		break;
91 	    }
92 	}
93 	free(calstr);
94     }
95     return ctype;
96 }
97 
98 bool_t
is_bounds_var(char * varname,int * pargrpidp,int * parvaridp)99 is_bounds_var(char *varname, int *pargrpidp, int *parvaridp) {
100     bounds_node_t *bp = bounds_list.first;
101     for(; bp; bp = bp->next) {
102 	if(NCSTREQ(bp->bounds_name, varname)) {
103 	    *pargrpidp = bp->ncid;
104 	    *parvaridp = bp->varid;
105 	    return true;
106 	}
107     }
108     return false;
109 }
110 
111 /* Test if attribute is of form required by cdtime:
112  *     <time_unit> since <base_time>
113  * where
114  *     <time_unit>:
115  */
116 bool_t
is_valid_time_unit(const char * units)117 is_valid_time_unit(const char *units) {
118 	char charunits[CD_MAX_RELUNITS];
119 	char basetime_1[CD_MAX_CHARTIME];
120 	char basetime_2[CD_MAX_CHARTIME];
121 	int nconv1, nconv2;
122 	bool_t okunit = false;
123 
124 	/* Allow ISO-8601 "T" date-time separator as well as blank separator */
125 	nconv1 = sscanf(units,"%s since %[^T]T%s", charunits, basetime_1, basetime_2);
126 	nconv2 = sscanf(units,"%s since %s %s", charunits, basetime_1, basetime_2);
127 	if (!(nconv1 > 1 || nconv2 > 1))
128 	    return false;
129 	/* Check for unit compatible with cdtime library, no attempt
130 	 * to enforce CF-compliance or udunits compliance here ... */
131 	if(!strncmp(charunits,"sec",3) || !strcmp(charunits,"s")){
132 	    okunit = true;
133 	}
134 	else if(!strncmp(charunits,"min",3) || !strcmp(charunits,"mn")){
135 	    okunit = true;
136 	}
137 	else if(!strncmp(charunits,"hour",4) || !strcmp(charunits,"hr")){
138 	    okunit = true;
139 	}
140 	else if(!strncmp(charunits,"day",3) || !strcmp(charunits,"dy")){
141 	    okunit = true;
142 	}
143 	else if(!strncmp(charunits,"week",4) || !strcmp(charunits,"wk")){
144 	    okunit = true;
145 	}
146 	else if(!strncmp(charunits,"month",5) || !strcmp(charunits,"mo")){
147 	    okunit = true;
148 	}
149 	else if(!strncmp(charunits,"season",6)){
150 	    okunit = true;
151 	}
152 	else if(!strncmp(charunits,"year",4) || !strcmp(charunits,"yr")){
153 	    okunit = true;
154 	}
155 	if (!okunit)
156 	    return false;
157 	return true;
158 }
159 
160 /* Return true only if this is a "bounds" attribute */
161 bool_t
is_bounds_att(ncatt_t * attp)162 is_bounds_att(ncatt_t *attp) {
163     if(attp->type == NC_CHAR && attp->valgp && NCSTREQ((char *)attp->name, "bounds")) {
164 	return true;
165     }
166 #ifdef USE_NETCDF4
167     if(attp->type == NC_STRING && attp->valgp && NCSTREQ((char *)attp->name, "bounds")) {
168 	return true;
169     }
170 #endif /* USE_NETCDF4 */
171     return false;
172 }
173 
174 /* Insert info about a bounds attribute into bounds list, so we can
175  * later determine which variables are bounds variables for which
176  * other variables.  att must be a variable "bounds" attribute.  */
177 void
insert_bounds_info(int ncid,int varid,ncatt_t * attp)178 insert_bounds_info(int ncid, int varid, ncatt_t *attp) {
179     static bool_t uninitialized = true;
180 
181     if(uninitialized) {
182 	bounds_list.nbnds = 0;
183 	bounds_list.first = NULL;
184     }
185     assert(is_bounds_att(attp));
186     bounds_add(attp->valgp, ncid, varid);
187 }
188 
189 void
get_timeinfo(int ncid1,int varid1,ncvar_t * vp)190 get_timeinfo(int ncid1, int varid1, ncvar_t *vp) {
191     ncatt_t uatt;		/* units attribute */
192     int nc_status;		/* return from netcdf calls */
193     char *units;
194     int ncid = ncid1;
195     int varid = varid1;
196 
197     vp->has_timeval = false; /* by default, turn on if criteria met */
198     vp->timeinfo = 0;
199     vp->is_bnds_var = false;
200     /* for timeinfo, treat a bounds variable like its "parent" time variable */
201     if(is_bounds_var(vp->name, &ncid, &varid)) {
202 	vp->is_bnds_var = true;
203     }
204 
205     /* time variables must have appropriate units attribute or be a bounds variable */
206     nc_status = nc_inq_att(ncid, varid, "units", &uatt.type, &uatt.len);
207     if(nc_status == NC_NOERR && uatt.type == NC_CHAR) { /* TODO: NC_STRING? */
208 	units = emalloc(uatt.len + 1);
209 	NC_CHECK(nc_get_att(ncid, varid, "units", units));
210 	units[uatt.len] = '\0';
211 	if(!is_valid_time_unit(units)) {
212 	    free(units);
213 	    return;
214 	}
215 	/* check for calendar attribute (not required even for time vars) */
216 	vp->timeinfo = (timeinfo_t *)emalloc(sizeof(timeinfo_t));
217 	memset((void*)vp->timeinfo,0,sizeof(timeinfo_t));
218 	vp->timeinfo->calendar = calendar_type(ncid, varid);
219 	/* Parse relative units, returning the unit and base component time. */
220  	if(cdParseRelunits(vp->timeinfo->calendar, units,
221 			   &vp->timeinfo->unit, &vp->timeinfo->origin) != 0) {
222 	    /* error parsing units so just treat as not a time variable */
223 	    free(vp->timeinfo);
224 	    free(units);
225 	    vp->timeinfo = NULL;
226 	    return;
227 	}
228 	/* Currently this gets reparsed for every value, need function
229 	 * like cdRel2Comp that resuses parsed units? */
230 	vp->timeinfo->units = strdup(units);
231 	vp->has_timeval = true;
232 	free(units);
233     }
234     return;
235 }
236 
237 /* print_att_times
238  * by Dave Allured, NOAA/PSD/CIRES.
239  * This version supports only primitive attribute types; do not call
240  * for user defined types.  Print interpreted, human readable (ISO)
241  * time strings for an attribute of a CF-like time variable.
242  *
243  * Print strings as CDL comments, following the normal non-decoded
244  * numeric values, which were already printed by the calling function.
245  * In the following example, this function prints only the right hand
246  * side, starting at the two slashes:
247  *
248  *    time:actual_range = 51133., 76670. ; // "1940-01-01", "2009-12-01"
249  *
250  * This function may be called for ALL primitive attributes.
251  * This function qualifies the attribute for numeric type and
252  * inheriting valid time attributes (has_time).  If the attribute
253  * does not qualify, this function prints nothing and safely
254  * returns.
255  *
256  * This function interprets and formats time values with the SAME
257  * methods already used in ncdump -t for data variables.
258  *
259  * This version has special line wrapping rules:
260  *
261  * (1) If the attribute has one or two values, the time strings are
262  *     always printed on the same line.
263  *
264  * (2) If the attribute has three or more values, the time strings
265  *     are always printed on successive lines, with line wrapping
266  *     as needed.
267  *
268  * Assume: Preceding call to pr_att_valgs has already screened
269  * this attribute for valid primitive types for the current netcdf
270  * model (netcdf 3 or 4).
271  */
272 
273 void
print_att_times(int ncid,int varid,const ncatt_t * att)274 print_att_times(
275     int ncid,
276     int varid,			/* parent var ID */
277     const ncatt_t *att		/* attribute structure */
278     )
279 {
280     nc_type type = att->type;	/* local copy */
281     bool_t wrap;
282     bool_t first_item;
283 
284     ncvar_t var;		/* fake var structure for the att values; */
285 				/* will add only the minimum necessary info */
286 
287 /* For common disqualifications, print nothing and return immediately. */
288 
289     if (type == NC_CHAR || type == NC_STRING)	/* must be numeric */
290 	return;
291 
292     if (varid == NC_GLOBAL)	/* time units not defined for global atts */
293 	return;
294 
295     assert (att->len > 0);	/* should already be eliminated by caller */
296 
297 #ifdef USE_NETCDF4
298     assert ( type == NC_BYTE   || type == NC_SHORT  || type == NC_INT
299           || type == NC_FLOAT  || type == NC_DOUBLE || type == NC_UBYTE
300           || type == NC_USHORT || type == NC_UINT   || type == NC_INT64
301           || type == NC_UINT64 );
302 #else   /* NETCDF3 */
303     assert ( type == NC_BYTE   || type == NC_SHORT  || type == NC_INT
304           || type == NC_FLOAT  || type == NC_DOUBLE );
305 #endif
306 
307 /* Get time info from parent variable, and qualify. */
308 
309     memset((void*)&var,0,sizeof(var));	/* clear the fake var structure */
310     get_timeinfo(ncid, varid, &var);	/* sets has_timeval, timeinfo members */
311 
312     if (var.has_timeval) {		/* no print unless time qualified */
313 
314 /* Convert each value to ISO date/time string, and print. */
315 
316 	size_t iel;				     /* attrib index */
317 	const char *valp = (const char *)att->valgp;  /* attrib value pointer */
318 	safebuf_t *sb = sbuf_new();		/* allocate new string buffer */
319 #ifdef NOTUSED
320         int func;				/* line wrap control */
321 	int separator = ' ';			/* default between data and time */
322 	if(formatting_specs.iso_separator)
323 	    separator = 'T';
324 #endif
325 
326 	var.type = att->type;		/* insert attrib type into fake var */
327 
328 	for (iel = 0; iel < att->len; iel++) {
329 	    nctime_val_tostring(&var, sb, (void *)valp);  /* convert to str. */
330 	    valp += att->tinfo->size;	/* increment value pointer, by type */
331 	    if (iel < att->len - 1)	/* add comma, except for final value */
332 		sbuf_cat(sb, ",");
333 
334             first_item = (iel == 0);	/* identify start of list */
335 
336             wrap = (att->len > 2);	/* specify line wrap variations:     */
337 					/* 1 or 2 values: keep on same line, */
338 					/* more than 2: enable line wrap     */
339 
340             lput2 (sbuf_str(sb), first_item, wrap);
341             				/* print string in CDL comment, */
342             				/* with auto newline            */
343 	}
344 
345         sbuf_free(sb);			/* clean up */
346 
347 	if(var.timeinfo->units)		/* clean up from get_timeinfo */
348 	    free(var.timeinfo->units);
349 	free(var.timeinfo);
350     }
351 }
352