xref: /dragonfly/usr.sbin/newsyslog/ptimes.c (revision 91f1d6e1)
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  * $FreeBSD: head/usr.sbin/newsyslog/ptimes.c 318960 2017-05-26 16:36:30Z dab $
43  */
44 
45 #include <ctype.h>
46 #include <limits.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <time.h>
51 
52 #include "extern.h"
53 
54 #define	SECS_PER_HOUR	3600
55 
56 /*
57  * Bit-values which indicate which components of time were specified
58  * by the string given to parse8601 or parseDWM.  These are needed to
59  * calculate what time-in-the-future will match that string.
60  */
61 #define	TSPEC_YEAR		0x0001
62 #define	TSPEC_MONTHOFYEAR	0x0002
63 #define	TSPEC_LDAYOFMONTH	0x0004
64 #define	TSPEC_DAYOFMONTH	0x0008
65 #define	TSPEC_DAYOFWEEK		0x0010
66 #define	TSPEC_HOUROFDAY		0x0020
67 
68 #define	TNYET_ADJ4DST		-10	/* DST has "not yet" been adjusted */
69 
70 struct ptime_data {
71 	time_t		 basesecs;	/* Base point for relative times */
72 	time_t		 tsecs;		/* Time in seconds */
73 	struct tm	 basetm;	/* Base Time expanded into fields */
74 	struct tm	 tm;		/* Time expanded into fields */
75 	int		 did_adj4dst;	/* Track calls to ptime_adjust4dst */
76 	int		 parseopts;	/* Options given for parsing */
77 	int		 tmspec;	/* Indicates which time fields had
78 					 * been specified by the user */
79 };
80 
81 static int	 days_pmonth(int month, int year);
82 static int	 parse8601(struct ptime_data *ptime, const char *str);
83 static int	 parseDWM(struct ptime_data *ptime, const char *str);
84 
85 /*
86  * Simple routine to calculate the number of days in a given month.
87  */
88 static int
89 days_pmonth(int month, int year)
90 {
91 	static const int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31,
92 	    30, 31, 30, 31};
93 	int ndays;
94 
95 	ndays = mtab[month];
96 
97 	if (month == 1) {
98 		/*
99 		 * We are usually called with a 'tm-year' value
100 		 * (ie, the value = the number of years past 1900).
101 		 */
102 		if (year < 1900)
103 			year += 1900;
104 		if (year % 4 == 0) {
105 			/*
106 			 * This is a leap year, as long as it is not a
107 			 * multiple of 100, or if it is a multiple of
108 			 * both 100 and 400.
109 			 */
110 			if (year % 100 != 0)
111 				ndays++;	/* not multiple of 100 */
112 			else if (year % 400 == 0)
113 				ndays++;	/* is multiple of 100 and 400 */
114 		}
115 	}
116 	return (ndays);
117 }
118 
119 /*-
120  * Parse a limited subset of ISO 8601. The specific format is as follows:
121  *
122  * [CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
123  *
124  * We don't accept a timezone specification; missing fields (including timezone)
125  * are defaulted to the current date but time zero.
126  */
127 static int
128 parse8601(struct ptime_data *ptime, const char *s)
129 {
130 	char *t;
131 	long l;
132 	struct tm tm;
133 
134 	l = strtol(s, &t, 10);
135 	if (l < 0 || l >= INT_MAX || (*t != '\0' && *t != 'T'))
136 		return (-1);
137 
138 	/*
139 	 * Now t points either to the end of the string (if no time was
140 	 * provided) or to the letter `T' which separates date and time in
141 	 * ISO 8601.  The pointer arithmetic is the same for either case.
142 	 */
143 	tm = ptime->tm;
144 	ptime->tmspec = TSPEC_HOUROFDAY;
145 	switch (t - s) {
146 	case 8:
147 		tm.tm_year = ((l / 1000000) - 19) * 100;
148 		l = l % 1000000;
149 		/* FALLTHROUGH */
150 	case 6:
151 		ptime->tmspec |= TSPEC_YEAR;
152 		tm.tm_year -= tm.tm_year % 100;
153 		tm.tm_year += l / 10000;
154 		l = l % 10000;
155 		/* FALLTHROUGH */
156 	case 4:
157 		ptime->tmspec |= TSPEC_MONTHOFYEAR;
158 		tm.tm_mon = (l / 100) - 1;
159 		l = l % 100;
160 		/* FALLTHROUGH */
161 	case 2:
162 		ptime->tmspec |= TSPEC_DAYOFMONTH;
163 		tm.tm_mday = l;
164 		/* FALLTHROUGH */
165 	case 0:
166 		break;
167 	default:
168 		return (-1);
169 	}
170 
171 	/* sanity check */
172 	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
173 	    || tm.tm_mday < 1 || tm.tm_mday > 31)
174 		return (-1);
175 
176 	if (*t != '\0') {
177 		s = ++t;
178 		l = strtol(s, &t, 10);
179 		if (l < 0 || l >= INT_MAX || (*t != '\0' && !isspace(*t)))
180 			return (-1);
181 
182 		switch (t - s) {
183 		case 6:
184 			tm.tm_sec = l % 100;
185 			l /= 100;
186 			/* FALLTHROUGH */
187 		case 4:
188 			tm.tm_min = l % 100;
189 			l /= 100;
190 			/* FALLTHROUGH */
191 		case 2:
192 			ptime->tmspec |= TSPEC_HOUROFDAY;
193 			tm.tm_hour = l;
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 				}
281 			}
282 			break;
283 
284 		case 'M':
285 			if (WMseen)
286 				return (-1);
287 			WMseen++;
288 			ptime->tmspec |= TSPEC_DAYOFMONTH;
289 			s++;
290 			if (tolower(*s) == 'l') {
291 				/* User wants the last day of the month. */
292 				ptime->tmspec |= TSPEC_LDAYOFMONTH;
293 				tm.tm_mday = daysmon;
294 				endval = s + 1;
295 			} else {
296 				l = strtol(s, &tmp, 10);
297 				if (l < 1 || l > 31)
298 					return (-1);
299 
300 				if (l > daysmon)
301 					return (-1);
302 				endval = tmp;
303 				tm.tm_mday = l;
304 			}
305 			break;
306 
307 		default:
308 			return (-1);
309 			break;
310 		}
311 
312 		if (endval == NULL)
313 			return (-1);
314 		else if (*endval == '\0' || isspace(*endval))
315 			break;
316 		else
317 			s = endval;
318 	}
319 
320 	ptime->tm = tm;
321 	return (0);
322 }
323 
324 /*
325  * Initialize a new ptime-related data area.
326  */
327 struct ptime_data *
328 ptime_init(const struct ptime_data *optsrc)
329 {
330 	struct ptime_data *newdata;
331 
332 	newdata = malloc(sizeof(struct ptime_data));
333 	if (optsrc != NULL) {
334 		memcpy(newdata, optsrc, sizeof(struct ptime_data));
335 	} else {
336 		memset(newdata, '\0', sizeof(struct ptime_data));
337 		newdata->did_adj4dst = TNYET_ADJ4DST;
338 	}
339 
340 	return (newdata);
341 }
342 
343 /*
344  * Adjust a given time if that time is in a different timezone than
345  * some other time.
346  */
347 int
348 ptime_adjust4dst(struct ptime_data *ptime, const struct ptime_data *dstsrc)
349 {
350 	struct ptime_data adjtime;
351 
352 	if (ptime == NULL)
353 		return (-1);
354 
355 	/*
356 	 * Changes are not made to the given time until after all
357 	 * of the calculations have been successful.
358 	 */
359 	adjtime = *ptime;
360 
361 	/* Check to see if this adjustment was already made */
362 	if ((adjtime.did_adj4dst != TNYET_ADJ4DST) &&
363 	    (adjtime.did_adj4dst == dstsrc->tm.tm_isdst))
364 		return (0);		/* yes, so don't make it twice */
365 
366 	/* See if daylight-saving has changed between the two times. */
367 	if (dstsrc->tm.tm_isdst != adjtime.tm.tm_isdst) {
368 		if (adjtime.tm.tm_isdst == 1)
369 			adjtime.tsecs -= SECS_PER_HOUR;
370 		else if (adjtime.tm.tm_isdst == 0)
371 			adjtime.tsecs += SECS_PER_HOUR;
372 		adjtime.tm = *(localtime(&adjtime.tsecs));
373 		/* Remember that this adjustment has been made */
374 		adjtime.did_adj4dst = dstsrc->tm.tm_isdst;
375 		/*
376 		 * XXX - Should probably check to see if changing the
377 		 *	hour also changed the value of is_dst.  What
378 		 *	should we do in that case?
379 		 */
380 	}
381 
382 	*ptime = adjtime;
383 	return (0);
384 }
385 
386 int
387 ptime_relparse(struct ptime_data *ptime, int parseopts, time_t basetime,
388     const char *str)
389 {
390 	int dpm, pres;
391 	struct tm temp_tm;
392 
393 	ptime->parseopts = parseopts;
394 	ptime->basesecs = basetime;
395 	ptime->basetm = *(localtime(&ptime->basesecs));
396 	ptime->tm = ptime->basetm;
397 	ptime->tm.tm_hour = ptime->tm.tm_min = ptime->tm.tm_sec = 0;
398 
399 	/*
400 	 * Call a routine which sets ptime.tm and ptime.tspecs based
401 	 * on the given string and parsing-options.  Note that the
402 	 * routine should not call mktime to set ptime.tsecs.
403 	 */
404 	if (parseopts & PTM_PARSE_DWM)
405 		pres = parseDWM(ptime, str);
406 	else
407 		pres = parse8601(ptime, str);
408 	if (pres < 0) {
409 		ptime->tsecs = (time_t)pres;
410 		return (pres);
411 	}
412 
413 	/*
414 	 * Before calling mktime, check to see if we ended up with a
415 	 * "day-of-month" that does not exist in the selected month.
416 	 * If we did call mktime with that info, then mktime will
417 	 * make it look like the user specifically requested a day
418 	 * in the following month (eg: Feb 31 turns into Mar 3rd).
419 	 */
420 	dpm = days_pmonth(ptime->tm.tm_mon, ptime->tm.tm_year);
421 	if ((parseopts & PTM_PARSE_MATCHDOM) &&
422 	    (ptime->tmspec & TSPEC_DAYOFMONTH) &&
423 	    (ptime->tm.tm_mday> dpm)) {
424 		/*
425 		 * ptime_nxtime() will want a ptime->tsecs value,
426 		 * but we need to avoid mktime resetting all the
427 		 * ptime->tm values.
428 		 */
429 		if (verbose && dbg_at_times > 1)
430 			fprintf(stderr,
431 			    "\t-- dom fixed: %4d/%02d/%02d %02d:%02d (%02d)",
432 			    ptime->tm.tm_year, ptime->tm.tm_mon,
433 			    ptime->tm.tm_mday, ptime->tm.tm_hour,
434 			    ptime->tm.tm_min, dpm);
435 		temp_tm = ptime->tm;
436 		ptime->tsecs = mktime(&temp_tm);
437 		if (ptime->tsecs > (time_t)-1)
438 			ptimeset_nxtime(ptime);
439 		if (verbose && dbg_at_times > 1)
440 			fprintf(stderr,
441 			    " to: %4d/%02d/%02d %02d:%02d\n",
442 			    ptime->tm.tm_year, ptime->tm.tm_mon,
443 			    ptime->tm.tm_mday, ptime->tm.tm_hour,
444 			    ptime->tm.tm_min);
445 	}
446 
447 	/*
448 	 * Convert the ptime.tm into standard time_t seconds.  Check
449 	 * for invalid times, which includes things like the hour lost
450 	 * when switching from "standard time" to "daylight saving".
451 	 */
452 	ptime->tsecs = mktime(&ptime->tm);
453 	if (ptime->tsecs == (time_t)-1) {
454 		ptime->tsecs = (time_t)-2;
455 		return (-2);
456 	}
457 
458 	return (0);
459 }
460 
461 int
462 ptime_free(struct ptime_data *ptime)
463 {
464 
465 	if (ptime == NULL)
466 		return (-1);
467 
468 	free(ptime);
469 	return (0);
470 }
471 
472 /*
473  * Some trivial routines so ptime_data can remain a completely
474  * opaque type.
475  */
476 const char *
477 ptimeget_ctime(const struct ptime_data *ptime)
478 {
479 
480 	if (ptime == NULL)
481 		return ("Null time in ptimeget_ctime()\n");
482 
483 	return (ctime(&ptime->tsecs));
484 }
485 
486 /*
487  * Generate a time of day string in an RFC5424 compatible format. Return a
488  * pointer to the buffer with the timestamp string or NULL if an error. If the
489  * time is not supplied, cannot be converted to local time, or the resulting
490  * string would overflow the buffer, the returned string will be the RFC5424
491  * NILVALUE.
492  */
493 char *
494 ptimeget_ctime_rfc5424(const struct ptime_data *ptime,
495     char *timebuf, size_t bufsize)
496 {
497 	static const char NILVALUE[] = {"-"};	/* RFC5424 specified NILVALUE */
498 	int chars;
499 	struct tm tm;
500 	int tz_hours;
501 	int tz_mins;
502 	long tz_offset;
503 	char tz_sign;
504 
505 	if (timebuf == NULL) {
506 		return (NULL);
507 	}
508 
509 	if (bufsize < sizeof(NILVALUE)) {
510 		return (NULL);
511 	}
512 
513 	/*
514 	 * Convert to localtime. RFC5424 mandates the use of the NILVALUE if
515 	 * the time cannot be obtained, so use that if there is an error in the
516 	 * conversion.
517 	 */
518 	if (ptime == NULL || localtime_r(&(ptime->tsecs), &tm) == NULL) {
519 		strlcpy(timebuf, NILVALUE, bufsize);
520 		return (timebuf);
521 	}
522 
523 	/*
524 	 * Convert the time to a string in RFC5424 format. The conversion
525 	 * cannot be done with strftime() because it cannot produce the correct
526 	 * timezone offset format.
527 	 */
528 	if (tm.tm_gmtoff < 0) {
529 		tz_sign = '-';
530 		tz_offset = -tm.tm_gmtoff;
531 	} else {
532 		tz_sign = '+';
533 		tz_offset = tm.tm_gmtoff;
534 	}
535 
536 	tz_hours = tz_offset / 3600;
537 	tz_mins = (tz_offset % 3600) / 60;
538 
539 	chars = snprintf(timebuf, bufsize,
540 	    "%04d-%02d-%02d"	/* date */
541 	    "T%02d:%02d:%02d"	/* time */
542 	    "%c%02d:%02d",	/* time zone offset */
543 	    tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
544 	    tm.tm_hour, tm.tm_min, tm.tm_sec,
545 	    tz_sign, tz_hours, tz_mins);
546 
547 	/* If the timestamp is too big for timebuf, return the NILVALUE. */
548 	if (chars >= (int)bufsize) {
549 		strlcpy(timebuf, NILVALUE, bufsize);
550 	}
551 
552 	return (timebuf);
553 }
554 
555 double
556 ptimeget_diff(const struct ptime_data *minuend, const struct
557     ptime_data *subtrahend)
558 {
559 
560 	/* Just like difftime(), we have no good error-return */
561 	if (minuend == NULL || subtrahend == NULL)
562 		return (0.0);
563 
564 	return (difftime(minuend->tsecs, subtrahend->tsecs));
565 }
566 
567 time_t
568 ptimeget_secs(const struct ptime_data *ptime)
569 {
570 
571 	if (ptime == NULL)
572 		return (-1);
573 
574 	return (ptime->tsecs);
575 }
576 
577 /*
578  * Generate an approximate timestamp for the next event, based on
579  * what parts of time were specified by the original parameter to
580  * ptime_relparse(). The result may be -1 if there is no obvious
581  * "next time" which will work.
582  */
583 int
584 ptimeset_nxtime(struct ptime_data *ptime)
585 {
586 	int moredays, tdpm, tmon, tyear;
587 	struct ptime_data nextmatch;
588 
589 	if (ptime == NULL)
590 		return (-1);
591 
592 	/*
593 	 * Changes are not made to the given time until after all
594 	 * of the calculations have been successful.
595 	 */
596 	nextmatch = *ptime;
597 	/*
598 	 * If the user specified a year and we're already past that
599 	 * time, then there will never be another one!
600 	 */
601 	if (ptime->tmspec & TSPEC_YEAR)
602 		return (-1);
603 
604 	/*
605 	 * The caller gave us a time in the past.  Calculate how much
606 	 * time is needed to go from that valid rotate time to the
607 	 * next valid rotate time.  We only need to get to the nearest
608 	 * hour, because newsyslog is only run once per hour.
609 	 */
610 	moredays = 0;
611 	if (ptime->tmspec & TSPEC_MONTHOFYEAR) {
612 		/* Special case: Feb 29th does not happen every year. */
613 		if (ptime->tm.tm_mon == 1 && ptime->tm.tm_mday == 29) {
614 			nextmatch.tm.tm_year += 4;
615 			if (days_pmonth(1, nextmatch.tm.tm_year) < 29)
616 				nextmatch.tm.tm_year += 4;
617 		} else {
618 			nextmatch.tm.tm_year += 1;
619 		}
620 		nextmatch.tm.tm_isdst = -1;
621 		nextmatch.tsecs = mktime(&nextmatch.tm);
622 
623 	} else if (ptime->tmspec & TSPEC_LDAYOFMONTH) {
624 		/*
625 		 * Need to get to the last day of next month.  Origtm is
626 		 * already at the last day of this month, so just add to
627 		 * it number of days in the next month.
628 		 */
629 		if (ptime->tm.tm_mon < 11)
630 			moredays = days_pmonth(ptime->tm.tm_mon + 1,
631 			    ptime->tm.tm_year);
632 		else
633 			moredays = days_pmonth(0, ptime->tm.tm_year + 1);
634 
635 	} else if (ptime->tmspec & TSPEC_DAYOFMONTH) {
636 		/* Jump to the same day in the next month */
637 		moredays = days_pmonth(ptime->tm.tm_mon, ptime->tm.tm_year);
638 		/*
639 		 * In some cases, the next month may not *have* the
640 		 * desired day-of-the-month.  If that happens, then
641 		 * move to the next month that does have enough days.
642 		 */
643 		tmon = ptime->tm.tm_mon;
644 		tyear = ptime->tm.tm_year;
645 		for (;;) {
646 			if (tmon < 11)
647 				tmon += 1;
648 			else {
649 				tmon = 0;
650 				tyear += 1;
651 			}
652 			tdpm = days_pmonth(tmon, tyear);
653 			if (tdpm >= ptime->tm.tm_mday)
654 				break;
655 			moredays += tdpm;
656 		}
657 
658 	} else if (ptime->tmspec & TSPEC_DAYOFWEEK) {
659 		moredays = 7;
660 	} else if (ptime->tmspec & TSPEC_HOUROFDAY) {
661 		moredays = 1;
662 	}
663 
664 	if (moredays != 0) {
665 		nextmatch.tsecs += SECS_PER_HOUR * 24 * moredays;
666 		nextmatch.tm = *(localtime(&nextmatch.tsecs));
667 	}
668 
669 	/*
670 	 * The new time will need to be adjusted if the setting of
671 	 * daylight-saving has changed between the two times.
672 	 */
673 	ptime_adjust4dst(&nextmatch, ptime);
674 
675 	/* Everything worked.  Update the given time and return. */
676 	*ptime = nextmatch;
677 	return (0);
678 }
679 
680 int
681 ptimeset_time(struct ptime_data *ptime, time_t secs)
682 {
683 
684 	if (ptime == NULL)
685 		return (-1);
686 
687 	ptime->tsecs = secs;
688 	ptime->tm = *(localtime(&ptime->tsecs));
689 	ptime->parseopts = 0;
690 	/* ptime->tmspec = ? */
691 	return (0);
692 }
693