fixed version number and date/time
[rrdtool.git] / src / rrd_rpncalc.c
1 /****************************************************************************
2  * RRDtool 1.0.28  Copyright Tobias Oetiker, 1997 - 2002
3  ****************************************************************************
4  * rrd_rpncalc.c  RPN calculator functions
5  ****************************************************************************/
6
7 #include "rrd_tool.h"
8 #include "rrd_rpncalc.h"
9 #include <limits.h>
10
11 short addop2str(enum op_en op, enum op_en op_type, char *op_str, 
12             char **result_str, unsigned short *offset);
13 int tzoffset(time_t); /* used to implement LTIME */
14
15 short rpn_compact(rpnp_t *rpnp, rpn_cdefds_t **rpnc, short *count)
16 {
17     short i;
18     *count = 0;
19     /* count the number of rpn nodes */
20     while(rpnp[*count].op != OP_END) (*count)++;
21     if (++(*count) > DS_CDEF_MAX_RPN_NODES) {
22         rrd_set_error("Maximum %d RPN nodes permitted",
23                       DS_CDEF_MAX_RPN_NODES);
24         return -1;
25     }
26     
27     /* allocate memory */
28     *rpnc = (rpn_cdefds_t *) calloc(*count,sizeof(rpn_cdefds_t));
29     for (i = 0; rpnp[i].op != OP_END; i++)
30     {
31         (*rpnc)[i].op = (char) rpnp[i].op;
32         if (rpnp[i].op == OP_NUMBER) {
33             /* rpnp.val is a double, rpnc.val is a short */
34             double temp = floor(rpnp[i].val);
35             if (temp < SHRT_MIN || temp > SHRT_MAX) {
36                 rrd_set_error(
37                     "constants must be integers in the interval (%d, %d)",
38                     SHRT_MIN, SHRT_MAX);
39                 free(*rpnc);    
40                 return -1;
41             }
42             (*rpnc)[i].val = (short) temp;
43         } else if (rpnp[i].op == OP_VARIABLE) {
44             (*rpnc)[i].val = (short) rpnp[i].ptr;
45         }
46     }
47     /* terminate the sequence */
48     (*rpnc)[(*count) - 1].op = OP_END;
49     return 0;
50 }
51
52 rpnp_t * rpn_expand(rpn_cdefds_t *rpnc)
53 {
54     short i;
55     rpnp_t *rpnp;
56     
57     /* DS_CDEF_MAX_RPN_NODES is small, so at the expense of some wasted
58      * memory we avoid any reallocs */
59     rpnp = (rpnp_t *) calloc(DS_CDEF_MAX_RPN_NODES,sizeof(rpnp_t));
60     if (rpnp == NULL) return NULL;
61     for (i = 0; rpnc[i].op != OP_END; ++i)
62     {
63         rpnp[i].op = (long) rpnc[i].op;
64         if (rpnp[i].op == OP_NUMBER) {
65             rpnp[i].val = (double) rpnc[i].val;
66         } else if (rpnp[i].op == OP_VARIABLE) {
67             rpnp[i].ptr = (long) rpnc[i].val;
68         }
69     }
70     /* terminate the sequence */
71     rpnp[i].op = OP_END;
72     return rpnp;
73 }
74
75 /* rpn_compact2str: convert a compact sequence of RPN operator nodes back
76  * into a CDEF string. This function is used by rrd_dump.
77  * arguments:
78  *  rpnc: an array of compact RPN operator nodes
79  *  ds_def: a pointer to the data source definition section of an RRD header
80  *   for lookup of data source names by index
81  *  str: out string, memory is allocated by the function, must be freed by the
82  *   the caller */
83 void rpn_compact2str(rpn_cdefds_t *rpnc,ds_def_t *ds_def,char **str)
84 {
85     unsigned short i,offset = 0;
86     char buffer[7]; /* short as a string */
87     
88     for (i = 0; rpnc[i].op != OP_END; i++)
89     {
90         if (i > 0) (*str)[offset++] = ',';
91         
92 #define add_op(VV,VVV) \
93           if (addop2str(rpnc[i].op, VV, VVV, str, &offset) == 1) continue;
94         
95         if (rpnc[i].op == OP_NUMBER) {
96             /* convert a short into a string */
97 #ifdef WIN32
98             _itoa(rpnc[i].val,buffer,10);
99 #else
100             sprintf(buffer,"%d",rpnc[i].val);
101 #endif
102             add_op(OP_NUMBER,buffer)
103                 }
104         
105         if (rpnc[i].op == OP_VARIABLE) {
106             char *ds_name = ds_def[rpnc[i].val].ds_nam;
107             add_op(OP_VARIABLE, ds_name)
108                 }
109 #undef add_op
110         
111 #define add_op(VV,VVV) \
112           if (addop2str(rpnc[i].op, VV, #VVV, str, &offset) == 1) continue;
113         
114           add_op(OP_ADD,+)
115           add_op(OP_SUB,-)
116           add_op(OP_MUL,*)
117           add_op(OP_DIV,/)
118           add_op(OP_MOD,%)
119           add_op(OP_SIN,SIN)
120           add_op(OP_COS,COS)
121           add_op(OP_LOG,LOG)
122           add_op(OP_FLOOR,FLOOR)
123           add_op(OP_CEIL,CEIL)
124           add_op(OP_EXP,EXP)
125           add_op(OP_DUP,DUP)
126           add_op(OP_EXC,EXC)
127           add_op(OP_POP,POP)
128           add_op(OP_LT,LT)
129           add_op(OP_LE,LE)
130           add_op(OP_GT,GT)
131           add_op(OP_GE,GE)
132           add_op(OP_EQ,EQ)
133           add_op(OP_IF,IF)
134           add_op(OP_MIN,MIN)
135           add_op(OP_MAX,MAX)
136           add_op(OP_LIMIT,LIMIT)
137           add_op(OP_UNKN,UNKN)
138           add_op(OP_UN,UN)
139           add_op(OP_NEGINF,NEGINF)
140           add_op(OP_PREV,PREV)
141           add_op(OP_INF,INF)
142           add_op(OP_NOW,NOW)
143           add_op(OP_LTIME,LTIME)
144           add_op(OP_TIME,TIME)
145
146 #undef add_op
147               }
148     (*str)[offset] = '\0';
149
150 }
151
152 short addop2str(enum op_en op, enum op_en op_type, char *op_str, 
153                 char **result_str, unsigned short *offset)
154 {
155     if (op == op_type) {
156         short op_len;
157         op_len = strlen(op_str);
158         *result_str =  (char *) rrd_realloc(*result_str,
159                                             (op_len + 1 + *offset)*sizeof(char));
160         if (*result_str == NULL) {
161             rrd_set_error("failed to alloc memory in addop2str");
162             return -1;
163         }
164         strncpy(&((*result_str)[*offset]),op_str,op_len);
165         *offset += op_len;
166         return 1;
167     }
168     return 0;
169 }
170
171 void parseCDEF_DS(char *def,rrd_t *rrd, int ds_idx)
172 {
173     rpnp_t *rpnp = NULL;
174     rpn_cdefds_t *rpnc = NULL;
175     short count, i;
176     
177     rpnp = rpn_parse((void*) rrd, def, &lookup_DS);
178     if (rpnp == NULL) {
179         rrd_set_error("failed to parse computed data source %s", def);
180         return;
181     }
182     /* Check for OP nodes not permitted in COMPUTE DS.
183      * Moved this check from within rpn_compact() because it really is
184      * COMPUTE DS specific. This is less efficient, but creation doesn't
185      * occur too often. */
186     for (i = 0; rpnp[i].op != OP_END; i++) {
187         if (rpnp[i].op == OP_TIME || rpnp[i].op == OP_LTIME || 
188             rpnp[i].op == OP_PREV)
189         {
190             rrd_set_error(
191                 "operators time, ltime and prev not supported with DS COMPUTE");
192             free(rpnp);
193             return;
194         }
195     }
196     if (rpn_compact(rpnp,&rpnc,&count) == -1) {
197         free(rpnp);
198         return;
199     }
200     /* copy the compact rpn representation over the ds_def par array */
201     memcpy((void*) &(rrd -> ds_def[ds_idx].par[DS_cdef]),
202            (void*) rpnc, count*sizeof(rpn_cdefds_t));
203     free(rpnp);
204     free(rpnc);
205 }
206
207 /* lookup a data source name in the rrd struct and return the index,
208  * should use ds_match() here except:
209  * (1) need a void * pointer to the rrd
210  * (2) error handling is left to the caller
211  */
212 long lookup_DS(void *rrd_vptr,char *ds_name)
213 {
214     int i;
215     rrd_t *rrd; 
216     
217     rrd = (rrd_t *) rrd_vptr;
218     
219     for (i = 0; i < rrd -> stat_head -> ds_cnt; ++i)
220     {
221         if(strcmp(ds_name,rrd -> ds_def[i].ds_nam) == 0)
222             return i;
223     }
224     /* the caller handles a bad data source name in the rpn string */
225     return -1;
226 }
227
228 /* rpn_parse : parse a string and generate a rpnp array; modified
229  * str2rpn() originally included in rrd_graph.c
230  * arguments:
231  * key_hash: a transparent argument passed to lookup(); conceptually this
232  *    is a hash object for lookup of a numeric key given a variable name
233  * expr: the string RPN expression, including variable names
234  * lookup(): a function that retrieves a numeric key given a variable name
235  */
236 rpnp_t * 
237 rpn_parse(void *key_hash,char *expr,long (*lookup)(void *,char*)){
238     int pos=0;
239     long steps=-1;    
240     rpnp_t  *rpnp;
241     char vname[30];
242     
243     rpnp=NULL;
244     
245     while(*expr){
246         if ((rpnp = (rpnp_t *) rrd_realloc(rpnp, (++steps + 2)* 
247                                        sizeof(rpnp_t)))==NULL){
248             return NULL;
249         }
250         
251         else if((sscanf(expr,"%lf%n",&rpnp[steps].val,&pos) == 1) && (expr[pos] == ',')){
252             rpnp[steps].op = OP_NUMBER;
253             expr+=pos;
254         } 
255         
256 #define match_op(VV,VVV) \
257         else if (strncmp(expr, #VVV, strlen(#VVV))==0){ \
258             rpnp[steps].op = VV; \
259             expr+=strlen(#VVV); \
260         }
261
262         match_op(OP_ADD,+)
263         match_op(OP_SUB,-)
264         match_op(OP_MUL,*)
265         match_op(OP_DIV,/)
266         match_op(OP_MOD,%)
267         match_op(OP_SIN,SIN)
268         match_op(OP_COS,COS)
269         match_op(OP_LOG,LOG)
270         match_op(OP_FLOOR,FLOOR)
271         match_op(OP_CEIL,CEIL)
272         match_op(OP_EXP,EXP)
273         match_op(OP_DUP,DUP)
274         match_op(OP_EXC,EXC)
275         match_op(OP_POP,POP)
276         match_op(OP_LT,LT)
277         match_op(OP_LE,LE)
278         match_op(OP_GT,GT)
279         match_op(OP_GE,GE)
280         match_op(OP_EQ,EQ)
281         match_op(OP_IF,IF)
282         match_op(OP_MIN,MIN)
283         match_op(OP_MAX,MAX)
284         match_op(OP_LIMIT,LIMIT)
285           /* order is important here ! .. match longest first */
286         match_op(OP_UNKN,UNKN)
287         match_op(OP_UN,UN)
288         match_op(OP_NEGINF,NEGINF)
289         match_op(OP_PREV,PREV)
290         match_op(OP_INF,INF)
291         match_op(OP_NOW,NOW)
292         match_op(OP_LTIME,LTIME)
293         match_op(OP_TIME,TIME)
294
295 #undef match_op
296
297
298             else if ((sscanf(expr,"%29[_A-Za-z0-9]%n",
299                              vname,&pos) == 1) 
300                      && ((rpnp[steps].ptr = (*lookup)(key_hash,vname)) != -1)){
301                 rpnp[steps].op = OP_VARIABLE;
302                 expr+=pos;
303             }      
304         
305         else {
306             free(rpnp);
307             return NULL;
308         }
309         if (*expr == 0)
310             break;
311         if (*expr == ',')
312             expr++;
313         else {
314             free(rpnp);
315             return NULL;
316         }  
317     }
318     rpnp[steps+1].op = OP_END;
319     return rpnp;
320 }
321
322 void
323 rpnstack_init(rpnstack_t *rpnstack)
324 {
325     rpnstack -> s = NULL;
326     rpnstack -> dc_stacksize = 0;
327     rpnstack -> dc_stackblock = 100;
328 }
329
330 void
331 rpnstack_free(rpnstack_t *rpnstack)
332 {
333    if (rpnstack -> s != NULL)
334           free(rpnstack -> s);
335    rpnstack -> dc_stacksize = 0;
336 }
337
338 /* rpn_calc: run the RPN calculator; also performs variable substitution;
339  * moved and modified from data_calc() originally included in rrd_graph.c 
340  * arguments:
341  * rpnp : an array of RPN operators (including variable references)
342  * rpnstack : the initialized stack
343  * data_idx : when data_idx is a multiple of rpnp.step, the rpnp.data pointer
344  *   is advanced by rpnp.ds_cnt; used only for variable substitution
345  * output : an array of output values; OP_PREV assumes this array contains
346  *   the "previous" value at index position output_idx-1; the definition of
347  *   "previous" depends on the calling environment
348  * output_idx : an index into the output array in which to store the output
349  *   of the RPN calculator
350  * returns: -1 if the computation failed (also calls rrd_set_error)
351  *           0 on success
352  */
353 short
354 rpn_calc(rpnp_t *rpnp, rpnstack_t *rpnstack, long data_idx, 
355                 rrd_value_t *output, int output_idx)
356 {
357     int rpi;
358     long stptr = -1;
359    
360     /* process each op from the rpn in turn */
361     for (rpi=0; rpnp[rpi].op != OP_END; rpi++){
362         /* allocate or grow the stack */
363         if (stptr + 5 > rpnstack -> dc_stacksize){
364             /* could move this to a separate function */
365             rpnstack -> dc_stacksize += rpnstack -> dc_stackblock;              
366             rpnstack -> s = rrd_realloc(rpnstack -> s,
367                         (rpnstack -> dc_stacksize)*sizeof(*(rpnstack -> s)));
368             if (rpnstack -> s == NULL){
369                 rrd_set_error("RPN stack overflow");
370                 return -1;
371             }
372         }
373         switch (rpnp[rpi].op){
374             case OP_NUMBER:
375                 rpnstack -> s[++stptr] = rpnp[rpi].val;
376                 break;
377             case OP_VARIABLE:
378                 /* Sanity check: VDEFs shouldn't make it here */
379                 if (rpnp[rpi].ds_cnt == 0) {
380                     rrd_set_error("VDEF made it into rpn_calc... aborting");
381                     return -1;
382                 } else {
383                     /* make sure we pull the correct value from
384                      * the *.data array. Adjust the pointer into
385                      * the array acordingly. Advance the ptr one
386                      * row in the rra (skip over non-relevant
387                      * data sources)
388                      */
389                     if (data_idx % rpnp[rpi].step == 0){
390                         rpnp[rpi].data += rpnp[rpi].ds_cnt;
391                     }
392                     rpnstack -> s[++stptr] =  *(rpnp[rpi].data);
393                 }
394                 break;
395             case OP_PREV:
396                 if ((output_idx-1) <= 0) {
397                     rpnstack -> s[++stptr] = DNAN;
398                 } else {
399                     rpnstack -> s[++stptr] = output[output_idx-1];
400                 }
401                 break;
402        case OP_UNKN:
403            rpnstack -> s[++stptr] = DNAN; 
404            break;
405        case OP_INF:
406            rpnstack -> s[++stptr] = DINF; 
407            break;
408        case OP_NEGINF:
409            rpnstack -> s[++stptr] = -DINF; 
410            break;
411        case OP_NOW:
412            rpnstack -> s[++stptr] = (double)time(NULL);
413            break;
414        case OP_TIME:
415            /* HACK: this relies on the data_idx being the time,
416             * which the within-function scope is unaware of */
417            rpnstack -> s[++stptr] = (double) data_idx;
418            break;
419        case OP_LTIME:
420            rpnstack -> s[++stptr] = (double) tzoffset(data_idx) + (double)data_idx;
421            break;
422        case OP_ADD:
423            if(stptr<1){
424                rrd_set_error("RPN stack underflow");
425                return -1;
426            }
427            rpnstack -> s[stptr-1] = rpnstack -> s[stptr-1] 
428                + rpnstack -> s[stptr];
429            stptr--;
430            break;
431        case OP_SUB:
432            if(stptr<1){
433                rrd_set_error("RPN stack underflow");
434                return -1;
435            }
436            rpnstack -> s[stptr-1] = rpnstack -> s[stptr-1] - rpnstack -> s[stptr];
437            stptr--;
438            break;
439        case OP_MUL:
440            if(stptr<1){
441                rrd_set_error("RPN stack underflow");
442                return -1;
443            }
444            rpnstack -> s[stptr-1] = (rpnstack -> s[stptr-1]) * (rpnstack -> s[stptr]);
445            stptr--;
446            break;
447        case OP_DIV:
448            if(stptr<1){
449                rrd_set_error("RPN stack underflow");
450                return -1;
451            }
452            rpnstack -> s[stptr-1] = rpnstack -> s[stptr-1] / rpnstack -> s[stptr];
453            stptr--;
454            break;
455        case OP_MOD:
456            if(stptr<1){
457                rrd_set_error("RPN stack underflow");
458                return -1;
459            }
460            rpnstack -> s[stptr-1] = fmod(rpnstack -> s[stptr-1],rpnstack -> s[stptr]);
461            stptr--;
462            break;
463        case OP_SIN:
464            if(stptr<0){
465                rrd_set_error("RPN stack underflow");
466                return -1;
467            }
468            rpnstack -> s[stptr] = sin(rpnstack -> s[stptr]);
469            break;
470        case OP_COS:
471            if(stptr<0){
472                rrd_set_error("RPN stack underflow");
473                return -1;
474            }
475            rpnstack -> s[stptr] = cos(rpnstack -> s[stptr]);
476            break;
477        case OP_CEIL:
478            if(stptr<0){
479                rrd_set_error("RPN stack underflow");
480                return -1;
481            }
482            rpnstack -> s[stptr] = ceil(rpnstack -> s[stptr]);
483            break;
484        case OP_FLOOR:
485            if(stptr<0){
486                rrd_set_error("RPN stack underflow");
487                return -1;
488            }
489            rpnstack -> s[stptr] = floor(rpnstack -> s[stptr]);
490            break;
491        case OP_LOG:
492            if(stptr<0){
493                rrd_set_error("RPN stack underflow");
494                return -1;
495            }
496            rpnstack -> s[stptr] = log(rpnstack -> s[stptr]);
497            break;
498        case OP_DUP:
499            if(stptr<0){
500                rrd_set_error("RPN stack underflow");
501                return -1;
502            }
503            rpnstack -> s[stptr+1] = rpnstack -> s[stptr];
504            stptr++;
505            break;
506        case OP_POP:
507            if(stptr<0){
508                rrd_set_error("RPN stack underflow");
509                return -1;
510            }
511            stptr--;
512            break;
513        case OP_EXC:
514            if(stptr<1){
515                rrd_set_error("RPN stack underflow");
516                return -1;
517            } else {
518                double dummy;
519                dummy = rpnstack -> s[stptr] ;
520                rpnstack -> s[stptr] = rpnstack -> s[stptr-1];
521                rpnstack -> s[stptr-1] = dummy;
522            }
523            break;
524        case OP_EXP:
525            if(stptr<0){
526                rrd_set_error("RPN stack underflow");
527                return -1;
528            }
529            rpnstack -> s[stptr] = exp(rpnstack -> s[stptr]);
530            break;
531        case OP_LT:
532            if(stptr<1){
533                rrd_set_error("RPN stack underflow");
534                return -1;
535            }
536            if (isnan(rpnstack -> s[stptr-1])) 
537                ;
538            else if (isnan(rpnstack -> s[stptr]))
539                rpnstack -> s[stptr-1] = rpnstack -> s[stptr];
540            else
541                rpnstack -> s[stptr-1] = rpnstack -> s[stptr-1] < rpnstack -> s[stptr] ? 1.0 : 0.0;
542            stptr--;
543            break;
544        case OP_LE:
545            if(stptr<1){
546                rrd_set_error("RPN stack underflow");
547                return -1;
548            }
549            if (isnan(rpnstack -> s[stptr-1])) 
550                ;
551            else if (isnan(rpnstack -> s[stptr]))
552                rpnstack -> s[stptr-1] = rpnstack -> s[stptr];
553            else
554                rpnstack -> s[stptr-1] = rpnstack -> s[stptr-1] <= rpnstack -> s[stptr] ? 1.0 : 0.0;
555            stptr--;
556            break;
557        case OP_GT:
558            if(stptr<1){
559                rrd_set_error("RPN stack underflow");
560                return -1;
561            }
562            if (isnan(rpnstack -> s[stptr-1])) 
563                ;
564            else if (isnan(rpnstack -> s[stptr]))
565                rpnstack -> s[stptr-1] = rpnstack -> s[stptr];
566            else
567                rpnstack -> s[stptr-1] = rpnstack -> s[stptr-1] > rpnstack -> s[stptr] ? 1.0 : 0.0;
568            stptr--;
569            break;
570        case OP_GE:
571            if(stptr<1){
572                rrd_set_error("RPN stack underflow");
573                return -1;
574            }
575            if (isnan(rpnstack -> s[stptr-1])) 
576                ;
577            else if (isnan(rpnstack -> s[stptr]))
578                rpnstack -> s[stptr-1] = rpnstack -> s[stptr];
579            else
580                rpnstack -> s[stptr-1] = rpnstack -> s[stptr-1] >= rpnstack -> s[stptr] ? 1.0 : 0.0;
581            stptr--;
582            break;
583        case OP_EQ:
584            if(stptr<1){
585                rrd_set_error("RPN stack underflow");
586                return -1;
587            }
588            if (isnan(rpnstack -> s[stptr-1])) 
589                ;
590            else if (isnan(rpnstack -> s[stptr]))
591                rpnstack -> s[stptr-1] = rpnstack -> s[stptr];
592            else
593                rpnstack -> s[stptr-1] = rpnstack -> s[stptr-1] == rpnstack -> s[stptr] ? 1.0 : 0.0;
594            stptr--;
595            break;
596        case OP_IF:
597            if(stptr<2){
598                rrd_set_error("RPN stack underflow");
599                return -1;
600            }
601            rpnstack->s[stptr-2] = rpnstack->s[stptr-2] != 0.0 ? rpnstack->s[stptr-1] : rpnstack->s[stptr];
602            stptr--;
603            stptr--;
604            break;
605        case OP_MIN:
606            if(stptr<1){
607                rrd_set_error("RPN stack underflow");
608                return -1;
609            }
610            if (isnan(rpnstack->s[stptr-1])) 
611                ;
612            else if (isnan(rpnstack->s[stptr]))
613                rpnstack->s[stptr-1] = rpnstack->s[stptr];
614            else if (rpnstack->s[stptr-1] > rpnstack->s[stptr])
615                rpnstack->s[stptr-1] = rpnstack->s[stptr];
616            stptr--;
617            break;
618        case OP_MAX:
619            if(stptr<1){
620                rrd_set_error("RPN stack underflow");
621                return -1;
622            }
623            if (isnan(rpnstack->s[stptr-1])) 
624                ;
625            else if (isnan(rpnstack->s[stptr]))
626                rpnstack->s[stptr-1] = rpnstack->s[stptr];
627            else if (rpnstack->s[stptr-1] < rpnstack->s[stptr])
628                rpnstack->s[stptr-1] = rpnstack->s[stptr];
629            stptr--;
630            break;
631        case OP_LIMIT:
632            if(stptr<2){
633                rrd_set_error("RPN stack underflow");
634                free(rpnstack->s);
635                return -1;
636            }
637            if (isnan(rpnstack->s[stptr-2])) 
638                ;
639            else if (isnan(rpnstack->s[stptr-1]))
640                rpnstack->s[stptr-2] = rpnstack->s[stptr-1];
641            else if (isnan(rpnstack->s[stptr]))
642                rpnstack->s[stptr-2] = rpnstack->s[stptr];
643            else if (rpnstack->s[stptr-2] < rpnstack->s[stptr-1])
644                rpnstack->s[stptr-2] = DNAN;
645            else if (rpnstack->s[stptr-2] > rpnstack->s[stptr])
646                rpnstack->s[stptr-2] = DNAN;
647            stptr-=2;
648            break;
649        case OP_UN:
650            if(stptr<0){
651                rrd_set_error("RPN stack underflow");
652                return -1;
653            }
654            rpnstack->s[stptr] = isnan(rpnstack->s[stptr]) ? 1.0 : 0.0;
655            break;
656        case OP_END:
657            break;
658        }
659    }
660    if(stptr!=0){
661        rrd_set_error("RPN final stack size != 1");
662        return -1;
663    }
664    
665    output[output_idx] = rpnstack->s[0];
666    return 0;
667 }
668
669 /* figure out what the local timezone offset for any point in
670    time was. Return it in seconds */
671 int
672 tzoffset( time_t now ){
673     int gm_sec, gm_min, gm_hour, gm_yday, gm_year,
674         l_sec, l_min, l_hour, l_yday, l_year;
675     struct tm *t;
676     int off;
677     t = gmtime(&now);
678     gm_sec = t->tm_sec;
679     gm_min = t->tm_min;
680     gm_hour = t->tm_hour;
681     gm_yday = t->tm_yday;
682     gm_year = t->tm_year;
683     t = localtime(&now);
684     l_sec = t->tm_sec;
685     l_min = t->tm_min;
686     l_hour = t->tm_hour;
687     l_yday = t->tm_yday;
688     l_year = t->tm_year;
689     off = (l_sec-gm_sec)+(l_min-gm_min)*60+(l_hour-gm_hour)*3600; 
690     if ( l_yday > gm_yday || l_year > gm_year){
691         off += 24*3600;
692     } else if ( l_yday < gm_yday || l_year < gm_year){
693         off -= 24*3600;
694     }
695    return off;
696 }
697