12f8d7c56SGarance A Drosehn /*-
22f8d7c56SGarance A Drosehn * ------+---------+---------+---------+---------+---------+---------+---------*
32f8d7c56SGarance A Drosehn * Initial version of parse8601 was originally added to newsyslog.c in
42f8d7c56SGarance A Drosehn * FreeBSD on Jan 22, 1999 by Garrett Wollman <wollman@FreeBSD.org>.
52f8d7c56SGarance A Drosehn * Initial version of parseDWM was originally added to newsyslog.c in
62f8d7c56SGarance A Drosehn * FreeBSD on Apr 4, 2000 by Hellmuth Michaelis <hm@FreeBSD.org>.
72f8d7c56SGarance A Drosehn *
82f8d7c56SGarance A Drosehn * Copyright (c) 2003 - Garance Alistair Drosehn <gad@FreeBSD.org>.
92f8d7c56SGarance A Drosehn * All rights reserved.
102f8d7c56SGarance A Drosehn *
112f8d7c56SGarance A Drosehn * Redistribution and use in source and binary forms, with or without
122f8d7c56SGarance A Drosehn * modification, are permitted provided that the following conditions
132f8d7c56SGarance A Drosehn * are met:
142f8d7c56SGarance A Drosehn * 1. Redistributions of source code must retain the above copyright
152f8d7c56SGarance A Drosehn * notice, this list of conditions and the following disclaimer.
162f8d7c56SGarance A Drosehn * 2. Redistributions in binary form must reproduce the above copyright
172f8d7c56SGarance A Drosehn * notice, this list of conditions and the following disclaimer in the
182f8d7c56SGarance A Drosehn * documentation and/or other materials provided with the distribution.
192f8d7c56SGarance A Drosehn *
202f8d7c56SGarance A Drosehn * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
212f8d7c56SGarance A Drosehn * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
222f8d7c56SGarance A Drosehn * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
232f8d7c56SGarance A Drosehn * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
242f8d7c56SGarance A Drosehn * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
252f8d7c56SGarance A Drosehn * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
262f8d7c56SGarance A Drosehn * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
272f8d7c56SGarance A Drosehn * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
282f8d7c56SGarance A Drosehn * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
292f8d7c56SGarance A Drosehn * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
302f8d7c56SGarance A Drosehn * SUCH DAMAGE.
312f8d7c56SGarance A Drosehn *
322f8d7c56SGarance A Drosehn * The views and conclusions contained in the software and documentation
332f8d7c56SGarance A Drosehn * are those of the authors and should not be interpreted as representing
342f8d7c56SGarance A Drosehn * official policies, either expressed or implied, of the FreeBSD Project.
352f8d7c56SGarance A Drosehn *
362f8d7c56SGarance A Drosehn * ------+---------+---------+---------+---------+---------+---------+---------*
376a1485e2SGarance A Drosehn * This is intended to be a set of general-purpose routines to process times.
386a1485e2SGarance A Drosehn * Right now it probably still has a number of assumptions in it, such that
396a1485e2SGarance A Drosehn * it works fine for newsyslog but might not work for other uses.
406a1485e2SGarance A Drosehn * ------+---------+---------+---------+---------+---------+---------+---------*
412f8d7c56SGarance A Drosehn */
422f8d7c56SGarance A Drosehn
432f8d7c56SGarance A Drosehn #include <sys/cdefs.h>
442f8d7c56SGarance A Drosehn #include <ctype.h>
452f8d7c56SGarance A Drosehn #include <limits.h>
462f8d7c56SGarance A Drosehn #include <stdio.h>
472f8d7c56SGarance A Drosehn #include <stdlib.h>
486a1485e2SGarance A Drosehn #include <string.h>
492f8d7c56SGarance A Drosehn #include <time.h>
502f8d7c56SGarance A Drosehn
512f8d7c56SGarance A Drosehn #include "extern.h"
522f8d7c56SGarance A Drosehn
536a1485e2SGarance A Drosehn #define SECS_PER_HOUR 3600
546a1485e2SGarance A Drosehn
556a1485e2SGarance A Drosehn /*
566a1485e2SGarance A Drosehn * Bit-values which indicate which components of time were specified
576a1485e2SGarance A Drosehn * by the string given to parse8601 or parseDWM. These are needed to
586a1485e2SGarance A Drosehn * calculate what time-in-the-future will match that string.
596a1485e2SGarance A Drosehn */
606a1485e2SGarance A Drosehn #define TSPEC_YEAR 0x0001
616a1485e2SGarance A Drosehn #define TSPEC_MONTHOFYEAR 0x0002
626a1485e2SGarance A Drosehn #define TSPEC_LDAYOFMONTH 0x0004
636a1485e2SGarance A Drosehn #define TSPEC_DAYOFMONTH 0x0008
646a1485e2SGarance A Drosehn #define TSPEC_DAYOFWEEK 0x0010
656a1485e2SGarance A Drosehn #define TSPEC_HOUROFDAY 0x0020
666a1485e2SGarance A Drosehn
676a1485e2SGarance A Drosehn #define TNYET_ADJ4DST -10 /* DST has "not yet" been adjusted */
686a1485e2SGarance A Drosehn
696a1485e2SGarance A Drosehn struct ptime_data {
706a1485e2SGarance A Drosehn time_t basesecs; /* Base point for relative times */
716a1485e2SGarance A Drosehn time_t tsecs; /* Time in seconds */
726a1485e2SGarance A Drosehn struct tm basetm; /* Base Time expanded into fields */
736a1485e2SGarance A Drosehn struct tm tm; /* Time expanded into fields */
746a1485e2SGarance A Drosehn int did_adj4dst; /* Track calls to ptime_adjust4dst */
756a1485e2SGarance A Drosehn int parseopts; /* Options given for parsing */
766a1485e2SGarance A Drosehn int tmspec; /* Indicates which time fields had
776a1485e2SGarance A Drosehn * been specified by the user */
786a1485e2SGarance A Drosehn };
796a1485e2SGarance A Drosehn
80526d55a4SGarance A Drosehn static int days_pmonth(int month, int year);
816a1485e2SGarance A Drosehn static int parse8601(struct ptime_data *ptime, const char *str);
826a1485e2SGarance A Drosehn static int parseDWM(struct ptime_data *ptime, const char *str);
83526d55a4SGarance A Drosehn
84526d55a4SGarance A Drosehn /*
85526d55a4SGarance A Drosehn * Simple routine to calculate the number of days in a given month.
86526d55a4SGarance A Drosehn */
87526d55a4SGarance A Drosehn static int
days_pmonth(int month,int year)88526d55a4SGarance A Drosehn days_pmonth(int month, int year)
89526d55a4SGarance A Drosehn {
90526d55a4SGarance A Drosehn static const int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31,
91526d55a4SGarance A Drosehn 30, 31, 30, 31};
92526d55a4SGarance A Drosehn int ndays;
93526d55a4SGarance A Drosehn
94526d55a4SGarance A Drosehn ndays = mtab[month];
95526d55a4SGarance A Drosehn
96526d55a4SGarance A Drosehn if (month == 1) {
97526d55a4SGarance A Drosehn /*
98526d55a4SGarance A Drosehn * We are usually called with a 'tm-year' value
99526d55a4SGarance A Drosehn * (ie, the value = the number of years past 1900).
100526d55a4SGarance A Drosehn */
101526d55a4SGarance A Drosehn if (year < 1900)
102526d55a4SGarance A Drosehn year += 1900;
103526d55a4SGarance A Drosehn if (year % 4 == 0) {
104526d55a4SGarance A Drosehn /*
105526d55a4SGarance A Drosehn * This is a leap year, as long as it is not a
106526d55a4SGarance A Drosehn * multiple of 100, or if it is a multiple of
107526d55a4SGarance A Drosehn * both 100 and 400.
108526d55a4SGarance A Drosehn */
109526d55a4SGarance A Drosehn if (year % 100 != 0)
110526d55a4SGarance A Drosehn ndays++; /* not multiple of 100 */
111526d55a4SGarance A Drosehn else if (year % 400 == 0)
112526d55a4SGarance A Drosehn ndays++; /* is multiple of 100 and 400 */
113526d55a4SGarance A Drosehn }
114526d55a4SGarance A Drosehn }
115526d55a4SGarance A Drosehn return (ndays);
116526d55a4SGarance A Drosehn }
117526d55a4SGarance A Drosehn
1182f8d7c56SGarance A Drosehn /*-
1192f8d7c56SGarance A Drosehn * Parse a limited subset of ISO 8601. The specific format is as follows:
1202f8d7c56SGarance A Drosehn *
1212f8d7c56SGarance A Drosehn * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter)
1222f8d7c56SGarance A Drosehn *
1232f8d7c56SGarance A Drosehn * We don't accept a timezone specification; missing fields (including timezone)
1242f8d7c56SGarance A Drosehn * are defaulted to the current date but time zero.
1252f8d7c56SGarance A Drosehn */
1266a1485e2SGarance A Drosehn static int
parse8601(struct ptime_data * ptime,const char * s)1276a1485e2SGarance A Drosehn parse8601(struct ptime_data *ptime, const char *s)
1282f8d7c56SGarance A Drosehn {
1292f8d7c56SGarance A Drosehn char *t;
1302f8d7c56SGarance A Drosehn long l;
1316a1485e2SGarance A Drosehn struct tm tm;
1322f8d7c56SGarance A Drosehn
1332f8d7c56SGarance A Drosehn l = strtol(s, &t, 10);
1342f8d7c56SGarance A Drosehn if (l < 0 || l >= INT_MAX || (*t != '\0' && *t != 'T'))
1352f8d7c56SGarance A Drosehn return (-1);
1362f8d7c56SGarance A Drosehn
1372f8d7c56SGarance A Drosehn /*
1382f8d7c56SGarance A Drosehn * Now t points either to the end of the string (if no time was
1392f8d7c56SGarance A Drosehn * provided) or to the letter `T' which separates date and time in
1402f8d7c56SGarance A Drosehn * ISO 8601. The pointer arithmetic is the same for either case.
1412f8d7c56SGarance A Drosehn */
1426a1485e2SGarance A Drosehn tm = ptime->tm;
1436a1485e2SGarance A Drosehn ptime->tmspec = TSPEC_HOUROFDAY;
1442f8d7c56SGarance A Drosehn switch (t - s) {
1452f8d7c56SGarance A Drosehn case 8:
1462f8d7c56SGarance A Drosehn tm.tm_year = ((l / 1000000) - 19) * 100;
1472f8d7c56SGarance A Drosehn l = l % 1000000;
1484c8f6471SMark Johnston /* FALLTHROUGH */
1492f8d7c56SGarance A Drosehn case 6:
1506a1485e2SGarance A Drosehn ptime->tmspec |= TSPEC_YEAR;
1512f8d7c56SGarance A Drosehn tm.tm_year -= tm.tm_year % 100;
1522f8d7c56SGarance A Drosehn tm.tm_year += l / 10000;
1532f8d7c56SGarance A Drosehn l = l % 10000;
1544c8f6471SMark Johnston /* FALLTHROUGH */
1552f8d7c56SGarance A Drosehn case 4:
1566a1485e2SGarance A Drosehn ptime->tmspec |= TSPEC_MONTHOFYEAR;
1572f8d7c56SGarance A Drosehn tm.tm_mon = (l / 100) - 1;
1582f8d7c56SGarance A Drosehn l = l % 100;
1594c8f6471SMark Johnston /* FALLTHROUGH */
1602f8d7c56SGarance A Drosehn case 2:
1616a1485e2SGarance A Drosehn ptime->tmspec |= TSPEC_DAYOFMONTH;
1622f8d7c56SGarance A Drosehn tm.tm_mday = l;
1634c8f6471SMark Johnston /* FALLTHROUGH */
1642f8d7c56SGarance A Drosehn case 0:
1652f8d7c56SGarance A Drosehn break;
1662f8d7c56SGarance A Drosehn default:
1672f8d7c56SGarance A Drosehn return (-1);
1682f8d7c56SGarance A Drosehn }
1692f8d7c56SGarance A Drosehn
1702f8d7c56SGarance A Drosehn /* sanity check */
1712f8d7c56SGarance A Drosehn if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
1722f8d7c56SGarance A Drosehn || tm.tm_mday < 1 || tm.tm_mday > 31)
1732f8d7c56SGarance A Drosehn return (-1);
1742f8d7c56SGarance A Drosehn
1752f8d7c56SGarance A Drosehn if (*t != '\0') {
1762f8d7c56SGarance A Drosehn s = ++t;
1772f8d7c56SGarance A Drosehn l = strtol(s, &t, 10);
1782f8d7c56SGarance A Drosehn if (l < 0 || l >= INT_MAX || (*t != '\0' && !isspace(*t)))
1792f8d7c56SGarance A Drosehn return (-1);
1802f8d7c56SGarance A Drosehn
1812f8d7c56SGarance A Drosehn switch (t - s) {
1822f8d7c56SGarance A Drosehn case 6:
1832f8d7c56SGarance A Drosehn tm.tm_sec = l % 100;
1842f8d7c56SGarance A Drosehn l /= 100;
1854c8f6471SMark Johnston /* FALLTHROUGH */
1862f8d7c56SGarance A Drosehn case 4:
1872f8d7c56SGarance A Drosehn tm.tm_min = l % 100;
1882f8d7c56SGarance A Drosehn l /= 100;
1894c8f6471SMark Johnston /* FALLTHROUGH */
1902f8d7c56SGarance A Drosehn case 2:
1916a1485e2SGarance A Drosehn ptime->tmspec |= TSPEC_HOUROFDAY;
1922f8d7c56SGarance A Drosehn tm.tm_hour = l;
1934c8f6471SMark Johnston /* FALLTHROUGH */
1942f8d7c56SGarance A Drosehn case 0:
1952f8d7c56SGarance A Drosehn break;
1962f8d7c56SGarance A Drosehn default:
1972f8d7c56SGarance A Drosehn return (-1);
1982f8d7c56SGarance A Drosehn }
1992f8d7c56SGarance A Drosehn
2002f8d7c56SGarance A Drosehn /* sanity check */
2012f8d7c56SGarance A Drosehn if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
2022f8d7c56SGarance A Drosehn || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
2032f8d7c56SGarance A Drosehn return (-1);
2042f8d7c56SGarance A Drosehn }
2052f8d7c56SGarance A Drosehn
2066a1485e2SGarance A Drosehn ptime->tm = tm;
2076a1485e2SGarance A Drosehn return (0);
2082f8d7c56SGarance A Drosehn }
2092f8d7c56SGarance A Drosehn
2102f8d7c56SGarance A Drosehn /*-
2112f8d7c56SGarance A Drosehn * Parse a cyclic time specification, the format is as follows:
2122f8d7c56SGarance A Drosehn *
2132f8d7c56SGarance A Drosehn * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
2142f8d7c56SGarance A Drosehn *
2152f8d7c56SGarance A Drosehn * to rotate a logfile cyclic at
2162f8d7c56SGarance A Drosehn *
2172f8d7c56SGarance A Drosehn * - every day (D) within a specific hour (hh) (hh = 0...23)
2182f8d7c56SGarance A Drosehn * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday)
2192f8d7c56SGarance A Drosehn * - once a month (M) at a specific day (d) (d = 1..31,l|L)
2202f8d7c56SGarance A Drosehn *
2212f8d7c56SGarance A Drosehn * We don't accept a timezone specification; missing fields
2222f8d7c56SGarance A Drosehn * are defaulted to the current date but time zero.
2232f8d7c56SGarance A Drosehn */
2246a1485e2SGarance A Drosehn static int
parseDWM(struct ptime_data * ptime,const char * s)2256a1485e2SGarance A Drosehn parseDWM(struct ptime_data *ptime, const char *s)
2262f8d7c56SGarance A Drosehn {
2276a1485e2SGarance A Drosehn int daysmon, Dseen, WMseen;
228c3033287SGarance A Drosehn const char *endval;
229c3033287SGarance A Drosehn char *tmp;
2302f8d7c56SGarance A Drosehn long l;
2316a1485e2SGarance A Drosehn struct tm tm;
2322f8d7c56SGarance A Drosehn
233526d55a4SGarance A Drosehn /* Save away the number of days in this month */
2346a1485e2SGarance A Drosehn tm = ptime->tm;
235526d55a4SGarance A Drosehn daysmon = days_pmonth(tm.tm_mon, tm.tm_year);
2362f8d7c56SGarance A Drosehn
2376a1485e2SGarance A Drosehn WMseen = Dseen = 0;
2386a1485e2SGarance A Drosehn ptime->tmspec = TSPEC_HOUROFDAY;
2392f8d7c56SGarance A Drosehn for (;;) {
240c3033287SGarance A Drosehn endval = NULL;
2412f8d7c56SGarance A Drosehn switch (*s) {
2422f8d7c56SGarance A Drosehn case 'D':
2432f8d7c56SGarance A Drosehn if (Dseen)
2442f8d7c56SGarance A Drosehn return (-1);
2452f8d7c56SGarance A Drosehn Dseen++;
2466a1485e2SGarance A Drosehn ptime->tmspec |= TSPEC_HOUROFDAY;
2472f8d7c56SGarance A Drosehn s++;
248c3033287SGarance A Drosehn l = strtol(s, &tmp, 10);
2492f8d7c56SGarance A Drosehn if (l < 0 || l > 23)
2502f8d7c56SGarance A Drosehn return (-1);
251c3033287SGarance A Drosehn endval = tmp;
2522f8d7c56SGarance A Drosehn tm.tm_hour = l;
2532f8d7c56SGarance A Drosehn break;
2542f8d7c56SGarance A Drosehn
2552f8d7c56SGarance A Drosehn case 'W':
2562f8d7c56SGarance A Drosehn if (WMseen)
2572f8d7c56SGarance A Drosehn return (-1);
2582f8d7c56SGarance A Drosehn WMseen++;
2596a1485e2SGarance A Drosehn ptime->tmspec |= TSPEC_DAYOFWEEK;
2602f8d7c56SGarance A Drosehn s++;
261c3033287SGarance A Drosehn l = strtol(s, &tmp, 10);
2622f8d7c56SGarance A Drosehn if (l < 0 || l > 6)
2632f8d7c56SGarance A Drosehn return (-1);
264c3033287SGarance A Drosehn endval = tmp;
2652f8d7c56SGarance A Drosehn if (l != tm.tm_wday) {
2662f8d7c56SGarance A Drosehn int save;
2672f8d7c56SGarance A Drosehn
2682f8d7c56SGarance A Drosehn if (l < tm.tm_wday) {
2692f8d7c56SGarance A Drosehn save = 6 - tm.tm_wday;
2702f8d7c56SGarance A Drosehn save += (l + 1);
2712f8d7c56SGarance A Drosehn } else {
2722f8d7c56SGarance A Drosehn save = l - tm.tm_wday;
2732f8d7c56SGarance A Drosehn }
2742f8d7c56SGarance A Drosehn
2752f8d7c56SGarance A Drosehn tm.tm_mday += save;
2762f8d7c56SGarance A Drosehn
277526d55a4SGarance A Drosehn if (tm.tm_mday > daysmon) {
2782f8d7c56SGarance A Drosehn tm.tm_mon++;
279526d55a4SGarance A Drosehn tm.tm_mday = tm.tm_mday - daysmon;
280*b7b447fdSGarance A Drosehn if (tm.tm_mon >= 12) {
281*b7b447fdSGarance A Drosehn tm.tm_mon = 0;
282*b7b447fdSGarance A Drosehn tm.tm_year++;
283*b7b447fdSGarance A Drosehn }
2842f8d7c56SGarance A Drosehn }
2852f8d7c56SGarance A Drosehn }
2862f8d7c56SGarance A Drosehn break;
2872f8d7c56SGarance A Drosehn
2882f8d7c56SGarance A Drosehn case 'M':
2892f8d7c56SGarance A Drosehn if (WMseen)
2902f8d7c56SGarance A Drosehn return (-1);
2912f8d7c56SGarance A Drosehn WMseen++;
2926a1485e2SGarance A Drosehn ptime->tmspec |= TSPEC_DAYOFMONTH;
2932f8d7c56SGarance A Drosehn s++;
2942f8d7c56SGarance A Drosehn if (tolower(*s) == 'l') {
2956a1485e2SGarance A Drosehn /* User wants the last day of the month. */
2966a1485e2SGarance A Drosehn ptime->tmspec |= TSPEC_LDAYOFMONTH;
297526d55a4SGarance A Drosehn tm.tm_mday = daysmon;
298c3033287SGarance A Drosehn endval = s + 1;
2992f8d7c56SGarance A Drosehn } else {
300c3033287SGarance A Drosehn l = strtol(s, &tmp, 10);
3012f8d7c56SGarance A Drosehn if (l < 1 || l > 31)
3022f8d7c56SGarance A Drosehn return (-1);
3032f8d7c56SGarance A Drosehn
304526d55a4SGarance A Drosehn if (l > daysmon)
3052f8d7c56SGarance A Drosehn return (-1);
306c3033287SGarance A Drosehn endval = tmp;
3072f8d7c56SGarance A Drosehn tm.tm_mday = l;
3082f8d7c56SGarance A Drosehn }
3092f8d7c56SGarance A Drosehn break;
3102f8d7c56SGarance A Drosehn
3112f8d7c56SGarance A Drosehn default:
3122f8d7c56SGarance A Drosehn return (-1);
3132f8d7c56SGarance A Drosehn break;
3142f8d7c56SGarance A Drosehn }
3152f8d7c56SGarance A Drosehn
316c3033287SGarance A Drosehn if (endval == NULL)
317c3033287SGarance A Drosehn return (-1);
318c3033287SGarance A Drosehn else if (*endval == '\0' || isspace(*endval))
3192f8d7c56SGarance A Drosehn break;
3202f8d7c56SGarance A Drosehn else
321c3033287SGarance A Drosehn s = endval;
3222f8d7c56SGarance A Drosehn }
3232f8d7c56SGarance A Drosehn
3246a1485e2SGarance A Drosehn ptime->tm = tm;
3256a1485e2SGarance A Drosehn return (0);
3266a1485e2SGarance A Drosehn }
3276a1485e2SGarance A Drosehn
3282f8d7c56SGarance A Drosehn /*
3296a1485e2SGarance A Drosehn * Initialize a new ptime-related data area.
3302f8d7c56SGarance A Drosehn */
3316a1485e2SGarance A Drosehn struct ptime_data *
ptime_init(const struct ptime_data * optsrc)3326a1485e2SGarance A Drosehn ptime_init(const struct ptime_data *optsrc)
3336a1485e2SGarance A Drosehn {
3346a1485e2SGarance A Drosehn struct ptime_data *newdata;
3356a1485e2SGarance A Drosehn
3366a1485e2SGarance A Drosehn newdata = malloc(sizeof(struct ptime_data));
3376a1485e2SGarance A Drosehn if (optsrc != NULL) {
3386a1485e2SGarance A Drosehn memcpy(newdata, optsrc, sizeof(struct ptime_data));
3396a1485e2SGarance A Drosehn } else {
3406a1485e2SGarance A Drosehn memset(newdata, '\0', sizeof(struct ptime_data));
3416a1485e2SGarance A Drosehn newdata->did_adj4dst = TNYET_ADJ4DST;
3426a1485e2SGarance A Drosehn }
3436a1485e2SGarance A Drosehn
3446a1485e2SGarance A Drosehn return (newdata);
3456a1485e2SGarance A Drosehn }
3466a1485e2SGarance A Drosehn
3476a1485e2SGarance A Drosehn /*
3486a1485e2SGarance A Drosehn * Adjust a given time if that time is in a different timezone than
3496a1485e2SGarance A Drosehn * some other time.
3506a1485e2SGarance A Drosehn */
3516a1485e2SGarance A Drosehn int
ptime_adjust4dst(struct ptime_data * ptime,const struct ptime_data * dstsrc)3526a1485e2SGarance A Drosehn ptime_adjust4dst(struct ptime_data *ptime, const struct ptime_data *dstsrc)
3536a1485e2SGarance A Drosehn {
3546a1485e2SGarance A Drosehn struct ptime_data adjtime;
3556a1485e2SGarance A Drosehn
3566a1485e2SGarance A Drosehn if (ptime == NULL)
3576a1485e2SGarance A Drosehn return (-1);
3586a1485e2SGarance A Drosehn
3596a1485e2SGarance A Drosehn /*
3606a1485e2SGarance A Drosehn * Changes are not made to the given time until after all
3616a1485e2SGarance A Drosehn * of the calculations have been successful.
3626a1485e2SGarance A Drosehn */
3636a1485e2SGarance A Drosehn adjtime = *ptime;
3646a1485e2SGarance A Drosehn
3656a1485e2SGarance A Drosehn /* Check to see if this adjustment was already made */
3666a1485e2SGarance A Drosehn if ((adjtime.did_adj4dst != TNYET_ADJ4DST) &&
3676a1485e2SGarance A Drosehn (adjtime.did_adj4dst == dstsrc->tm.tm_isdst))
3686a1485e2SGarance A Drosehn return (0); /* yes, so don't make it twice */
3696a1485e2SGarance A Drosehn
3706a1485e2SGarance A Drosehn /* See if daylight-saving has changed between the two times. */
3716a1485e2SGarance A Drosehn if (dstsrc->tm.tm_isdst != adjtime.tm.tm_isdst) {
3726a1485e2SGarance A Drosehn if (adjtime.tm.tm_isdst == 1)
3736a1485e2SGarance A Drosehn adjtime.tsecs -= SECS_PER_HOUR;
3746a1485e2SGarance A Drosehn else if (adjtime.tm.tm_isdst == 0)
3756a1485e2SGarance A Drosehn adjtime.tsecs += SECS_PER_HOUR;
3766a1485e2SGarance A Drosehn adjtime.tm = *(localtime(&adjtime.tsecs));
3776a1485e2SGarance A Drosehn /* Remember that this adjustment has been made */
3786a1485e2SGarance A Drosehn adjtime.did_adj4dst = dstsrc->tm.tm_isdst;
3796a1485e2SGarance A Drosehn /*
3806a1485e2SGarance A Drosehn * XXX - Should probably check to see if changing the
3816a1485e2SGarance A Drosehn * hour also changed the value of is_dst. What
3826a1485e2SGarance A Drosehn * should we do in that case?
3836a1485e2SGarance A Drosehn */
3846a1485e2SGarance A Drosehn }
3856a1485e2SGarance A Drosehn
3866a1485e2SGarance A Drosehn *ptime = adjtime;
3876a1485e2SGarance A Drosehn return (0);
3886a1485e2SGarance A Drosehn }
3896a1485e2SGarance A Drosehn
3906a1485e2SGarance A Drosehn int
ptime_relparse(struct ptime_data * ptime,int parseopts,time_t basetime,const char * str)3916a1485e2SGarance A Drosehn ptime_relparse(struct ptime_data *ptime, int parseopts, time_t basetime,
3926a1485e2SGarance A Drosehn const char *str)
3936a1485e2SGarance A Drosehn {
3946a1485e2SGarance A Drosehn int dpm, pres;
3956a1485e2SGarance A Drosehn struct tm temp_tm;
3966a1485e2SGarance A Drosehn
3976a1485e2SGarance A Drosehn ptime->parseopts = parseopts;
3986a1485e2SGarance A Drosehn ptime->basesecs = basetime;
3996a1485e2SGarance A Drosehn ptime->basetm = *(localtime(&ptime->basesecs));
4006a1485e2SGarance A Drosehn ptime->tm = ptime->basetm;
4016a1485e2SGarance A Drosehn ptime->tm.tm_hour = ptime->tm.tm_min = ptime->tm.tm_sec = 0;
4026a1485e2SGarance A Drosehn
4036a1485e2SGarance A Drosehn /*
4046a1485e2SGarance A Drosehn * Call a routine which sets ptime.tm and ptime.tspecs based
4056a1485e2SGarance A Drosehn * on the given string and parsing-options. Note that the
4066a1485e2SGarance A Drosehn * routine should not call mktime to set ptime.tsecs.
4076a1485e2SGarance A Drosehn */
4086a1485e2SGarance A Drosehn if (parseopts & PTM_PARSE_DWM)
4096a1485e2SGarance A Drosehn pres = parseDWM(ptime, str);
4106a1485e2SGarance A Drosehn else
4116a1485e2SGarance A Drosehn pres = parse8601(ptime, str);
4126a1485e2SGarance A Drosehn if (pres < 0) {
4136a1485e2SGarance A Drosehn ptime->tsecs = (time_t)pres;
4146a1485e2SGarance A Drosehn return (pres);
4156a1485e2SGarance A Drosehn }
4166a1485e2SGarance A Drosehn
4176a1485e2SGarance A Drosehn /*
4186a1485e2SGarance A Drosehn * Before calling mktime, check to see if we ended up with a
4196a1485e2SGarance A Drosehn * "day-of-month" that does not exist in the selected month.
4206a1485e2SGarance A Drosehn * If we did call mktime with that info, then mktime will
4216a1485e2SGarance A Drosehn * make it look like the user specifically requested a day
4226a1485e2SGarance A Drosehn * in the following month (eg: Feb 31 turns into Mar 3rd).
4236a1485e2SGarance A Drosehn */
4246a1485e2SGarance A Drosehn dpm = days_pmonth(ptime->tm.tm_mon, ptime->tm.tm_year);
4256a1485e2SGarance A Drosehn if ((parseopts & PTM_PARSE_MATCHDOM) &&
4266a1485e2SGarance A Drosehn (ptime->tmspec & TSPEC_DAYOFMONTH) &&
4276a1485e2SGarance A Drosehn (ptime->tm.tm_mday> dpm)) {
4286a1485e2SGarance A Drosehn /*
4296a1485e2SGarance A Drosehn * ptime_nxtime() will want a ptime->tsecs value,
4306a1485e2SGarance A Drosehn * but we need to avoid mktime resetting all the
4316a1485e2SGarance A Drosehn * ptime->tm values.
4326a1485e2SGarance A Drosehn */
4336a1485e2SGarance A Drosehn if (verbose && dbg_at_times > 1)
4346a1485e2SGarance A Drosehn fprintf(stderr,
4356a1485e2SGarance A Drosehn "\t-- dom fixed: %4d/%02d/%02d %02d:%02d (%02d)",
4366a1485e2SGarance A Drosehn ptime->tm.tm_year, ptime->tm.tm_mon,
4376a1485e2SGarance A Drosehn ptime->tm.tm_mday, ptime->tm.tm_hour,
4386a1485e2SGarance A Drosehn ptime->tm.tm_min, dpm);
4396a1485e2SGarance A Drosehn temp_tm = ptime->tm;
4406a1485e2SGarance A Drosehn ptime->tsecs = mktime(&temp_tm);
4416a1485e2SGarance A Drosehn if (ptime->tsecs > (time_t)-1)
4426a1485e2SGarance A Drosehn ptimeset_nxtime(ptime);
4436a1485e2SGarance A Drosehn if (verbose && dbg_at_times > 1)
4446a1485e2SGarance A Drosehn fprintf(stderr,
4456a1485e2SGarance A Drosehn " to: %4d/%02d/%02d %02d:%02d\n",
4466a1485e2SGarance A Drosehn ptime->tm.tm_year, ptime->tm.tm_mon,
4476a1485e2SGarance A Drosehn ptime->tm.tm_mday, ptime->tm.tm_hour,
4486a1485e2SGarance A Drosehn ptime->tm.tm_min);
4496a1485e2SGarance A Drosehn }
4506a1485e2SGarance A Drosehn
4516a1485e2SGarance A Drosehn /*
4526a1485e2SGarance A Drosehn * Convert the ptime.tm into standard time_t seconds. Check
4536a1485e2SGarance A Drosehn * for invalid times, which includes things like the hour lost
4546a1485e2SGarance A Drosehn * when switching from "standard time" to "daylight saving".
4556a1485e2SGarance A Drosehn */
4566a1485e2SGarance A Drosehn ptime->tsecs = mktime(&ptime->tm);
4576a1485e2SGarance A Drosehn if (ptime->tsecs == (time_t)-1) {
4586a1485e2SGarance A Drosehn ptime->tsecs = (time_t)-2;
4596a1485e2SGarance A Drosehn return (-2);
4606a1485e2SGarance A Drosehn }
4616a1485e2SGarance A Drosehn
4626a1485e2SGarance A Drosehn return (0);
4636a1485e2SGarance A Drosehn }
4646a1485e2SGarance A Drosehn
4656a1485e2SGarance A Drosehn int
ptime_free(struct ptime_data * ptime)4666a1485e2SGarance A Drosehn ptime_free(struct ptime_data *ptime)
4676a1485e2SGarance A Drosehn {
4686a1485e2SGarance A Drosehn
4696a1485e2SGarance A Drosehn if (ptime == NULL)
4706a1485e2SGarance A Drosehn return (-1);
4716a1485e2SGarance A Drosehn
4726a1485e2SGarance A Drosehn free(ptime);
4736a1485e2SGarance A Drosehn return (0);
4746a1485e2SGarance A Drosehn }
4756a1485e2SGarance A Drosehn
4766a1485e2SGarance A Drosehn /*
4776a1485e2SGarance A Drosehn * Some trivial routines so ptime_data can remain a completely
4786a1485e2SGarance A Drosehn * opaque type.
4796a1485e2SGarance A Drosehn */
4806a1485e2SGarance A Drosehn const char *
ptimeget_ctime(const struct ptime_data * ptime)4816a1485e2SGarance A Drosehn ptimeget_ctime(const struct ptime_data *ptime)
4826a1485e2SGarance A Drosehn {
4836a1485e2SGarance A Drosehn
4846a1485e2SGarance A Drosehn if (ptime == NULL)
4856a1485e2SGarance A Drosehn return ("Null time in ptimeget_ctime()\n");
4866a1485e2SGarance A Drosehn
4876a1485e2SGarance A Drosehn return (ctime(&ptime->tsecs));
4886a1485e2SGarance A Drosehn }
4896a1485e2SGarance A Drosehn
490b326fec4SDavid Bright /*
491b326fec4SDavid Bright * Generate a time of day string in an RFC5424 compatible format. Return a
492b326fec4SDavid Bright * pointer to the buffer with the timestamp string or NULL if an error. If the
493b326fec4SDavid Bright * time is not supplied, cannot be converted to local time, or the resulting
494b326fec4SDavid Bright * string would overflow the buffer, the returned string will be the RFC5424
495b326fec4SDavid Bright * NILVALUE.
496b326fec4SDavid Bright */
497b326fec4SDavid Bright char *
ptimeget_ctime_rfc5424(const struct ptime_data * ptime,char * timebuf,size_t bufsize)498b326fec4SDavid Bright ptimeget_ctime_rfc5424(const struct ptime_data *ptime,
499b326fec4SDavid Bright char *timebuf, size_t bufsize)
500b326fec4SDavid Bright {
501b326fec4SDavid Bright static const char NILVALUE[] = {"-"}; /* RFC5424 specified NILVALUE */
502b326fec4SDavid Bright int chars;
503b326fec4SDavid Bright struct tm tm;
504b326fec4SDavid Bright int tz_hours;
505b326fec4SDavid Bright int tz_mins;
506b326fec4SDavid Bright long tz_offset;
507b326fec4SDavid Bright char tz_sign;
508b326fec4SDavid Bright
509b326fec4SDavid Bright if (timebuf == NULL) {
510b326fec4SDavid Bright return (NULL);
511b326fec4SDavid Bright }
512b326fec4SDavid Bright
513b326fec4SDavid Bright if (bufsize < sizeof(NILVALUE)) {
514b326fec4SDavid Bright return (NULL);
515b326fec4SDavid Bright }
516b326fec4SDavid Bright
517b326fec4SDavid Bright /*
518b326fec4SDavid Bright * Convert to localtime. RFC5424 mandates the use of the NILVALUE if
519b326fec4SDavid Bright * the time cannot be obtained, so use that if there is an error in the
520b326fec4SDavid Bright * conversion.
521b326fec4SDavid Bright */
522b326fec4SDavid Bright if (ptime == NULL || localtime_r(&(ptime->tsecs), &tm) == NULL) {
523b326fec4SDavid Bright strlcpy(timebuf, NILVALUE, bufsize);
524b326fec4SDavid Bright return (timebuf);
525b326fec4SDavid Bright }
526b326fec4SDavid Bright
527b326fec4SDavid Bright /*
528b326fec4SDavid Bright * Convert the time to a string in RFC5424 format. The conversion
529b326fec4SDavid Bright * cannot be done with strftime() because it cannot produce the correct
530b326fec4SDavid Bright * timezone offset format.
531b326fec4SDavid Bright */
532b326fec4SDavid Bright if (tm.tm_gmtoff < 0) {
533b326fec4SDavid Bright tz_sign = '-';
534b326fec4SDavid Bright tz_offset = -tm.tm_gmtoff;
535b326fec4SDavid Bright } else {
536b326fec4SDavid Bright tz_sign = '+';
537b326fec4SDavid Bright tz_offset = tm.tm_gmtoff;
538b326fec4SDavid Bright }
539b326fec4SDavid Bright
540b326fec4SDavid Bright tz_hours = tz_offset / 3600;
541b326fec4SDavid Bright tz_mins = (tz_offset % 3600) / 60;
542b326fec4SDavid Bright
543b326fec4SDavid Bright chars = snprintf(timebuf, bufsize,
544b326fec4SDavid Bright "%04d-%02d-%02d" /* date */
545b326fec4SDavid Bright "T%02d:%02d:%02d" /* time */
546b326fec4SDavid Bright "%c%02d:%02d", /* time zone offset */
547b326fec4SDavid Bright tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
548b326fec4SDavid Bright tm.tm_hour, tm.tm_min, tm.tm_sec,
549b326fec4SDavid Bright tz_sign, tz_hours, tz_mins);
550b326fec4SDavid Bright
551b326fec4SDavid Bright /* If the timestamp is too big for timebuf, return the NILVALUE. */
552b326fec4SDavid Bright if (chars >= (int)bufsize) {
553b326fec4SDavid Bright strlcpy(timebuf, NILVALUE, bufsize);
554b326fec4SDavid Bright }
555b326fec4SDavid Bright
556b326fec4SDavid Bright return (timebuf);
557b326fec4SDavid Bright }
558b326fec4SDavid Bright
5596a1485e2SGarance A Drosehn double
ptimeget_diff(const struct ptime_data * minuend,const struct ptime_data * subtrahend)5606a1485e2SGarance A Drosehn ptimeget_diff(const struct ptime_data *minuend, const struct
5616a1485e2SGarance A Drosehn ptime_data *subtrahend)
5626a1485e2SGarance A Drosehn {
5636a1485e2SGarance A Drosehn
5646a1485e2SGarance A Drosehn /* Just like difftime(), we have no good error-return */
5656a1485e2SGarance A Drosehn if (minuend == NULL || subtrahend == NULL)
5666a1485e2SGarance A Drosehn return (0.0);
5676a1485e2SGarance A Drosehn
5686a1485e2SGarance A Drosehn return (difftime(minuend->tsecs, subtrahend->tsecs));
5696a1485e2SGarance A Drosehn }
5706a1485e2SGarance A Drosehn
5716a1485e2SGarance A Drosehn time_t
ptimeget_secs(const struct ptime_data * ptime)5726a1485e2SGarance A Drosehn ptimeget_secs(const struct ptime_data *ptime)
5736a1485e2SGarance A Drosehn {
5746a1485e2SGarance A Drosehn
5756a1485e2SGarance A Drosehn if (ptime == NULL)
5766a1485e2SGarance A Drosehn return (-1);
5776a1485e2SGarance A Drosehn
5786a1485e2SGarance A Drosehn return (ptime->tsecs);
5796a1485e2SGarance A Drosehn }
5806a1485e2SGarance A Drosehn
5816a1485e2SGarance A Drosehn /*
5826a1485e2SGarance A Drosehn * Generate an approximate timestamp for the next event, based on
5836a1485e2SGarance A Drosehn * what parts of time were specified by the original parameter to
5846a1485e2SGarance A Drosehn * ptime_relparse(). The result may be -1 if there is no obvious
5856a1485e2SGarance A Drosehn * "next time" which will work.
5866a1485e2SGarance A Drosehn */
5876a1485e2SGarance A Drosehn int
ptimeset_nxtime(struct ptime_data * ptime)5886a1485e2SGarance A Drosehn ptimeset_nxtime(struct ptime_data *ptime)
5896a1485e2SGarance A Drosehn {
5906a1485e2SGarance A Drosehn int moredays, tdpm, tmon, tyear;
5916a1485e2SGarance A Drosehn struct ptime_data nextmatch;
5926a1485e2SGarance A Drosehn
5936a1485e2SGarance A Drosehn if (ptime == NULL)
5946a1485e2SGarance A Drosehn return (-1);
5956a1485e2SGarance A Drosehn
5966a1485e2SGarance A Drosehn /*
5976a1485e2SGarance A Drosehn * Changes are not made to the given time until after all
5986a1485e2SGarance A Drosehn * of the calculations have been successful.
5996a1485e2SGarance A Drosehn */
6006a1485e2SGarance A Drosehn nextmatch = *ptime;
6016a1485e2SGarance A Drosehn /*
6026a1485e2SGarance A Drosehn * If the user specified a year and we're already past that
6036a1485e2SGarance A Drosehn * time, then there will never be another one!
6046a1485e2SGarance A Drosehn */
6056a1485e2SGarance A Drosehn if (ptime->tmspec & TSPEC_YEAR)
6066a1485e2SGarance A Drosehn return (-1);
6076a1485e2SGarance A Drosehn
6086a1485e2SGarance A Drosehn /*
6096a1485e2SGarance A Drosehn * The caller gave us a time in the past. Calculate how much
6106a1485e2SGarance A Drosehn * time is needed to go from that valid rotate time to the
6116a1485e2SGarance A Drosehn * next valid rotate time. We only need to get to the nearest
6126a1485e2SGarance A Drosehn * hour, because newsyslog is only run once per hour.
6136a1485e2SGarance A Drosehn */
6146a1485e2SGarance A Drosehn moredays = 0;
6156a1485e2SGarance A Drosehn if (ptime->tmspec & TSPEC_MONTHOFYEAR) {
6166a1485e2SGarance A Drosehn /* Special case: Feb 29th does not happen every year. */
6176a1485e2SGarance A Drosehn if (ptime->tm.tm_mon == 1 && ptime->tm.tm_mday == 29) {
6186a1485e2SGarance A Drosehn nextmatch.tm.tm_year += 4;
6196a1485e2SGarance A Drosehn if (days_pmonth(1, nextmatch.tm.tm_year) < 29)
6206a1485e2SGarance A Drosehn nextmatch.tm.tm_year += 4;
6216a1485e2SGarance A Drosehn } else {
6226a1485e2SGarance A Drosehn nextmatch.tm.tm_year += 1;
6236a1485e2SGarance A Drosehn }
6246a1485e2SGarance A Drosehn nextmatch.tm.tm_isdst = -1;
6256a1485e2SGarance A Drosehn nextmatch.tsecs = mktime(&nextmatch.tm);
6266a1485e2SGarance A Drosehn
6276a1485e2SGarance A Drosehn } else if (ptime->tmspec & TSPEC_LDAYOFMONTH) {
6286a1485e2SGarance A Drosehn /*
6296a1485e2SGarance A Drosehn * Need to get to the last day of next month. Origtm is
6306a1485e2SGarance A Drosehn * already at the last day of this month, so just add to
6316a1485e2SGarance A Drosehn * it number of days in the next month.
6326a1485e2SGarance A Drosehn */
6336a1485e2SGarance A Drosehn if (ptime->tm.tm_mon < 11)
6346a1485e2SGarance A Drosehn moredays = days_pmonth(ptime->tm.tm_mon + 1,
6356a1485e2SGarance A Drosehn ptime->tm.tm_year);
6366a1485e2SGarance A Drosehn else
6376a1485e2SGarance A Drosehn moredays = days_pmonth(0, ptime->tm.tm_year + 1);
6386a1485e2SGarance A Drosehn
6396a1485e2SGarance A Drosehn } else if (ptime->tmspec & TSPEC_DAYOFMONTH) {
6406a1485e2SGarance A Drosehn /* Jump to the same day in the next month */
6416a1485e2SGarance A Drosehn moredays = days_pmonth(ptime->tm.tm_mon, ptime->tm.tm_year);
6426a1485e2SGarance A Drosehn /*
6436a1485e2SGarance A Drosehn * In some cases, the next month may not *have* the
6446a1485e2SGarance A Drosehn * desired day-of-the-month. If that happens, then
6456a1485e2SGarance A Drosehn * move to the next month that does have enough days.
6466a1485e2SGarance A Drosehn */
6476a1485e2SGarance A Drosehn tmon = ptime->tm.tm_mon;
6486a1485e2SGarance A Drosehn tyear = ptime->tm.tm_year;
6496a1485e2SGarance A Drosehn for (;;) {
6506a1485e2SGarance A Drosehn if (tmon < 11)
6516a1485e2SGarance A Drosehn tmon += 1;
6526a1485e2SGarance A Drosehn else {
6536a1485e2SGarance A Drosehn tmon = 0;
6546a1485e2SGarance A Drosehn tyear += 1;
6556a1485e2SGarance A Drosehn }
6566a1485e2SGarance A Drosehn tdpm = days_pmonth(tmon, tyear);
6576a1485e2SGarance A Drosehn if (tdpm >= ptime->tm.tm_mday)
6586a1485e2SGarance A Drosehn break;
6596a1485e2SGarance A Drosehn moredays += tdpm;
6606a1485e2SGarance A Drosehn }
6616a1485e2SGarance A Drosehn
6626a1485e2SGarance A Drosehn } else if (ptime->tmspec & TSPEC_DAYOFWEEK) {
6636a1485e2SGarance A Drosehn moredays = 7;
6646a1485e2SGarance A Drosehn } else if (ptime->tmspec & TSPEC_HOUROFDAY) {
6656a1485e2SGarance A Drosehn moredays = 1;
6666a1485e2SGarance A Drosehn }
6676a1485e2SGarance A Drosehn
6686a1485e2SGarance A Drosehn if (moredays != 0) {
6696a1485e2SGarance A Drosehn nextmatch.tsecs += SECS_PER_HOUR * 24 * moredays;
6706a1485e2SGarance A Drosehn nextmatch.tm = *(localtime(&nextmatch.tsecs));
6716a1485e2SGarance A Drosehn }
6726a1485e2SGarance A Drosehn
6736a1485e2SGarance A Drosehn /*
6746a1485e2SGarance A Drosehn * The new time will need to be adjusted if the setting of
6756a1485e2SGarance A Drosehn * daylight-saving has changed between the two times.
6766a1485e2SGarance A Drosehn */
6776a1485e2SGarance A Drosehn ptime_adjust4dst(&nextmatch, ptime);
6786a1485e2SGarance A Drosehn
6796a1485e2SGarance A Drosehn /* Everything worked. Update the given time and return. */
6806a1485e2SGarance A Drosehn *ptime = nextmatch;
6816a1485e2SGarance A Drosehn return (0);
6826a1485e2SGarance A Drosehn }
6836a1485e2SGarance A Drosehn
6846a1485e2SGarance A Drosehn int
ptimeset_time(struct ptime_data * ptime,time_t secs)6856a1485e2SGarance A Drosehn ptimeset_time(struct ptime_data *ptime, time_t secs)
6866a1485e2SGarance A Drosehn {
6876a1485e2SGarance A Drosehn
6886a1485e2SGarance A Drosehn if (ptime == NULL)
6896a1485e2SGarance A Drosehn return (-1);
6906a1485e2SGarance A Drosehn
6916a1485e2SGarance A Drosehn ptime->tsecs = secs;
6926a1485e2SGarance A Drosehn ptime->tm = *(localtime(&ptime->tsecs));
6936a1485e2SGarance A Drosehn ptime->parseopts = 0;
6946a1485e2SGarance A Drosehn /* ptime->tmspec = ? */
6956a1485e2SGarance A Drosehn return (0);
6962f8d7c56SGarance A Drosehn }
697