]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/newsyslog/ptimes.c
nvi: import version 2.2.1
[FreeBSD/FreeBSD.git] / usr.sbin / newsyslog / ptimes.c
1 /*-
2  * ------+---------+---------+---------+---------+---------+---------+---------*
3  * Initial version of parse8601 was originally added to newsyslog.c in
4  *     FreeBSD on Jan 22, 1999 by Garrett Wollman <wollman@FreeBSD.org>.
5  * Initial version of parseDWM was originally added to newsyslog.c in
6  *     FreeBSD on Apr  4, 2000 by Hellmuth Michaelis <hm@FreeBSD.org>.
7  *
8  * Copyright (c) 2003  - Garance Alistair Drosehn <gad@FreeBSD.org>.
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  *   1. Redistributions of source code must retain the above copyright
15  *      notice, this list of conditions and the following disclaimer.
16  *   2. Redistributions in binary form must reproduce the above copyright
17  *      notice, this list of conditions and the following disclaimer in the
18  *      documentation and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  * The views and conclusions contained in the software and documentation
33  * are those of the authors and should not be interpreted as representing
34  * official policies, either expressed or implied, of the FreeBSD Project.
35  *
36  * ------+---------+---------+---------+---------+---------+---------+---------*
37  * This is intended to be a set of general-purpose routines to process times.
38  * Right now it probably still has a number of assumptions in it, such that
39  * it works fine for newsyslog but might not work for other uses.
40  * ------+---------+---------+---------+---------+---------+---------+---------*
41  */
42
43 #include <sys/cdefs.h>
44 #include <ctype.h>
45 #include <limits.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <time.h>
50
51 #include "extern.h"
52
53 #define SECS_PER_HOUR   3600
54
55 /*
56  * Bit-values which indicate which components of time were specified
57  * by the string given to parse8601 or parseDWM.  These are needed to
58  * calculate what time-in-the-future will match that string.
59  */
60 #define TSPEC_YEAR              0x0001
61 #define TSPEC_MONTHOFYEAR       0x0002
62 #define TSPEC_LDAYOFMONTH       0x0004
63 #define TSPEC_DAYOFMONTH        0x0008
64 #define TSPEC_DAYOFWEEK         0x0010
65 #define TSPEC_HOUROFDAY         0x0020
66
67 #define TNYET_ADJ4DST           -10     /* DST has "not yet" been adjusted */
68
69 struct ptime_data {
70         time_t           basesecs;      /* Base point for relative times */
71         time_t           tsecs;         /* Time in seconds */
72         struct tm        basetm;        /* Base Time expanded into fields */
73         struct tm        tm;            /* Time expanded into fields */
74         int              did_adj4dst;   /* Track calls to ptime_adjust4dst */
75         int              parseopts;     /* Options given for parsing */
76         int              tmspec;        /* Indicates which time fields had
77                                          * been specified by the user */
78 };
79
80 static int       days_pmonth(int month, int year);
81 static int       parse8601(struct ptime_data *ptime, const char *str);
82 static int       parseDWM(struct ptime_data *ptime, const char *str);
83
84 /*
85  * Simple routine to calculate the number of days in a given month.
86  */
87 static int
88 days_pmonth(int month, int year)
89 {
90         static const int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31,
91             30, 31, 30, 31};
92         int ndays;
93
94         ndays = mtab[month];
95
96         if (month == 1) {
97                 /*
98                  * We are usually called with a 'tm-year' value
99                  * (ie, the value = the number of years past 1900).
100                  */
101                 if (year < 1900)
102                         year += 1900;
103                 if (year % 4 == 0) {
104                         /*
105                          * This is a leap year, as long as it is not a
106                          * multiple of 100, or if it is a multiple of
107                          * both 100 and 400.
108                          */
109                         if (year % 100 != 0)
110                                 ndays++;        /* not multiple of 100 */
111                         else if (year % 400 == 0)
112                                 ndays++;        /* is multiple of 100 and 400 */
113                 }
114         }
115         return (ndays);
116 }
117
118 /*-
119  * Parse a limited subset of ISO 8601. The specific format is as follows:
120  *
121  * [CC[YY[MM[DD]]]][THH[MM[SS]]]        (where `T' is the literal letter)
122  *
123  * We don't accept a timezone specification; missing fields (including timezone)
124  * are defaulted to the current date but time zero.
125  */
126 static int
127 parse8601(struct ptime_data *ptime, const char *s)
128 {
129         char *t;
130         long l;
131         struct tm tm;
132
133         l = strtol(s, &t, 10);
134         if (l < 0 || l >= INT_MAX || (*t != '\0' && *t != 'T'))
135                 return (-1);
136
137         /*
138          * Now t points either to the end of the string (if no time was
139          * provided) or to the letter `T' which separates date and time in
140          * ISO 8601.  The pointer arithmetic is the same for either case.
141          */
142         tm = ptime->tm;
143         ptime->tmspec = TSPEC_HOUROFDAY;
144         switch (t - s) {
145         case 8:
146                 tm.tm_year = ((l / 1000000) - 19) * 100;
147                 l = l % 1000000;
148                 /* FALLTHROUGH */
149         case 6:
150                 ptime->tmspec |= TSPEC_YEAR;
151                 tm.tm_year -= tm.tm_year % 100;
152                 tm.tm_year += l / 10000;
153                 l = l % 10000;
154                 /* FALLTHROUGH */
155         case 4:
156                 ptime->tmspec |= TSPEC_MONTHOFYEAR;
157                 tm.tm_mon = (l / 100) - 1;
158                 l = l % 100;
159                 /* FALLTHROUGH */
160         case 2:
161                 ptime->tmspec |= TSPEC_DAYOFMONTH;
162                 tm.tm_mday = l;
163                 /* FALLTHROUGH */
164         case 0:
165                 break;
166         default:
167                 return (-1);
168         }
169
170         /* sanity check */
171         if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
172             || tm.tm_mday < 1 || tm.tm_mday > 31)
173                 return (-1);
174
175         if (*t != '\0') {
176                 s = ++t;
177                 l = strtol(s, &t, 10);
178                 if (l < 0 || l >= INT_MAX || (*t != '\0' && !isspace(*t)))
179                         return (-1);
180
181                 switch (t - s) {
182                 case 6:
183                         tm.tm_sec = l % 100;
184                         l /= 100;
185                         /* FALLTHROUGH */
186                 case 4:
187                         tm.tm_min = l % 100;
188                         l /= 100;
189                         /* FALLTHROUGH */
190                 case 2:
191                         ptime->tmspec |= TSPEC_HOUROFDAY;
192                         tm.tm_hour = l;
193                         /* FALLTHROUGH */
194                 case 0:
195                         break;
196                 default:
197                         return (-1);
198                 }
199
200                 /* sanity check */
201                 if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
202                     || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
203                         return (-1);
204         }
205
206         ptime->tm = tm;
207         return (0);
208 }
209
210 /*-
211  * Parse a cyclic time specification, the format is as follows:
212  *
213  *      [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
214  *
215  * to rotate a logfile cyclic at
216  *
217  *      - every day (D) within a specific hour (hh)     (hh = 0...23)
218  *      - once a week (W) at a specific day (d)     OR  (d = 0..6, 0 = Sunday)
219  *      - once a month (M) at a specific day (d)        (d = 1..31,l|L)
220  *
221  * We don't accept a timezone specification; missing fields
222  * are defaulted to the current date but time zero.
223  */
224 static int
225 parseDWM(struct ptime_data *ptime, const char *s)
226 {
227         int daysmon, Dseen, WMseen;
228         const char *endval;
229         char *tmp;
230         long l;
231         struct tm tm;
232
233         /* Save away the number of days in this month */
234         tm = ptime->tm;
235         daysmon = days_pmonth(tm.tm_mon, tm.tm_year);
236
237         WMseen = Dseen = 0;
238         ptime->tmspec = TSPEC_HOUROFDAY;
239         for (;;) {
240                 endval = NULL;
241                 switch (*s) {
242                 case 'D':
243                         if (Dseen)
244                                 return (-1);
245                         Dseen++;
246                         ptime->tmspec |= TSPEC_HOUROFDAY;
247                         s++;
248                         l = strtol(s, &tmp, 10);
249                         if (l < 0 || l > 23)
250                                 return (-1);
251                         endval = tmp;
252                         tm.tm_hour = l;
253                         break;
254
255                 case 'W':
256                         if (WMseen)
257                                 return (-1);
258                         WMseen++;
259                         ptime->tmspec |= TSPEC_DAYOFWEEK;
260                         s++;
261                         l = strtol(s, &tmp, 10);
262                         if (l < 0 || l > 6)
263                                 return (-1);
264                         endval = tmp;
265                         if (l != tm.tm_wday) {
266                                 int save;
267
268                                 if (l < tm.tm_wday) {
269                                         save = 6 - tm.tm_wday;
270                                         save += (l + 1);
271                                 } else {
272                                         save = l - tm.tm_wday;
273                                 }
274
275                                 tm.tm_mday += save;
276
277                                 if (tm.tm_mday > daysmon) {
278                                         tm.tm_mon++;
279                                         tm.tm_mday = tm.tm_mday - daysmon;
280                                         if (tm.tm_mon >= 12) {
281                                                 tm.tm_mon = 0;
282                                                 tm.tm_year++;
283                                         }
284                                 }
285                         }
286                         break;
287
288                 case 'M':
289                         if (WMseen)
290                                 return (-1);
291                         WMseen++;
292                         ptime->tmspec |= TSPEC_DAYOFMONTH;
293                         s++;
294                         if (tolower(*s) == 'l') {
295                                 /* User wants the last day of the month. */
296                                 ptime->tmspec |= TSPEC_LDAYOFMONTH;
297                                 tm.tm_mday = daysmon;
298                                 endval = s + 1;
299                         } else {
300                                 l = strtol(s, &tmp, 10);
301                                 if (l < 1 || l > 31)
302                                         return (-1);
303
304                                 if (l > daysmon)
305                                         return (-1);
306                                 endval = tmp;
307                                 tm.tm_mday = l;
308                         }
309                         break;
310
311                 default:
312                         return (-1);
313                         break;
314                 }
315
316                 if (endval == NULL)
317                         return (-1);
318                 else if (*endval == '\0' || isspace(*endval))
319                         break;
320                 else
321                         s = endval;
322         }
323
324         ptime->tm = tm;
325         return (0);
326 }
327
328 /*
329  * Initialize a new ptime-related data area.
330  */
331 struct ptime_data *
332 ptime_init(const struct ptime_data *optsrc)
333 {
334         struct ptime_data *newdata;
335
336         newdata = malloc(sizeof(struct ptime_data));
337         if (optsrc != NULL) {
338                 memcpy(newdata, optsrc, sizeof(struct ptime_data));
339         } else {
340                 memset(newdata, '\0', sizeof(struct ptime_data));
341                 newdata->did_adj4dst = TNYET_ADJ4DST;
342         }
343
344         return (newdata);
345 }
346
347 /*
348  * Adjust a given time if that time is in a different timezone than
349  * some other time.
350  */
351 int
352 ptime_adjust4dst(struct ptime_data *ptime, const struct ptime_data *dstsrc)
353 {
354         struct ptime_data adjtime;
355
356         if (ptime == NULL)
357                 return (-1);
358
359         /*
360          * Changes are not made to the given time until after all
361          * of the calculations have been successful.
362          */
363         adjtime = *ptime;
364
365         /* Check to see if this adjustment was already made */
366         if ((adjtime.did_adj4dst != TNYET_ADJ4DST) &&
367             (adjtime.did_adj4dst == dstsrc->tm.tm_isdst))
368                 return (0);             /* yes, so don't make it twice */
369
370         /* See if daylight-saving has changed between the two times. */
371         if (dstsrc->tm.tm_isdst != adjtime.tm.tm_isdst) {
372                 if (adjtime.tm.tm_isdst == 1)
373                         adjtime.tsecs -= SECS_PER_HOUR;
374                 else if (adjtime.tm.tm_isdst == 0)
375                         adjtime.tsecs += SECS_PER_HOUR;
376                 adjtime.tm = *(localtime(&adjtime.tsecs));
377                 /* Remember that this adjustment has been made */
378                 adjtime.did_adj4dst = dstsrc->tm.tm_isdst;
379                 /*
380                  * XXX - Should probably check to see if changing the
381                  *      hour also changed the value of is_dst.  What
382                  *      should we do in that case?
383                  */
384         }
385
386         *ptime = adjtime;
387         return (0);
388 }
389
390 int
391 ptime_relparse(struct ptime_data *ptime, int parseopts, time_t basetime,
392     const char *str)
393 {
394         int dpm, pres;
395         struct tm temp_tm;
396
397         ptime->parseopts = parseopts;
398         ptime->basesecs = basetime;
399         ptime->basetm = *(localtime(&ptime->basesecs));
400         ptime->tm = ptime->basetm;
401         ptime->tm.tm_hour = ptime->tm.tm_min = ptime->tm.tm_sec = 0;
402
403         /*
404          * Call a routine which sets ptime.tm and ptime.tspecs based
405          * on the given string and parsing-options.  Note that the
406          * routine should not call mktime to set ptime.tsecs.
407          */
408         if (parseopts & PTM_PARSE_DWM)
409                 pres = parseDWM(ptime, str);
410         else
411                 pres = parse8601(ptime, str);
412         if (pres < 0) {
413                 ptime->tsecs = (time_t)pres;
414                 return (pres);
415         }
416
417         /*
418          * Before calling mktime, check to see if we ended up with a
419          * "day-of-month" that does not exist in the selected month.
420          * If we did call mktime with that info, then mktime will
421          * make it look like the user specifically requested a day
422          * in the following month (eg: Feb 31 turns into Mar 3rd).
423          */
424         dpm = days_pmonth(ptime->tm.tm_mon, ptime->tm.tm_year);
425         if ((parseopts & PTM_PARSE_MATCHDOM) &&
426             (ptime->tmspec & TSPEC_DAYOFMONTH) &&
427             (ptime->tm.tm_mday> dpm)) {
428                 /*
429                  * ptime_nxtime() will want a ptime->tsecs value,
430                  * but we need to avoid mktime resetting all the
431                  * ptime->tm values.
432                  */
433                 if (verbose && dbg_at_times > 1)
434                         fprintf(stderr,
435                             "\t-- dom fixed: %4d/%02d/%02d %02d:%02d (%02d)",
436                             ptime->tm.tm_year, ptime->tm.tm_mon,
437                             ptime->tm.tm_mday, ptime->tm.tm_hour,
438                             ptime->tm.tm_min, dpm);
439                 temp_tm = ptime->tm;
440                 ptime->tsecs = mktime(&temp_tm);
441                 if (ptime->tsecs > (time_t)-1)
442                         ptimeset_nxtime(ptime);
443                 if (verbose && dbg_at_times > 1)
444                         fprintf(stderr,
445                             " to: %4d/%02d/%02d %02d:%02d\n",
446                             ptime->tm.tm_year, ptime->tm.tm_mon,
447                             ptime->tm.tm_mday, ptime->tm.tm_hour,
448                             ptime->tm.tm_min);
449         }
450
451         /*
452          * Convert the ptime.tm into standard time_t seconds.  Check
453          * for invalid times, which includes things like the hour lost
454          * when switching from "standard time" to "daylight saving".
455          */
456         ptime->tsecs = mktime(&ptime->tm);
457         if (ptime->tsecs == (time_t)-1) {
458                 ptime->tsecs = (time_t)-2;
459                 return (-2);
460         }
461
462         return (0);
463 }
464
465 int
466 ptime_free(struct ptime_data *ptime)
467 {
468
469         if (ptime == NULL)
470                 return (-1);
471
472         free(ptime);
473         return (0);
474 }
475
476 /*
477  * Some trivial routines so ptime_data can remain a completely
478  * opaque type.
479  */
480 const char *
481 ptimeget_ctime(const struct ptime_data *ptime)
482 {
483
484         if (ptime == NULL)
485                 return ("Null time in ptimeget_ctime()\n");
486
487         return (ctime(&ptime->tsecs));
488 }
489
490 /*
491  * Generate a time of day string in an RFC5424 compatible format. Return a
492  * pointer to the buffer with the timestamp string or NULL if an error. If the
493  * time is not supplied, cannot be converted to local time, or the resulting
494  * string would overflow the buffer, the returned string will be the RFC5424
495  * NILVALUE.
496  */
497 char *
498 ptimeget_ctime_rfc5424(const struct ptime_data *ptime,
499     char *timebuf, size_t bufsize)
500 {
501         static const char NILVALUE[] = {"-"};   /* RFC5424 specified NILVALUE */
502         int chars;
503         struct tm tm;
504         int tz_hours;
505         int tz_mins;
506         long tz_offset;
507         char tz_sign;
508
509         if (timebuf == NULL) {
510                 return (NULL);
511         }
512
513         if (bufsize < sizeof(NILVALUE)) {
514                 return (NULL);
515         }
516
517         /*
518          * Convert to localtime. RFC5424 mandates the use of the NILVALUE if
519          * the time cannot be obtained, so use that if there is an error in the
520          * conversion.
521          */
522         if (ptime == NULL || localtime_r(&(ptime->tsecs), &tm) == NULL) {
523                 strlcpy(timebuf, NILVALUE, bufsize);
524                 return (timebuf);
525         }
526
527         /*
528          * Convert the time to a string in RFC5424 format. The conversion
529          * cannot be done with strftime() because it cannot produce the correct
530          * timezone offset format.
531          */
532         if (tm.tm_gmtoff < 0) {
533                 tz_sign = '-';
534                 tz_offset = -tm.tm_gmtoff;
535         } else {
536                 tz_sign = '+';
537                 tz_offset = tm.tm_gmtoff;
538         }
539
540         tz_hours = tz_offset / 3600;
541         tz_mins = (tz_offset % 3600) / 60;
542
543         chars = snprintf(timebuf, bufsize,
544             "%04d-%02d-%02d"    /* date */
545             "T%02d:%02d:%02d"   /* time */
546             "%c%02d:%02d",      /* time zone offset */
547             tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
548             tm.tm_hour, tm.tm_min, tm.tm_sec,
549             tz_sign, tz_hours, tz_mins);
550
551         /* If the timestamp is too big for timebuf, return the NILVALUE. */
552         if (chars >= (int)bufsize) {
553                 strlcpy(timebuf, NILVALUE, bufsize);
554         }
555
556         return (timebuf);
557 }
558
559 double
560 ptimeget_diff(const struct ptime_data *minuend, const struct
561     ptime_data *subtrahend)
562 {
563
564         /* Just like difftime(), we have no good error-return */
565         if (minuend == NULL || subtrahend == NULL)
566                 return (0.0);
567
568         return (difftime(minuend->tsecs, subtrahend->tsecs));
569 }
570
571 time_t
572 ptimeget_secs(const struct ptime_data *ptime)
573 {
574
575         if (ptime == NULL)
576                 return (-1);
577
578         return (ptime->tsecs);
579 }
580
581 /*
582  * Generate an approximate timestamp for the next event, based on
583  * what parts of time were specified by the original parameter to
584  * ptime_relparse(). The result may be -1 if there is no obvious
585  * "next time" which will work.
586  */
587 int
588 ptimeset_nxtime(struct ptime_data *ptime)
589 {
590         int moredays, tdpm, tmon, tyear;
591         struct ptime_data nextmatch;
592
593         if (ptime == NULL)
594                 return (-1);
595
596         /*
597          * Changes are not made to the given time until after all
598          * of the calculations have been successful.
599          */
600         nextmatch = *ptime;
601         /*
602          * If the user specified a year and we're already past that
603          * time, then there will never be another one!
604          */
605         if (ptime->tmspec & TSPEC_YEAR)
606                 return (-1);
607
608         /*
609          * The caller gave us a time in the past.  Calculate how much
610          * time is needed to go from that valid rotate time to the
611          * next valid rotate time.  We only need to get to the nearest
612          * hour, because newsyslog is only run once per hour.
613          */
614         moredays = 0;
615         if (ptime->tmspec & TSPEC_MONTHOFYEAR) {
616                 /* Special case: Feb 29th does not happen every year. */
617                 if (ptime->tm.tm_mon == 1 && ptime->tm.tm_mday == 29) {
618                         nextmatch.tm.tm_year += 4;
619                         if (days_pmonth(1, nextmatch.tm.tm_year) < 29)
620                                 nextmatch.tm.tm_year += 4;
621                 } else {
622                         nextmatch.tm.tm_year += 1;
623                 }
624                 nextmatch.tm.tm_isdst = -1;
625                 nextmatch.tsecs = mktime(&nextmatch.tm);
626
627         } else if (ptime->tmspec & TSPEC_LDAYOFMONTH) {
628                 /*
629                  * Need to get to the last day of next month.  Origtm is
630                  * already at the last day of this month, so just add to
631                  * it number of days in the next month.
632                  */
633                 if (ptime->tm.tm_mon < 11)
634                         moredays = days_pmonth(ptime->tm.tm_mon + 1,
635                             ptime->tm.tm_year);
636                 else
637                         moredays = days_pmonth(0, ptime->tm.tm_year + 1);
638
639         } else if (ptime->tmspec & TSPEC_DAYOFMONTH) {
640                 /* Jump to the same day in the next month */
641                 moredays = days_pmonth(ptime->tm.tm_mon, ptime->tm.tm_year);
642                 /*
643                  * In some cases, the next month may not *have* the
644                  * desired day-of-the-month.  If that happens, then
645                  * move to the next month that does have enough days.
646                  */
647                 tmon = ptime->tm.tm_mon;
648                 tyear = ptime->tm.tm_year;
649                 for (;;) {
650                         if (tmon < 11)
651                                 tmon += 1;
652                         else {
653                                 tmon = 0;
654                                 tyear += 1;
655                         }
656                         tdpm = days_pmonth(tmon, tyear);
657                         if (tdpm >= ptime->tm.tm_mday)
658                                 break;
659                         moredays += tdpm;
660                 }
661
662         } else if (ptime->tmspec & TSPEC_DAYOFWEEK) {
663                 moredays = 7;
664         } else if (ptime->tmspec & TSPEC_HOUROFDAY) {
665                 moredays = 1;
666         }
667
668         if (moredays != 0) {
669                 nextmatch.tsecs += SECS_PER_HOUR * 24 * moredays;
670                 nextmatch.tm = *(localtime(&nextmatch.tsecs));
671         }
672
673         /*
674          * The new time will need to be adjusted if the setting of
675          * daylight-saving has changed between the two times.
676          */
677         ptime_adjust4dst(&nextmatch, ptime);
678
679         /* Everything worked.  Update the given time and return. */
680         *ptime = nextmatch;
681         return (0);
682 }
683
684 int
685 ptimeset_time(struct ptime_data *ptime, time_t secs)
686 {
687
688         if (ptime == NULL)
689                 return (-1);
690
691         ptime->tsecs = secs;
692         ptime->tm = *(localtime(&ptime->tsecs));
693         ptime->parseopts = 0;
694         /* ptime->tmspec = ? */
695         return (0);
696 }