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