160484Sobrien/*-
2218822Sdim * ------+---------+---------+---------+---------+---------+---------+---------*
3218822Sdim * Initial version of parse8601 was originally added to newsyslog.c in
460484Sobrien *     FreeBSD on Jan 22, 1999 by Garrett Wollman <wollman@FreeBSD.org>.
560484Sobrien * Initial version of parseDWM was originally added to newsyslog.c in
660484Sobrien *     FreeBSD on Apr  4, 2000 by Hellmuth Michaelis <hm@FreeBSD.org>.
760484Sobrien *
860484Sobrien * Copyright (c) 2003  - Garance Alistair Drosehn <gad@FreeBSD.org>.
960484Sobrien * All rights reserved.
1060484Sobrien *
1160484Sobrien * Redistribution and use in source and binary forms, with or without
1260484Sobrien * modification, are permitted provided that the following conditions
1360484Sobrien * are met:
1460484Sobrien *   1. Redistributions of source code must retain the above copyright
1560484Sobrien *      notice, this list of conditions and the following disclaimer.
1660484Sobrien *   2. Redistributions in binary form must reproduce the above copyright
1760484Sobrien *      notice, this list of conditions and the following disclaimer in the
1860484Sobrien *      documentation and/or other materials provided with the distribution.
1960484Sobrien *
2060484Sobrien * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21218822Sdim * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22218822Sdim * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2360484Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2460484Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2560484Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2660484Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2760484Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2860484Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2960484Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3060484Sobrien * SUCH DAMAGE.
3160484Sobrien *
3260484Sobrien * The views and conclusions contained in the software and documentation
3360484Sobrien * are those of the authors and should not be interpreted as representing
3460484Sobrien * official policies, either expressed or implied, of the FreeBSD Project.
3560484Sobrien *
3660484Sobrien * ------+---------+---------+---------+---------+---------+---------+---------*
3760484Sobrien * This is intended to be a set of general-purpose routines to process times.
3860484Sobrien * Right now it probably still has a number of assumptions in it, such that
3960484Sobrien * it works fine for newsyslog but might not work for other uses.
4060484Sobrien * ------+---------+---------+---------+---------+---------+---------+---------*
4160484Sobrien */
4260484Sobrien
4360484Sobrien#include <sys/cdefs.h>
4460484Sobrien__FBSDID("$FreeBSD: stable/10/usr.sbin/newsyslog/ptimes.c 321263 2017-07-20 00:44:01Z ngie $");
4560484Sobrien
4660484Sobrien#include <ctype.h>
4760484Sobrien#include <limits.h>
48130561Sobrien#include <stdio.h>
49130561Sobrien#include <stdlib.h>
5060484Sobrien#include <string.h>
51130561Sobrien#include <time.h>
52130561Sobrien
53130561Sobrien#include "extern.h"
54130561Sobrien
55130561Sobrien#define	SECS_PER_HOUR	3600
56130561Sobrien
57130561Sobrien/*
58130561Sobrien * Bit-values which indicate which components of time were specified
59130561Sobrien * by the string given to parse8601 or parseDWM.  These are needed to
60130561Sobrien * calculate what time-in-the-future will match that string.
61130561Sobrien */
62130561Sobrien#define	TSPEC_YEAR		0x0001
63130561Sobrien#define	TSPEC_MONTHOFYEAR	0x0002
64130561Sobrien#define	TSPEC_LDAYOFMONTH	0x0004
6560484Sobrien#define	TSPEC_DAYOFMONTH	0x0008
6660484Sobrien#define	TSPEC_DAYOFWEEK		0x0010
67130561Sobrien#define	TSPEC_HOUROFDAY		0x0020
68130561Sobrien
69218822Sdim#define	TNYET_ADJ4DST		-10	/* DST has "not yet" been adjusted */
70218822Sdim
71130561Sobrienstruct ptime_data {
72130561Sobrien	time_t		 basesecs;	/* Base point for relative times */
73130561Sobrien	time_t		 tsecs;		/* Time in seconds */
7460484Sobrien	struct tm	 basetm;	/* Base Time expanded into fields */
7560484Sobrien	struct tm	 tm;		/* Time expanded into fields */
7660484Sobrien	int		 did_adj4dst;	/* Track calls to ptime_adjust4dst */
77130561Sobrien	int		 parseopts;	/* Options given for parsing */
78130561Sobrien	int		 tmspec;	/* Indicates which time fields had
79130561Sobrien					 * been specified by the user */
8060484Sobrien};
8160484Sobrien
82130561Sobrienstatic int	 days_pmonth(int month, int year);
8377298Sobrienstatic int	 parse8601(struct ptime_data *ptime, const char *str);
84218822Sdimstatic int	 parseDWM(struct ptime_data *ptime, const char *str);
85218822Sdim
86218822Sdim/*
8760484Sobrien * Simple routine to calculate the number of days in a given month.
88218822Sdim */
89218822Sdimstatic int
90218822Sdimdays_pmonth(int month, int year)
9160484Sobrien{
9260484Sobrien	static const int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31,
9360484Sobrien	    30, 31, 30, 31};
9460484Sobrien	int ndays;
9560484Sobrien
9660484Sobrien	ndays = mtab[month];
9760484Sobrien
9860484Sobrien	if (month == 1) {
9960484Sobrien		/*
100218822Sdim		 * We are usually called with a 'tm-year' value
101218822Sdim		 * (ie, the value = the number of years past 1900).
102218822Sdim		 */
103218822Sdim		if (year < 1900)
104218822Sdim			year += 1900;
105218822Sdim		if (year % 4 == 0) {
10660484Sobrien			/*
10760484Sobrien			 * This is a leap year, as long as it is not a
10860484Sobrien			 * multiple of 100, or if it is a multiple of
10960484Sobrien			 * both 100 and 400.
110218822Sdim			 */
111218822Sdim			if (year % 100 != 0)
11260484Sobrien				ndays++;	/* not multiple of 100 */
11360484Sobrien			else if (year % 400 == 0)
11460484Sobrien				ndays++;	/* is multiple of 100 and 400 */
115130561Sobrien		}
11660484Sobrien	}
11760484Sobrien	return (ndays);
11860484Sobrien}
11960484Sobrien
12060484Sobrien/*-
121130561Sobrien * Parse a limited subset of ISO 8601. The specific format is as follows:
12260484Sobrien *
12360484Sobrien * [CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
12460484Sobrien *
12560484Sobrien * We don't accept a timezone specification; missing fields (including timezone)
126218822Sdim * are defaulted to the current date but time zero.
127218822Sdim */
128218822Sdimstatic int
129218822Sdimparse8601(struct ptime_data *ptime, const char *s)
130218822Sdim{
131218822Sdim	char *t;
132218822Sdim	long l;
133218822Sdim	struct tm tm;
134218822Sdim
135218822Sdim	l = strtol(s, &t, 10);
136218822Sdim	if (l < 0 || l >= INT_MAX || (*t != '\0' && *t != 'T'))
13760484Sobrien		return (-1);
138218822Sdim
13960484Sobrien	/*
14060484Sobrien	 * Now t points either to the end of the string (if no time was
14160484Sobrien	 * provided) or to the letter `T' which separates date and time in
14260484Sobrien	 * ISO 8601.  The pointer arithmetic is the same for either case.
14360484Sobrien	 */
144218822Sdim	tm = ptime->tm;
145218822Sdim	ptime->tmspec = TSPEC_HOUROFDAY;
146218822Sdim	switch (t - s) {
147218822Sdim	case 8:
148218822Sdim		tm.tm_year = ((l / 1000000) - 19) * 100;
149218822Sdim		l = l % 1000000;
150130561Sobrien	case 6:
151130561Sobrien		ptime->tmspec |= TSPEC_YEAR;
152130561Sobrien		tm.tm_year -= tm.tm_year % 100;
15360484Sobrien		tm.tm_year += l / 10000;
15460484Sobrien		l = l % 10000;
15560484Sobrien	case 4:
15660484Sobrien		ptime->tmspec |= TSPEC_MONTHOFYEAR;
15760484Sobrien		tm.tm_mon = (l / 100) - 1;
158218822Sdim		l = l % 100;
159218822Sdim	case 2:
16060484Sobrien		ptime->tmspec |= TSPEC_DAYOFMONTH;
161130561Sobrien		tm.tm_mday = l;
162130561Sobrien	case 0:
163130561Sobrien		break;
164130561Sobrien	default:
165130561Sobrien		return (-1);
16660484Sobrien	}
16760484Sobrien
168130561Sobrien	/* sanity check */
169130561Sobrien	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
170130561Sobrien	    || tm.tm_mday < 1 || tm.tm_mday > 31)
171130561Sobrien		return (-1);
172130561Sobrien
17360484Sobrien	if (*t != '\0') {
174218822Sdim		s = ++t;
175218822Sdim		l = strtol(s, &t, 10);
176218822Sdim		if (l < 0 || l >= INT_MAX || (*t != '\0' && !isspace(*t)))
177218822Sdim			return (-1);
178218822Sdim
179218822Sdim		switch (t - s) {
18060484Sobrien		case 6:
18160484Sobrien			tm.tm_sec = l % 100;
18278828Sobrien			l /= 100;
18378828Sobrien		case 4:
18478828Sobrien			tm.tm_min = l % 100;
18578828Sobrien			l /= 100;
18678828Sobrien		case 2:
187130561Sobrien			ptime->tmspec |= TSPEC_HOUROFDAY;
18878828Sobrien			tm.tm_hour = l;
189130561Sobrien		case 0:
19078828Sobrien			break;
19178828Sobrien		default:
19278828Sobrien			return (-1);
19378828Sobrien		}
19478828Sobrien
19578828Sobrien		/* sanity check */
19678828Sobrien		if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
197130561Sobrien		    || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
198218822Sdim			return (-1);
199218822Sdim	}
200218822Sdim
201218822Sdim	ptime->tm = tm;
202218822Sdim	return (0);
203218822Sdim}
204218822Sdim
205218822Sdim/*-
206130561Sobrien * Parse a cyclic time specification, the format is as follows:
207130561Sobrien *
208130561Sobrien *	[Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
209218822Sdim *
210130561Sobrien * to rotate a logfile cyclic at
211218822Sdim *
212130561Sobrien *	- every day (D) within a specific hour (hh)	(hh = 0...23)
213218822Sdim *	- once a week (W) at a specific day (d)     OR	(d = 0..6, 0 = Sunday)
214218822Sdim *	- once a month (M) at a specific day (d)	(d = 1..31,l|L)
215218822Sdim *
216218822Sdim * We don't accept a timezone specification; missing fields
217218822Sdim * are defaulted to the current date but time zero.
218218822Sdim */
219130561Sobrienstatic int
220130561SobrienparseDWM(struct ptime_data *ptime, const char *s)
221130561Sobrien{
222130561Sobrien	int daysmon, Dseen, WMseen;
223130561Sobrien	const char *endval;
224130561Sobrien	char *tmp;
225130561Sobrien	long l;
226130561Sobrien	struct tm tm;
227218822Sdim
228218822Sdim	/* Save away the number of days in this month */
229218822Sdim	tm = ptime->tm;
230218822Sdim	daysmon = days_pmonth(tm.tm_mon, tm.tm_year);
231218822Sdim
232218822Sdim	WMseen = Dseen = 0;
233218822Sdim	ptime->tmspec = TSPEC_HOUROFDAY;
234218822Sdim	for (;;) {
235218822Sdim		endval = NULL;
236218822Sdim		switch (*s) {
237218822Sdim		case 'D':
238218822Sdim			if (Dseen)
239218822Sdim				return (-1);
240218822Sdim			Dseen++;
241218822Sdim			ptime->tmspec |= TSPEC_HOUROFDAY;
242130561Sobrien			s++;
243130561Sobrien			l = strtol(s, &tmp, 10);
244130561Sobrien			if (l < 0 || l > 23)
245130561Sobrien				return (-1);
246130561Sobrien			endval = tmp;
247130561Sobrien			tm.tm_hour = l;
248130561Sobrien			break;
249130561Sobrien
250218822Sdim		case 'W':
251130561Sobrien			if (WMseen)
252130561Sobrien				return (-1);
253130561Sobrien			WMseen++;
254130561Sobrien			ptime->tmspec |= TSPEC_DAYOFWEEK;
255130561Sobrien			s++;
256218822Sdim			l = strtol(s, &tmp, 10);
257218822Sdim			if (l < 0 || l > 6)
258218822Sdim				return (-1);
259130561Sobrien			endval = tmp;
260130561Sobrien			if (l != tm.tm_wday) {
261130561Sobrien				int save;
262130561Sobrien
263130561Sobrien				if (l < tm.tm_wday) {
264130561Sobrien					save = 6 - tm.tm_wday;
265130561Sobrien					save += (l + 1);
266130561Sobrien				} else {
267130561Sobrien					save = l - tm.tm_wday;
268130561Sobrien				}
269130561Sobrien
270130561Sobrien				tm.tm_mday += save;
271130561Sobrien
272130561Sobrien				if (tm.tm_mday > daysmon) {
273130561Sobrien					tm.tm_mon++;
274130561Sobrien					tm.tm_mday = tm.tm_mday - daysmon;
275218822Sdim				}
276218822Sdim			}
277218822Sdim			break;
278218822Sdim
279218822Sdim		case 'M':
280218822Sdim			if (WMseen)
281218822Sdim				return (-1);
282218822Sdim			WMseen++;
283218822Sdim			ptime->tmspec |= TSPEC_DAYOFMONTH;
284218822Sdim			s++;
285218822Sdim			if (tolower(*s) == 'l') {
286218822Sdim				/* User wants the last day of the month. */
287				ptime->tmspec |= TSPEC_LDAYOFMONTH;
288				tm.tm_mday = daysmon;
289				endval = s + 1;
290			} else {
291				l = strtol(s, &tmp, 10);
292				if (l < 1 || l > 31)
293					return (-1);
294
295				if (l > daysmon)
296					return (-1);
297				endval = tmp;
298				tm.tm_mday = l;
299			}
300			break;
301
302		default:
303			return (-1);
304			break;
305		}
306
307		if (endval == NULL)
308			return (-1);
309		else if (*endval == '\0' || isspace(*endval))
310			break;
311		else
312			s = endval;
313	}
314
315	ptime->tm = tm;
316	return (0);
317}
318
319/*
320 * Initialize a new ptime-related data area.
321 */
322struct ptime_data *
323ptime_init(const struct ptime_data *optsrc)
324{
325	struct ptime_data *newdata;
326
327	newdata = malloc(sizeof(struct ptime_data));
328	if (optsrc != NULL) {
329		memcpy(newdata, optsrc, sizeof(struct ptime_data));
330	} else {
331		memset(newdata, '\0', sizeof(struct ptime_data));
332		newdata->did_adj4dst = TNYET_ADJ4DST;
333	}
334
335	return (newdata);
336}
337
338/*
339 * Adjust a given time if that time is in a different timezone than
340 * some other time.
341 */
342int
343ptime_adjust4dst(struct ptime_data *ptime, const struct ptime_data *dstsrc)
344{
345	struct ptime_data adjtime;
346
347	if (ptime == NULL)
348		return (-1);
349
350	/*
351	 * Changes are not made to the given time until after all
352	 * of the calculations have been successful.
353	 */
354	adjtime = *ptime;
355
356	/* Check to see if this adjustment was already made */
357	if ((adjtime.did_adj4dst != TNYET_ADJ4DST) &&
358	    (adjtime.did_adj4dst == dstsrc->tm.tm_isdst))
359		return (0);		/* yes, so don't make it twice */
360
361	/* See if daylight-saving has changed between the two times. */
362	if (dstsrc->tm.tm_isdst != adjtime.tm.tm_isdst) {
363		if (adjtime.tm.tm_isdst == 1)
364			adjtime.tsecs -= SECS_PER_HOUR;
365		else if (adjtime.tm.tm_isdst == 0)
366			adjtime.tsecs += SECS_PER_HOUR;
367		adjtime.tm = *(localtime(&adjtime.tsecs));
368		/* Remember that this adjustment has been made */
369		adjtime.did_adj4dst = dstsrc->tm.tm_isdst;
370		/*
371		 * XXX - Should probably check to see if changing the
372		 *	hour also changed the value of is_dst.  What
373		 *	should we do in that case?
374		 */
375	}
376
377	*ptime = adjtime;
378	return (0);
379}
380
381int
382ptime_relparse(struct ptime_data *ptime, int parseopts, time_t basetime,
383    const char *str)
384{
385	int dpm, pres;
386	struct tm temp_tm;
387
388	ptime->parseopts = parseopts;
389	ptime->basesecs = basetime;
390	ptime->basetm = *(localtime(&ptime->basesecs));
391	ptime->tm = ptime->basetm;
392	ptime->tm.tm_hour = ptime->tm.tm_min = ptime->tm.tm_sec = 0;
393
394	/*
395	 * Call a routine which sets ptime.tm and ptime.tspecs based
396	 * on the given string and parsing-options.  Note that the
397	 * routine should not call mktime to set ptime.tsecs.
398	 */
399	if (parseopts & PTM_PARSE_DWM)
400		pres = parseDWM(ptime, str);
401	else
402		pres = parse8601(ptime, str);
403	if (pres < 0) {
404		ptime->tsecs = (time_t)pres;
405		return (pres);
406	}
407
408	/*
409	 * Before calling mktime, check to see if we ended up with a
410	 * "day-of-month" that does not exist in the selected month.
411	 * If we did call mktime with that info, then mktime will
412	 * make it look like the user specifically requested a day
413	 * in the following month (eg: Feb 31 turns into Mar 3rd).
414	 */
415	dpm = days_pmonth(ptime->tm.tm_mon, ptime->tm.tm_year);
416	if ((parseopts & PTM_PARSE_MATCHDOM) &&
417	    (ptime->tmspec & TSPEC_DAYOFMONTH) &&
418	    (ptime->tm.tm_mday> dpm)) {
419		/*
420		 * ptime_nxtime() will want a ptime->tsecs value,
421		 * but we need to avoid mktime resetting all the
422		 * ptime->tm values.
423		 */
424		if (verbose && dbg_at_times > 1)
425			fprintf(stderr,
426			    "\t-- dom fixed: %4d/%02d/%02d %02d:%02d (%02d)",
427			    ptime->tm.tm_year, ptime->tm.tm_mon,
428			    ptime->tm.tm_mday, ptime->tm.tm_hour,
429			    ptime->tm.tm_min, dpm);
430		temp_tm = ptime->tm;
431		ptime->tsecs = mktime(&temp_tm);
432		if (ptime->tsecs > (time_t)-1)
433			ptimeset_nxtime(ptime);
434		if (verbose && dbg_at_times > 1)
435			fprintf(stderr,
436			    " to: %4d/%02d/%02d %02d:%02d\n",
437			    ptime->tm.tm_year, ptime->tm.tm_mon,
438			    ptime->tm.tm_mday, ptime->tm.tm_hour,
439			    ptime->tm.tm_min);
440	}
441
442	/*
443	 * Convert the ptime.tm into standard time_t seconds.  Check
444	 * for invalid times, which includes things like the hour lost
445	 * when switching from "standard time" to "daylight saving".
446	 */
447	ptime->tsecs = mktime(&ptime->tm);
448	if (ptime->tsecs == (time_t)-1) {
449		ptime->tsecs = (time_t)-2;
450		return (-2);
451	}
452
453	return (0);
454}
455
456int
457ptime_free(struct ptime_data *ptime)
458{
459
460	if (ptime == NULL)
461		return (-1);
462
463	free(ptime);
464	return (0);
465}
466
467/*
468 * Some trivial routines so ptime_data can remain a completely
469 * opaque type.
470 */
471const char *
472ptimeget_ctime(const struct ptime_data *ptime)
473{
474
475	if (ptime == NULL)
476		return ("Null time in ptimeget_ctime()\n");
477
478	return (ctime(&ptime->tsecs));
479}
480
481/*
482 * Generate a time of day string in an RFC5424 compatible format. Return a
483 * pointer to the buffer with the timestamp string or NULL if an error. If the
484 * time is not supplied, cannot be converted to local time, or the resulting
485 * string would overflow the buffer, the returned string will be the RFC5424
486 * NILVALUE.
487 */
488char *
489ptimeget_ctime_rfc5424(const struct ptime_data *ptime,
490    char *timebuf, size_t bufsize)
491{
492	static const char NILVALUE[] = {"-"};	/* RFC5424 specified NILVALUE */
493	int chars;
494	struct tm tm;
495	int tz_hours;
496	int tz_mins;
497	long tz_offset;
498	char tz_sign;
499
500	if (timebuf == NULL) {
501		return (NULL);
502	}
503
504	if (bufsize < sizeof(NILVALUE)) {
505		return (NULL);
506	}
507
508	/*
509	 * Convert to localtime. RFC5424 mandates the use of the NILVALUE if
510	 * the time cannot be obtained, so use that if there is an error in the
511	 * conversion.
512	 */
513	if (ptime == NULL || localtime_r(&(ptime->tsecs), &tm) == NULL) {
514		strlcpy(timebuf, NILVALUE, bufsize);
515		return (timebuf);
516	}
517
518	/*
519	 * Convert the time to a string in RFC5424 format. The conversion
520	 * cannot be done with strftime() because it cannot produce the correct
521	 * timezone offset format.
522	 */
523	if (tm.tm_gmtoff < 0) {
524		tz_sign = '-';
525		tz_offset = -tm.tm_gmtoff;
526	} else {
527		tz_sign = '+';
528		tz_offset = tm.tm_gmtoff;
529	}
530
531	tz_hours = tz_offset / 3600;
532	tz_mins = (tz_offset % 3600) / 60;
533
534	chars = snprintf(timebuf, bufsize,
535	    "%04d-%02d-%02d"	/* date */
536	    "T%02d:%02d:%02d"	/* time */
537	    "%c%02d:%02d",	/* time zone offset */
538	    tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
539	    tm.tm_hour, tm.tm_min, tm.tm_sec,
540	    tz_sign, tz_hours, tz_mins);
541
542	/* If the timestamp is too big for timebuf, return the NILVALUE. */
543	if (chars >= (int)bufsize) {
544		strlcpy(timebuf, NILVALUE, bufsize);
545	}
546
547	return (timebuf);
548}
549
550double
551ptimeget_diff(const struct ptime_data *minuend, const struct
552    ptime_data *subtrahend)
553{
554
555	/* Just like difftime(), we have no good error-return */
556	if (minuend == NULL || subtrahend == NULL)
557		return (0.0);
558
559	return (difftime(minuend->tsecs, subtrahend->tsecs));
560}
561
562time_t
563ptimeget_secs(const struct ptime_data *ptime)
564{
565
566	if (ptime == NULL)
567		return (-1);
568
569	return (ptime->tsecs);
570}
571
572/*
573 * Generate an approximate timestamp for the next event, based on
574 * what parts of time were specified by the original parameter to
575 * ptime_relparse(). The result may be -1 if there is no obvious
576 * "next time" which will work.
577 */
578int
579ptimeset_nxtime(struct ptime_data *ptime)
580{
581	int moredays, tdpm, tmon, tyear;
582	struct ptime_data nextmatch;
583
584	if (ptime == NULL)
585		return (-1);
586
587	/*
588	 * Changes are not made to the given time until after all
589	 * of the calculations have been successful.
590	 */
591	nextmatch = *ptime;
592	/*
593	 * If the user specified a year and we're already past that
594	 * time, then there will never be another one!
595	 */
596	if (ptime->tmspec & TSPEC_YEAR)
597		return (-1);
598
599	/*
600	 * The caller gave us a time in the past.  Calculate how much
601	 * time is needed to go from that valid rotate time to the
602	 * next valid rotate time.  We only need to get to the nearest
603	 * hour, because newsyslog is only run once per hour.
604	 */
605	moredays = 0;
606	if (ptime->tmspec & TSPEC_MONTHOFYEAR) {
607		/* Special case: Feb 29th does not happen every year. */
608		if (ptime->tm.tm_mon == 1 && ptime->tm.tm_mday == 29) {
609			nextmatch.tm.tm_year += 4;
610			if (days_pmonth(1, nextmatch.tm.tm_year) < 29)
611				nextmatch.tm.tm_year += 4;
612		} else {
613			nextmatch.tm.tm_year += 1;
614		}
615		nextmatch.tm.tm_isdst = -1;
616		nextmatch.tsecs = mktime(&nextmatch.tm);
617
618	} else if (ptime->tmspec & TSPEC_LDAYOFMONTH) {
619		/*
620		 * Need to get to the last day of next month.  Origtm is
621		 * already at the last day of this month, so just add to
622		 * it number of days in the next month.
623		 */
624		if (ptime->tm.tm_mon < 11)
625			moredays = days_pmonth(ptime->tm.tm_mon + 1,
626			    ptime->tm.tm_year);
627		else
628			moredays = days_pmonth(0, ptime->tm.tm_year + 1);
629
630	} else if (ptime->tmspec & TSPEC_DAYOFMONTH) {
631		/* Jump to the same day in the next month */
632		moredays = days_pmonth(ptime->tm.tm_mon, ptime->tm.tm_year);
633		/*
634		 * In some cases, the next month may not *have* the
635		 * desired day-of-the-month.  If that happens, then
636		 * move to the next month that does have enough days.
637		 */
638		tmon = ptime->tm.tm_mon;
639		tyear = ptime->tm.tm_year;
640		for (;;) {
641			if (tmon < 11)
642				tmon += 1;
643			else {
644				tmon = 0;
645				tyear += 1;
646			}
647			tdpm = days_pmonth(tmon, tyear);
648			if (tdpm >= ptime->tm.tm_mday)
649				break;
650			moredays += tdpm;
651		}
652
653	} else if (ptime->tmspec & TSPEC_DAYOFWEEK) {
654		moredays = 7;
655	} else if (ptime->tmspec & TSPEC_HOUROFDAY) {
656		moredays = 1;
657	}
658
659	if (moredays != 0) {
660		nextmatch.tsecs += SECS_PER_HOUR * 24 * moredays;
661		nextmatch.tm = *(localtime(&nextmatch.tsecs));
662	}
663
664	/*
665	 * The new time will need to be adjusted if the setting of
666	 * daylight-saving has changed between the two times.
667	 */
668	ptime_adjust4dst(&nextmatch, ptime);
669
670	/* Everything worked.  Update the given time and return. */
671	*ptime = nextmatch;
672	return (0);
673}
674
675int
676ptimeset_time(struct ptime_data *ptime, time_t secs)
677{
678
679	if (ptime == NULL)
680		return (-1);
681
682	ptime->tsecs = secs;
683	ptime->tm = *(localtime(&ptime->tsecs));
684	ptime->parseopts = 0;
685	/* ptime->tmspec = ? */
686	return (0);
687}
688