xref: /dflybsd-src/lib/libc/stdtime/getdate.c (revision 8f9e67109499acc57ad368877f4b3b0b7cdf6d93)
1*8f9e6710SSascha Wildner /*	$NetBSD: getdate.c,v 1.4 2018/01/04 20:57:29 kamil Exp $	*/
2*8f9e6710SSascha Wildner /*
3*8f9e6710SSascha Wildner  * Copyright (c) 2009 The NetBSD Foundation, Inc.
4*8f9e6710SSascha Wildner  * All rights reserved.
5*8f9e6710SSascha Wildner  *
6*8f9e6710SSascha Wildner  * This code is derived from software contributed to The NetBSD Foundation
7*8f9e6710SSascha Wildner  * by Brian Ginsbach.
8*8f9e6710SSascha Wildner  *
9*8f9e6710SSascha Wildner  * Redistribution and use in source and binary forms, with or without
10*8f9e6710SSascha Wildner  * modification, are permitted provided that the following conditions
11*8f9e6710SSascha Wildner  * are met:
12*8f9e6710SSascha Wildner  * 1. Redistributions of source code must retain the above copyright
13*8f9e6710SSascha Wildner  *    notice, this list of conditions and the following disclaimer.
14*8f9e6710SSascha Wildner  * 2. Redistributions in binary form must reproduce the above copyright
15*8f9e6710SSascha Wildner  *    notice, this list of conditions and the following disclaimer in the
16*8f9e6710SSascha Wildner  *    documentation and/or other materials provided with the distribution.
17*8f9e6710SSascha Wildner  *
18*8f9e6710SSascha Wildner  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
19*8f9e6710SSascha Wildner  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20*8f9e6710SSascha Wildner  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21*8f9e6710SSascha Wildner  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
22*8f9e6710SSascha Wildner  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23*8f9e6710SSascha Wildner  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24*8f9e6710SSascha Wildner  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25*8f9e6710SSascha Wildner  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26*8f9e6710SSascha Wildner  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27*8f9e6710SSascha Wildner  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28*8f9e6710SSascha Wildner  * POSSIBILITY OF SUCH DAMAGE.
29*8f9e6710SSascha Wildner  */
30*8f9e6710SSascha Wildner 
31*8f9e6710SSascha Wildner #include "namespace.h"
32*8f9e6710SSascha Wildner 
33*8f9e6710SSascha Wildner #include <sys/stat.h>
34*8f9e6710SSascha Wildner 
35*8f9e6710SSascha Wildner #include <errno.h>
36*8f9e6710SSascha Wildner #include <stdio.h>
37*8f9e6710SSascha Wildner #include <stdlib.h>
38*8f9e6710SSascha Wildner #include <string.h>
39*8f9e6710SSascha Wildner #include <time.h>
40*8f9e6710SSascha Wildner #include <unistd.h>
41*8f9e6710SSascha Wildner #include <util.h>
42*8f9e6710SSascha Wildner 
43*8f9e6710SSascha Wildner #include "un-namespace.h"
44*8f9e6710SSascha Wildner 
45*8f9e6710SSascha Wildner #define TMSENTINEL	(-1)
46*8f9e6710SSascha Wildner 
47*8f9e6710SSascha Wildner /*
48*8f9e6710SSascha Wildner  * getdate_err is set to one of the following values on error.
49*8f9e6710SSascha Wildner  *
50*8f9e6710SSascha Wildner  * 1	The DATEMSK environment variable is null or undefined.
51*8f9e6710SSascha Wildner  * 2	The template file cannot be opened for reading.
52*8f9e6710SSascha Wildner  * 3	Failed to get file status information.
53*8f9e6710SSascha Wildner  * 4	Template file is not a regular file.
54*8f9e6710SSascha Wildner  * 5	Encountered an error while reading the template file.
55*8f9e6710SSascha Wildner  * 6	Cannot allocate memory.
56*8f9e6710SSascha Wildner  * 7	Input string does not match any line in the template.
57*8f9e6710SSascha Wildner  * 8	Input string is invalid (for example, February 31) or could not
58*8f9e6710SSascha Wildner  *	be represented in a time_t.
59*8f9e6710SSascha Wildner  */
60*8f9e6710SSascha Wildner 
61*8f9e6710SSascha Wildner int getdate_err;
62*8f9e6710SSascha Wildner 
63*8f9e6710SSascha Wildner struct tm *
getdate(const char * str)64*8f9e6710SSascha Wildner getdate(const char *str)
65*8f9e6710SSascha Wildner {
66*8f9e6710SSascha Wildner 	char *datemsk, *line, *rp;
67*8f9e6710SSascha Wildner 	FILE *fp;
68*8f9e6710SSascha Wildner 	struct stat sb;
69*8f9e6710SSascha Wildner 	static struct tm rtm, tmnow;
70*8f9e6710SSascha Wildner 	struct tm *tmp, *rtmp = &rtm;
71*8f9e6710SSascha Wildner 	size_t lineno = 0;
72*8f9e6710SSascha Wildner 	time_t now;
73*8f9e6710SSascha Wildner 
74*8f9e6710SSascha Wildner 	if (((datemsk = getenv("DATEMSK")) == NULL) || *datemsk == '\0') {
75*8f9e6710SSascha Wildner 		getdate_err = 1;
76*8f9e6710SSascha Wildner 		return (NULL);
77*8f9e6710SSascha Wildner 	}
78*8f9e6710SSascha Wildner 
79*8f9e6710SSascha Wildner 	if (stat(datemsk, &sb) < 0) {
80*8f9e6710SSascha Wildner 		getdate_err = 3;
81*8f9e6710SSascha Wildner 		return (NULL);
82*8f9e6710SSascha Wildner 	}
83*8f9e6710SSascha Wildner 
84*8f9e6710SSascha Wildner 	if ((sb.st_mode & S_IFMT) != S_IFREG) {
85*8f9e6710SSascha Wildner 		getdate_err = 4;
86*8f9e6710SSascha Wildner 		return (NULL);
87*8f9e6710SSascha Wildner 	}
88*8f9e6710SSascha Wildner 
89*8f9e6710SSascha Wildner 	if ((fp = fopen(datemsk, "re")) == NULL) {
90*8f9e6710SSascha Wildner 		getdate_err = 2;
91*8f9e6710SSascha Wildner 		return (NULL);
92*8f9e6710SSascha Wildner 	}
93*8f9e6710SSascha Wildner 
94*8f9e6710SSascha Wildner 	/* loop through datemsk file */
95*8f9e6710SSascha Wildner 	errno = 0;
96*8f9e6710SSascha Wildner 	rp = NULL;
97*8f9e6710SSascha Wildner 	while ((line = fparseln(fp, NULL, &lineno, NULL, 0)) != NULL) {
98*8f9e6710SSascha Wildner 		/* initialize tmp with sentinels */
99*8f9e6710SSascha Wildner 		rtm.tm_sec = rtm.tm_min = rtm.tm_hour = TMSENTINEL;
100*8f9e6710SSascha Wildner 		rtm.tm_mday = rtm.tm_mon = rtm.tm_year = TMSENTINEL;
101*8f9e6710SSascha Wildner 		rtm.tm_wday = rtm.tm_yday = rtm.tm_isdst = TMSENTINEL;
102*8f9e6710SSascha Wildner 		rtm.tm_gmtoff = 0;
103*8f9e6710SSascha Wildner 		rtm.tm_zone = NULL;
104*8f9e6710SSascha Wildner 		rp = strptime(str, line, rtmp);
105*8f9e6710SSascha Wildner 		free(line);
106*8f9e6710SSascha Wildner 		if (rp != NULL)
107*8f9e6710SSascha Wildner 			break;
108*8f9e6710SSascha Wildner 		errno = 0;
109*8f9e6710SSascha Wildner 	}
110*8f9e6710SSascha Wildner 	if (errno != 0 || ferror(fp)) {
111*8f9e6710SSascha Wildner 		if (errno == ENOMEM)
112*8f9e6710SSascha Wildner 			getdate_err = 6;
113*8f9e6710SSascha Wildner 		else
114*8f9e6710SSascha Wildner 			getdate_err = 5;
115*8f9e6710SSascha Wildner 		fclose(fp);
116*8f9e6710SSascha Wildner 		return (NULL);
117*8f9e6710SSascha Wildner 	}
118*8f9e6710SSascha Wildner 	if (feof(fp) || (rp != NULL && *rp != '\0')) {
119*8f9e6710SSascha Wildner 		getdate_err = 7;
120*8f9e6710SSascha Wildner 		return (NULL);
121*8f9e6710SSascha Wildner 	}
122*8f9e6710SSascha Wildner 	fclose(fp);
123*8f9e6710SSascha Wildner 
124*8f9e6710SSascha Wildner 	time(&now);
125*8f9e6710SSascha Wildner 	tmp = localtime(&now);
126*8f9e6710SSascha Wildner 	tmnow = *tmp;
127*8f9e6710SSascha Wildner 
128*8f9e6710SSascha Wildner 	/*
129*8f9e6710SSascha Wildner 	 * This implementation does not accept setting the broken-down time
130*8f9e6710SSascha Wildner 	 * to anything other than the localtime().  It is not possible to
131*8f9e6710SSascha Wildner 	 * change the scanned timezone with %Z.
132*8f9e6710SSascha Wildner 	 *
133*8f9e6710SSascha Wildner 	 * Note IRIX and Solaris accept only the current zone for %Z.
134*8f9e6710SSascha Wildner 	 * XXX Is there any implementation that matches the standard?
135*8f9e6710SSascha Wildner 	 * XXX (Or am I reading the standard wrong?)
136*8f9e6710SSascha Wildner 	 *
137*8f9e6710SSascha Wildner 	 * Note: Neither XPG 6 (POSIX 2004) nor XPG 7 (POSIX 2008)
138*8f9e6710SSascha Wildner 	 * requires strptime(3) support for %Z.
139*8f9e6710SSascha Wildner 	 */
140*8f9e6710SSascha Wildner 
141*8f9e6710SSascha Wildner 	/*
142*8f9e6710SSascha Wildner 	 * Given only a weekday find the first matching weekday starting
143*8f9e6710SSascha Wildner 	 * with the current weekday and moving into the future.
144*8f9e6710SSascha Wildner 	 */
145*8f9e6710SSascha Wildner 	if (rtm.tm_wday != TMSENTINEL && rtm.tm_year == TMSENTINEL &&
146*8f9e6710SSascha Wildner 	    rtm.tm_mon == TMSENTINEL && rtm.tm_mday == TMSENTINEL) {
147*8f9e6710SSascha Wildner 		rtm.tm_year = tmnow.tm_year;
148*8f9e6710SSascha Wildner 		rtm.tm_mon = tmnow.tm_mon;
149*8f9e6710SSascha Wildner 		rtm.tm_mday = tmnow.tm_mday +
150*8f9e6710SSascha Wildner 			(rtm.tm_wday - tmnow.tm_wday + 7) % 7;
151*8f9e6710SSascha Wildner 	}
152*8f9e6710SSascha Wildner 
153*8f9e6710SSascha Wildner 	/*
154*8f9e6710SSascha Wildner 	 * Given only a month (and no year) find the first matching month
155*8f9e6710SSascha Wildner 	 * starting with the current month and moving into the future.
156*8f9e6710SSascha Wildner 	 */
157*8f9e6710SSascha Wildner 	if (rtm.tm_mon != TMSENTINEL) {
158*8f9e6710SSascha Wildner 		if (rtm.tm_year == TMSENTINEL) {
159*8f9e6710SSascha Wildner 			rtm.tm_year = tmnow.tm_year +
160*8f9e6710SSascha Wildner 				((rtm.tm_mon < tmnow.tm_mon)? 1 : 0);
161*8f9e6710SSascha Wildner 		}
162*8f9e6710SSascha Wildner 		if (rtm.tm_mday == TMSENTINEL) {
163*8f9e6710SSascha Wildner 			/* assume the first of the month */
164*8f9e6710SSascha Wildner 			rtm.tm_mday = 1;
165*8f9e6710SSascha Wildner 			/*
166*8f9e6710SSascha Wildner 			 * XXX This isn't documented! Just observed behavior.
167*8f9e6710SSascha Wildner 			 *
168*8f9e6710SSascha Wildner 			 * Given the weekday find the first matching weekday
169*8f9e6710SSascha Wildner 			 * starting with the weekday of the first day of the
170*8f9e6710SSascha Wildner 			 * the month and moving into the future.
171*8f9e6710SSascha Wildner 			 */
172*8f9e6710SSascha Wildner 			if (rtm.tm_wday != TMSENTINEL) {
173*8f9e6710SSascha Wildner 				struct tm tm;
174*8f9e6710SSascha Wildner 
175*8f9e6710SSascha Wildner 				memset(&tm, 0, sizeof(struct tm));
176*8f9e6710SSascha Wildner 				tm.tm_year = rtm.tm_year;
177*8f9e6710SSascha Wildner 				tm.tm_mon = rtm.tm_mon;
178*8f9e6710SSascha Wildner 				tm.tm_mday = 1;
179*8f9e6710SSascha Wildner 				mktime(&tm);
180*8f9e6710SSascha Wildner 				rtm.tm_mday +=
181*8f9e6710SSascha Wildner 					(rtm.tm_wday - tm.tm_wday + 7) % 7;
182*8f9e6710SSascha Wildner 			}
183*8f9e6710SSascha Wildner 		}
184*8f9e6710SSascha Wildner 	}
185*8f9e6710SSascha Wildner 
186*8f9e6710SSascha Wildner 	/*
187*8f9e6710SSascha Wildner 	 * Given no time of day assume the current time of day.
188*8f9e6710SSascha Wildner 	 */
189*8f9e6710SSascha Wildner 	if (rtm.tm_hour == TMSENTINEL &&
190*8f9e6710SSascha Wildner 	    rtm.tm_min == TMSENTINEL && rtm.tm_sec == TMSENTINEL) {
191*8f9e6710SSascha Wildner 		rtm.tm_hour = tmnow.tm_hour;
192*8f9e6710SSascha Wildner 		rtm.tm_min = tmnow.tm_min;
193*8f9e6710SSascha Wildner 		rtm.tm_sec = tmnow.tm_sec;
194*8f9e6710SSascha Wildner 	}
195*8f9e6710SSascha Wildner 	/*
196*8f9e6710SSascha Wildner 	 * Given an hour and no date, find the first matching hour starting
197*8f9e6710SSascha Wildner 	 * with the current hour and moving into the future
198*8f9e6710SSascha Wildner 	 */
199*8f9e6710SSascha Wildner 	if (rtm.tm_hour != TMSENTINEL &&
200*8f9e6710SSascha Wildner 	    rtm.tm_year == TMSENTINEL && rtm.tm_mon == TMSENTINEL &&
201*8f9e6710SSascha Wildner 	    rtm.tm_mday == TMSENTINEL) {
202*8f9e6710SSascha Wildner 		rtm.tm_year = tmnow.tm_year;
203*8f9e6710SSascha Wildner 		rtm.tm_mon = tmnow.tm_mon;
204*8f9e6710SSascha Wildner 		rtm.tm_mday = tmnow.tm_mday;
205*8f9e6710SSascha Wildner 		if (rtm.tm_hour < tmnow.tm_hour)
206*8f9e6710SSascha Wildner 			rtm.tm_hour += 24;
207*8f9e6710SSascha Wildner 	}
208*8f9e6710SSascha Wildner 
209*8f9e6710SSascha Wildner 	/*
210*8f9e6710SSascha Wildner 	 * Set to 'sane' values; mktime(3) does funny things otherwise.
211*8f9e6710SSascha Wildner 	 * No hours, no minutes, no seconds, no service.
212*8f9e6710SSascha Wildner 	 */
213*8f9e6710SSascha Wildner 	if (rtm.tm_hour == TMSENTINEL)
214*8f9e6710SSascha Wildner 		rtm.tm_hour = 0;
215*8f9e6710SSascha Wildner 	if (rtm.tm_min == TMSENTINEL)
216*8f9e6710SSascha Wildner 		rtm.tm_min = 0;
217*8f9e6710SSascha Wildner 	if (rtm.tm_sec == TMSENTINEL)
218*8f9e6710SSascha Wildner 		rtm.tm_sec = 0;
219*8f9e6710SSascha Wildner 
220*8f9e6710SSascha Wildner 	/*
221*8f9e6710SSascha Wildner 	 * Given only a year the values of month, day of month, day of year,
222*8f9e6710SSascha Wildner 	 * week day and is daylight (summer) time are unspecified.
223*8f9e6710SSascha Wildner 	 * (Specified on the Solaris man page not POSIX.)
224*8f9e6710SSascha Wildner 	 */
225*8f9e6710SSascha Wildner 	if (rtm.tm_year != TMSENTINEL &&
226*8f9e6710SSascha Wildner 	    rtm.tm_mon == TMSENTINEL && rtm.tm_mday == TMSENTINEL) {
227*8f9e6710SSascha Wildner 		rtm.tm_mon = 0;
228*8f9e6710SSascha Wildner 		rtm.tm_mday = 1;
229*8f9e6710SSascha Wildner 		/*
230*8f9e6710SSascha Wildner 		 * XXX More undocumented functionality but observed.
231*8f9e6710SSascha Wildner 		 *
232*8f9e6710SSascha Wildner 		 * Given the weekday find the first matching weekday
233*8f9e6710SSascha Wildner 		 * starting with the weekday of the first day of the
234*8f9e6710SSascha Wildner 		 * month and moving into the future.
235*8f9e6710SSascha Wildner 		 */
236*8f9e6710SSascha Wildner 		if (rtm.tm_wday != TMSENTINEL) {
237*8f9e6710SSascha Wildner 			struct tm tm;
238*8f9e6710SSascha Wildner 
239*8f9e6710SSascha Wildner 			memset(&tm, 0, sizeof(struct tm));
240*8f9e6710SSascha Wildner 			tm.tm_year = rtm.tm_year;
241*8f9e6710SSascha Wildner 			tm.tm_mon = rtm.tm_mon;
242*8f9e6710SSascha Wildner 			tm.tm_mday = 1;
243*8f9e6710SSascha Wildner 			mktime(&tm);
244*8f9e6710SSascha Wildner 			rtm.tm_mday += (rtm.tm_wday - tm.tm_wday + 7) % 7;
245*8f9e6710SSascha Wildner 		}
246*8f9e6710SSascha Wildner 	}
247*8f9e6710SSascha Wildner 
248*8f9e6710SSascha Wildner 	/*
249*8f9e6710SSascha Wildner 	 * Given only the century but no year within, the current year
250*8f9e6710SSascha Wildner 	 * is assumed.  (Specified on the Solaris man page not POSIX.)
251*8f9e6710SSascha Wildner 	 *
252*8f9e6710SSascha Wildner 	 * Warning ugly end case
253*8f9e6710SSascha Wildner 	 *
254*8f9e6710SSascha Wildner 	 * This is more work since strptime(3) doesn't "do the right thing".
255*8f9e6710SSascha Wildner 	 */
256*8f9e6710SSascha Wildner 	if (rtm.tm_year != TMSENTINEL && (rtm.tm_year - 1900) >= 0) {
257*8f9e6710SSascha Wildner 		rtm.tm_year -= 1900;
258*8f9e6710SSascha Wildner 		rtm.tm_year += (tmnow.tm_year % 100);
259*8f9e6710SSascha Wildner 	}
260*8f9e6710SSascha Wildner 
261*8f9e6710SSascha Wildner 	/*
262*8f9e6710SSascha Wildner 	 * mktime() will normalize all values and also check that the
263*8f9e6710SSascha Wildner 	 * value will fit into a time_t.
264*8f9e6710SSascha Wildner 	 *
265*8f9e6710SSascha Wildner 	 * This is only for POSIX correctness.	A date >= 1900 is
266*8f9e6710SSascha Wildner 	 * really ok, but using a time_t limits things.
267*8f9e6710SSascha Wildner 	 */
268*8f9e6710SSascha Wildner 	if (mktime(rtmp) < 0) {
269*8f9e6710SSascha Wildner 		getdate_err = 8;
270*8f9e6710SSascha Wildner 		return (NULL);
271*8f9e6710SSascha Wildner 	}
272*8f9e6710SSascha Wildner 
273*8f9e6710SSascha Wildner 	return (rtmp);
274*8f9e6710SSascha Wildner }
275