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