Modified rrd_graph
[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_NE,NE)
141           add_op(OP_PREV,PREV)
142           add_op(OP_INF,INF)
143           add_op(OP_ISINF,ISINF)
144           add_op(OP_NOW,NOW)
145           add_op(OP_LTIME,LTIME)
146           add_op(OP_TIME,TIME)
147
148 #undef add_op
149               }
150     (*str)[offset] = '\0';
151
152 }
153
154 short addop2str(enum op_en op, enum op_en op_type, char *op_str, 
155                 char **result_str, unsigned short *offset)
156 {
157     if (op == op_type) {
158         short op_len;
159         op_len = strlen(op_str);
160         *result_str =  (char *) rrd_realloc(*result_str,
161                                             (op_len + 1 + *offset)*sizeof(char));
162         if (*result_str == NULL) {
163             rrd_set_error("failed to alloc memory in addop2str");
164             return -1;
165         }
166         strncpy(&((*result_str)[*offset]),op_str,op_len);
167         *offset += op_len;
168         return 1;
169     }
170     return 0;
171 }
172
173 void parseCDEF_DS(char *def,rrd_t *rrd, int ds_idx)
174 {
175     rpnp_t *rpnp = NULL;
176     rpn_cdefds_t *rpnc = NULL;
177     short count, i;
178     
179     rpnp = rpn_parse((void*) rrd, def, &lookup_DS);
180     if (rpnp == NULL) {
181         rrd_set_error("failed to parse computed data source %s", def);
182         return;
183     }
184     /* Check for OP nodes not permitted in COMPUTE DS.
185      * Moved this check from within rpn_compact() because it really is
186      * COMPUTE DS specific. This is less efficient, but creation doesn't
187      * occur too often. */
188     for (i = 0; rpnp[i].op != OP_END; i++) {
189         if (rpnp[i].op == OP_TIME || rpnp[i].op == OP_LTIME || 
190             rpnp[i].op == OP_PREV)
191         {
192             rrd_set_error(
193                 "operators time, ltime and prev not supported with DS COMPUTE");
194             free(rpnp);
195             return;
196         }
197     }
198     if (rpn_compact(rpnp,&rpnc,&count) == -1) {
199         free(rpnp);
200         return;
201     }
202     /* copy the compact rpn representation over the ds_def par array */
203     memcpy((void*) &(rrd -> ds_def[ds_idx].par[DS_cdef]),
204            (void*) rpnc, count*sizeof(rpn_cdefds_t));
205     free(rpnp);
206     free(rpnc);
207 }
208
209 /* lookup a data source name in the rrd struct and return the index,
210  * should use ds_match() here except:
211  * (1) need a void * pointer to the rrd
212  * (2) error handling is left to the caller
213  */
214 long lookup_DS(void *rrd_vptr,char *ds_name)
215 {
216     int i;
217     rrd_t *rrd; 
218     
219     rrd = (rrd_t *) rrd_vptr;
220     
221     for (i = 0; i < rrd -> stat_head -> ds_cnt; ++i)
222     {
223         if(strcmp(ds_name,rrd -> ds_def[i].ds_nam) == 0)
224             return i;
225     }
226     /* the caller handles a bad data source name in the rpn string */
227     return -1;
228 }
229
230 /* rpn_parse : parse a string and generate a rpnp array; modified
231  * str2rpn() originally included in rrd_graph.c
232  * arguments:
233  * key_hash: a transparent argument passed to lookup(); conceptually this
234  *    is a hash object for lookup of a numeric key given a variable name
235  * expr: the string RPN expression, including variable names
236  * lookup(): a function that retrieves a numeric key given a variable name
237  */
238 rpnp_t * 
239 rpn_parse(void *key_hash,char *expr,long (*lookup)(void *,char*)){
240     int pos=0;
241     long steps=-1;    
242     rpnp_t  *rpnp;
243     char vname[30];
244     
245     rpnp=NULL;
246     
247     while(*expr){
248         if ((rpnp = (rpnp_t *) rrd_realloc(rpnp, (++steps + 2)* 
249                                        sizeof(rpnp_t)))==NULL){
250             return NULL;
251         }
252         
253         else if((sscanf(expr,"%lf%n",&rpnp[steps].val,&pos) == 1) && (expr[pos] == ',')){
254             rpnp[steps].op = OP_NUMBER;
255             expr+=pos;
256         } 
257         
258 #define match_op(VV,VVV) \
259         else if (strncmp(expr, #VVV, strlen(#VVV))==0){ \
260             rpnp[steps].op = VV; \
261             expr+=strlen(#VVV); \
262         }
263
264         match_op(OP_ADD,+)
265         match_op(OP_SUB,-)
266         match_op(OP_MUL,*)
267         match_op(OP_DIV,/)
268         match_op(OP_MOD,%)
269         match_op(OP_SIN,SIN)
270         match_op(OP_COS,COS)
271         match_op(OP_LOG,LOG)
272         match_op(OP_FLOOR,FLOOR)
273         match_op(OP_CEIL,CEIL)
274         match_op(OP_EXP,EXP)
275         match_op(OP_DUP,DUP)
276         match_op(OP_EXC,EXC)
277         match_op(OP_POP,POP)
278         match_op(OP_LT,LT)
279         match_op(OP_LE,LE)
280         match_op(OP_GT,GT)
281         match_op(OP_GE,GE)
282         match_op(OP_EQ,EQ)
283         match_op(OP_IF,IF)
284         match_op(OP_MIN,MIN)
285         match_op(OP_MAX,MAX)
286         match_op(OP_LIMIT,LIMIT)
287           /* order is important here ! .. match longest first */
288         match_op(OP_UNKN,UNKN)
289         match_op(OP_UN,UN)
290         match_op(OP_NEGINF,NEGINF)
291         match_op(OP_NE,NE)
292         match_op(OP_PREV,PREV)
293         match_op(OP_INF,INF)
294         match_op(OP_ISINF,ISINF)
295         match_op(OP_NOW,NOW)
296         match_op(OP_LTIME,LTIME)
297         match_op(OP_TIME,TIME)
298
299 #undef match_op
300
301
302             else if ((sscanf(expr,"%29[_A-Za-z0-9]%n",
303                              vname,&pos) == 1) 
304                      && ((rpnp[steps].ptr = (*lookup)(key_hash,vname)) != -1)){
305                 rpnp[steps].op = OP_VARIABLE;
306                 expr+=pos;
307             }      
308         
309         else {
310             free(rpnp);
311             return NULL;
312         }
313         if (*expr == 0)
314             break;
315         if (*expr == ',')
316             expr++;
317         else {
318             free(rpnp);
319             return NULL;
320         }  
321     }
322     rpnp[steps+1].op = OP_END;
323     return rpnp;
324 }
325
326 void
327 rpnstack_init(rpnstack_t *rpnstack)
328 {
329     rpnstack -> s = NULL;
330     rpnstack -> dc_stacksize = 0;
331     rpnstack -> dc_stackblock = 100;
332 }
333
334 void
335 rpnstack_free(rpnstack_t *rpnstack)
336 {
337    if (rpnstack -> s != NULL)
338           free(rpnstack -> s);
339    rpnstack -> dc_stacksize = 0;
340 }
341
342 /* rpn_calc: run the RPN calculator; also performs variable substitution;
343  * moved and modified from data_calc() originally included in rrd_graph.c 
344  * arguments:
345  * rpnp : an array of RPN operators (including variable references)
346  * rpnstack : the initialized stack
347  * data_idx : when data_idx is a multiple of rpnp.step, the rpnp.data pointer
348  *   is advanced by rpnp.ds_cnt; used only for variable substitution
349  * output : an array of output values; OP_PREV assumes this array contains
350  *   the "previous" value at index position output_idx-1; the definition of
351  *   "previous" depends on the calling environment
352  * output_idx : an index into the output array in which to store the output
353  *   of the RPN calculator
354  * returns: -1 if the computation failed (also calls rrd_set_error)
355  *           0 on success
356  */
357 short
358 rpn_calc(rpnp_t *rpnp, rpnstack_t *rpnstack, long data_idx, 
359                 rrd_value_t *output, int output_idx)
360 {
361     int rpi;
362     long stptr = -1;
363    
364     /* process each op from the rpn in turn */
365     for (rpi=0; rpnp[rpi].op != OP_END; rpi++){
366         /* allocate or grow the stack */
367         if (stptr + 5 > rpnstack -> dc_stacksize){
368             /* could move this to a separate function */
369             rpnstack -> dc_stacksize += rpnstack -> dc_stackblock;              
370             rpnstack -> s = rrd_realloc(rpnstack -> s,
371                         (rpnstack -> dc_stacksize)*sizeof(*(rpnstack -> s)));
372             if (rpnstack -> s == NULL){
373                 rrd_set_error("RPN stack overflow");
374                 return -1;
375             }
376         }
377
378 #define stackunderflow(MINSIZE)                         \
379         if(stptr<MINSIZE){                              \
380             rrd_set_error("RPN stack underflow");       \
381             return -1;                                  \
382         }
383
384         switch (rpnp[rpi].op){
385             case OP_NUMBER:
386                 rpnstack -> s[++stptr] = rpnp[rpi].val;
387                 break;
388             case OP_VARIABLE:
389                 /* Sanity check: VDEFs shouldn't make it here */
390                 if (rpnp[rpi].ds_cnt == 0) {
391                     rrd_set_error("VDEF made it into rpn_calc... aborting");
392                     return -1;
393                 } else {
394                     /* make sure we pull the correct value from
395                      * the *.data array. Adjust the pointer into
396                      * the array acordingly. Advance the ptr one
397                      * row in the rra (skip over non-relevant
398                      * data sources)
399                      */
400                     rpnstack -> s[++stptr] =  *(rpnp[rpi].data);
401                     if (data_idx % rpnp[rpi].step == 0){
402                         rpnp[rpi].data += rpnp[rpi].ds_cnt;
403                     }
404                 }
405                 break;
406             case OP_PREV:
407                 if ((output_idx-1) <= 0) {
408                     rpnstack -> s[++stptr] = DNAN;
409                 } else {
410                     rpnstack -> s[++stptr] = output[output_idx-1];
411                 }
412                 break;
413             case OP_UNKN:
414                 rpnstack -> s[++stptr] = DNAN; 
415                 break;
416             case OP_INF:
417                 rpnstack -> s[++stptr] = DINF; 
418                 break;
419             case OP_NEGINF:
420                 rpnstack -> s[++stptr] = -DINF; 
421                 break;
422             case OP_NOW:
423                 rpnstack -> s[++stptr] = (double)time(NULL);
424                 break;
425             case OP_TIME:
426                 /* HACK: this relies on the data_idx being the time,
427                 ** which the within-function scope is unaware of */
428                 rpnstack -> s[++stptr] = (double) data_idx;
429                 break;
430             case OP_LTIME:
431                 rpnstack -> s[++stptr] =
432                         (double) tzoffset(data_idx) + (double)data_idx;
433                 break;
434             case OP_ADD:
435                 stackunderflow(1);
436                 rpnstack -> s[stptr-1]  = rpnstack -> s[stptr-1] 
437                                         + rpnstack -> s[stptr];
438                 stptr--;
439                 break;
440             case OP_SUB:
441                 stackunderflow(1);
442                 rpnstack -> s[stptr-1]  = rpnstack -> s[stptr-1]
443                                         - rpnstack -> s[stptr];
444                 stptr--;
445                 break;
446             case OP_MUL:
447                 stackunderflow(1);
448                 rpnstack -> s[stptr-1]  = (rpnstack -> s[stptr-1])
449                                         * (rpnstack -> s[stptr]);
450                 stptr--;
451                 break;
452             case OP_DIV:
453                 stackunderflow(1);
454                 rpnstack -> s[stptr-1]  = rpnstack -> s[stptr-1]
455                                         / rpnstack -> s[stptr];
456                 stptr--;
457                 break;
458             case OP_MOD:
459                 stackunderflow(1);
460                 rpnstack -> s[stptr-1]= fmod(   rpnstack -> s[stptr-1]
461                                                 ,rpnstack -> s[stptr]);
462                 stptr--;
463                 break;
464             case OP_SIN:
465                 stackunderflow(0);
466                 rpnstack -> s[stptr] = sin(rpnstack -> s[stptr]);
467                 break;
468             case OP_COS:
469                 stackunderflow(0);
470                 rpnstack -> s[stptr] = cos(rpnstack -> s[stptr]);
471                 break;
472             case OP_CEIL:
473                 stackunderflow(0);
474                 rpnstack -> s[stptr] = ceil(rpnstack -> s[stptr]);
475                 break;
476             case OP_FLOOR:
477                 stackunderflow(0);
478                 rpnstack -> s[stptr] = floor(rpnstack -> s[stptr]);
479                 break;
480             case OP_LOG:
481                 stackunderflow(0);
482                 rpnstack -> s[stptr] = log(rpnstack -> s[stptr]);
483                 break;
484             case OP_DUP:
485                 stackunderflow(0);
486                 rpnstack -> s[stptr+1] = rpnstack -> s[stptr];
487                 stptr++;
488                 break;
489             case OP_POP:
490                 stackunderflow(0);
491                 stptr--;
492                 break;
493             case OP_EXC:
494                 stackunderflow(1);
495                 {
496                     double dummy;
497                     dummy = rpnstack -> s[stptr] ;
498                     rpnstack -> s[stptr] = rpnstack -> s[stptr-1];
499                     rpnstack -> s[stptr-1] = dummy;
500                 }
501                 break;
502             case OP_EXP:
503                 stackunderflow(0);
504                 rpnstack -> s[stptr] = exp(rpnstack -> s[stptr]);
505                 break;
506             case OP_LT:
507                 stackunderflow(1);
508                 if (isnan(rpnstack -> s[stptr-1])) 
509                     ;
510                 else if (isnan(rpnstack -> s[stptr]))
511                     rpnstack -> s[stptr-1] = rpnstack -> s[stptr];
512                 else
513                     rpnstack -> s[stptr-1] = rpnstack -> s[stptr-1] <
514                                         rpnstack -> s[stptr] ? 1.0 : 0.0;
515                 stptr--;
516                 break;
517             case OP_LE:
518                 stackunderflow(1);
519                 if (isnan(rpnstack -> s[stptr-1])) 
520                     ;
521                 else if (isnan(rpnstack -> s[stptr]))
522                     rpnstack -> s[stptr-1] = rpnstack -> s[stptr];
523                 else
524                     rpnstack -> s[stptr-1] = rpnstack -> s[stptr-1] <=
525                                         rpnstack -> s[stptr] ? 1.0 : 0.0;
526                 stptr--;
527                 break;
528             case OP_GT:
529                 stackunderflow(1);
530                 if (isnan(rpnstack -> s[stptr-1])) 
531                     ;
532                 else if (isnan(rpnstack -> s[stptr]))
533                     rpnstack -> s[stptr-1] = rpnstack -> s[stptr];
534                 else
535                     rpnstack -> s[stptr-1] = rpnstack -> s[stptr-1] >
536                                         rpnstack -> s[stptr] ? 1.0 : 0.0;
537                 stptr--;
538                 break;
539             case OP_GE:
540                 stackunderflow(1);
541                 if (isnan(rpnstack -> s[stptr-1])) 
542                     ;
543                 else if (isnan(rpnstack -> s[stptr]))
544                     rpnstack -> s[stptr-1] = rpnstack -> s[stptr];
545                 else
546                     rpnstack -> s[stptr-1] = rpnstack -> s[stptr-1] >=
547                                         rpnstack -> s[stptr] ? 1.0 : 0.0;
548                 stptr--;
549                 break;
550             case OP_NE:
551                 stackunderflow(1);
552                 if (isnan(rpnstack -> s[stptr-1]))
553                     ;
554                 else if (isnan(rpnstack -> s[stptr]))
555                     rpnstack -> s[stptr-1] = rpnstack -> s[stptr];
556                 else
557                     rpnstack -> s[stptr-1] = rpnstack -> s[stptr-1] ==
558                                         rpnstack -> s[stptr] ? 0.0 : 1.0;
559                 stptr--;
560                 break;
561             case OP_EQ:
562                 stackunderflow(1);
563                 if (isnan(rpnstack -> s[stptr-1])) 
564                     ;
565                 else if (isnan(rpnstack -> s[stptr]))
566                     rpnstack -> s[stptr-1] = rpnstack -> s[stptr];
567                 else
568                     rpnstack -> s[stptr-1] = rpnstack -> s[stptr-1] ==
569                                         rpnstack -> s[stptr] ? 1.0 : 0.0;
570                 stptr--;
571                 break;
572             case OP_IF:
573                 stackunderflow(2);
574                 rpnstack->s[stptr-2] = rpnstack->s[stptr-2] != 0.0 ?
575                                 rpnstack->s[stptr-1] : rpnstack->s[stptr];
576                 stptr--;
577                 stptr--;
578                 break;
579             case OP_MIN:
580                 stackunderflow(1);
581                 if (isnan(rpnstack->s[stptr-1])) 
582                     ;
583                 else if (isnan(rpnstack->s[stptr]))
584                     rpnstack->s[stptr-1] = rpnstack->s[stptr];
585                 else if (rpnstack->s[stptr-1] > rpnstack->s[stptr])
586                     rpnstack->s[stptr-1] = rpnstack->s[stptr];
587                 stptr--;
588                 break;
589             case OP_MAX:
590                 stackunderflow(1);
591                 if (isnan(rpnstack->s[stptr-1])) 
592                ;
593                 else if (isnan(rpnstack->s[stptr]))
594                     rpnstack->s[stptr-1] = rpnstack->s[stptr];
595                 else if (rpnstack->s[stptr-1] < rpnstack->s[stptr])
596                     rpnstack->s[stptr-1] = rpnstack->s[stptr];
597                 stptr--;
598                 break;
599             case OP_LIMIT:
600                 stackunderflow(2);
601                 if (isnan(rpnstack->s[stptr-2])) 
602                     ;
603                 else if (isnan(rpnstack->s[stptr-1]))
604                     rpnstack->s[stptr-2] = rpnstack->s[stptr-1];
605                 else if (isnan(rpnstack->s[stptr]))
606                     rpnstack->s[stptr-2] = rpnstack->s[stptr];
607                 else if (rpnstack->s[stptr-2] < rpnstack->s[stptr-1])
608                     rpnstack->s[stptr-2] = DNAN;
609                 else if (rpnstack->s[stptr-2] > rpnstack->s[stptr])
610                     rpnstack->s[stptr-2] = DNAN;
611                 stptr-=2;
612                 break;
613             case OP_UN:
614                 stackunderflow(0);
615                 rpnstack->s[stptr] = isnan(rpnstack->s[stptr]) ? 1.0 : 0.0;
616                 break;
617             case OP_ISINF:
618                 stackunderflow(0);
619                 rpnstack->s[stptr] = isinf(rpnstack->s[stptr]) ? 1.0 : 0.0;
620                 break;
621             case OP_END:
622                 break;
623        }
624 #undef stackunderflow
625    }
626    if(stptr!=0){
627        rrd_set_error("RPN final stack size != 1");
628        return -1;
629    }
630    
631    output[output_idx] = rpnstack->s[0];
632    return 0;
633 }
634
635 /* figure out what the local timezone offset for any point in
636    time was. Return it in seconds */
637 int
638 tzoffset( time_t now ){
639     int gm_sec, gm_min, gm_hour, gm_yday, gm_year,
640         l_sec, l_min, l_hour, l_yday, l_year;
641     struct tm *t;
642     int off;
643     t = gmtime(&now);
644     gm_sec = t->tm_sec;
645     gm_min = t->tm_min;
646     gm_hour = t->tm_hour;
647     gm_yday = t->tm_yday;
648     gm_year = t->tm_year;
649     t = localtime(&now);
650     l_sec = t->tm_sec;
651     l_min = t->tm_min;
652     l_hour = t->tm_hour;
653     l_yday = t->tm_yday;
654     l_year = t->tm_year;
655     off = (l_sec-gm_sec)+(l_min-gm_min)*60+(l_hour-gm_hour)*3600; 
656     if ( l_yday > gm_yday || l_year > gm_year){
657         off += 24*3600;
658     } else if ( l_yday < gm_yday || l_year < gm_year){
659         off -= 24*3600;
660     }
661    return off;
662 }
663