xref: /freebsd/usr.sbin/newsyslog/ptimes.c (revision 1d386b48a555f61cb7325543adbbb5c3f3407a66)
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
days_pmonth(int month,int year)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
parse8601(struct ptime_data * ptime,const char * s)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
parseDWM(struct ptime_data * ptime,const char * s)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 *
ptime_init(const struct ptime_data * optsrc)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
ptime_adjust4dst(struct ptime_data * ptime,const struct ptime_data * dstsrc)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
ptime_relparse(struct ptime_data * ptime,int parseopts,time_t basetime,const char * str)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
ptime_free(struct ptime_data * ptime)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 *
ptimeget_ctime(const struct ptime_data * ptime)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 *
ptimeget_ctime_rfc5424(const struct ptime_data * ptime,char * timebuf,size_t bufsize)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
ptimeget_diff(const struct ptime_data * minuend,const struct ptime_data * subtrahend)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
ptimeget_secs(const struct ptime_data * ptime)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
ptimeset_nxtime(struct ptime_data * ptime)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
ptimeset_time(struct ptime_data * ptime,time_t secs)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 }
697