xref: /minix3/lib/libc/time/getdate.c (revision 0a6a1f1d05b60e214de2f05a7310ddd1f0e590e7)
1*0a6a1f1dSLionel Sambuc /*	$NetBSD: getdate.c,v 1.3 2014/09/18 13:58:20 christos Exp $	*/
22fe8fb19SBen Gras /*
32fe8fb19SBen Gras  * Copyright (c) 2009 The NetBSD Foundation, Inc.
42fe8fb19SBen Gras  * All rights reserved.
52fe8fb19SBen Gras  *
62fe8fb19SBen Gras  * This code is derived from software contributed to The NetBSD Foundation
72fe8fb19SBen Gras  * by Brian Ginsbach.
82fe8fb19SBen Gras  *
92fe8fb19SBen Gras  * Redistribution and use in source and binary forms, with or without
102fe8fb19SBen Gras  * modification, are permitted provided that the following conditions
112fe8fb19SBen Gras  * are met:
122fe8fb19SBen Gras  * 1. Redistributions of source code must retain the above copyright
132fe8fb19SBen Gras  *    notice, this list of conditions and the following disclaimer.
142fe8fb19SBen Gras  * 2. Redistributions in binary form must reproduce the above copyright
152fe8fb19SBen Gras  *    notice, this list of conditions and the following disclaimer in the
162fe8fb19SBen Gras  *    documentation and/or other materials provided with the distribution.
172fe8fb19SBen Gras  *
182fe8fb19SBen Gras  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
192fe8fb19SBen Gras  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
202fe8fb19SBen Gras  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
212fe8fb19SBen Gras  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
222fe8fb19SBen Gras  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
232fe8fb19SBen Gras  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
242fe8fb19SBen Gras  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
252fe8fb19SBen Gras  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
262fe8fb19SBen Gras  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
272fe8fb19SBen Gras  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
282fe8fb19SBen Gras  * POSSIBILITY OF SUCH DAMAGE.
292fe8fb19SBen Gras  */
302fe8fb19SBen Gras 
312fe8fb19SBen Gras #include <sys/stat.h>
322fe8fb19SBen Gras 
332fe8fb19SBen Gras #include <errno.h>
342fe8fb19SBen Gras #include <stdio.h>
352fe8fb19SBen Gras #include <stdlib.h>
362fe8fb19SBen Gras #include <string.h>
372fe8fb19SBen Gras #include <time.h>
382fe8fb19SBen Gras #include <unistd.h>
392fe8fb19SBen Gras #include <util.h>
402fe8fb19SBen Gras 
412fe8fb19SBen Gras #define TMSENTINEL	(-1)
422fe8fb19SBen Gras 
432fe8fb19SBen Gras /*
442fe8fb19SBen Gras  * getdate_err is set to one of the following values on error.
452fe8fb19SBen Gras  *
462fe8fb19SBen Gras  * 1	The DATEMSK environment variable is null or undefined.
472fe8fb19SBen Gras  * 2	The template file cannot be opened for reading.
482fe8fb19SBen Gras  * 3	Failed to get file status information.
492fe8fb19SBen Gras  * 4	Template file is not a regular file.
502fe8fb19SBen Gras  * 5	Encountered an error while reading the template file.
512fe8fb19SBen Gras  * 6	Cannot allocate memory.
522fe8fb19SBen Gras  * 7	Input string does not match any line in the template.
532fe8fb19SBen Gras  * 8	Input string is invalid (for example, February 31) or could not
542fe8fb19SBen Gras  *	be represented in a time_t.
552fe8fb19SBen Gras  */
562fe8fb19SBen Gras 
572fe8fb19SBen Gras int getdate_err;
582fe8fb19SBen Gras 
592fe8fb19SBen Gras struct tm *
getdate(const char * str)602fe8fb19SBen Gras getdate(const char *str)
612fe8fb19SBen Gras {
622fe8fb19SBen Gras 	char *datemsk, *line, *rp;
632fe8fb19SBen Gras 	FILE *fp;
642fe8fb19SBen Gras 	struct stat sb;
652fe8fb19SBen Gras 	static struct tm rtm, tmnow;
662fe8fb19SBen Gras 	struct tm *tmp, *rtmp = &rtm;
672fe8fb19SBen Gras 	size_t lineno = 0;
682fe8fb19SBen Gras 	time_t now;
692fe8fb19SBen Gras 
702fe8fb19SBen Gras 	if (((datemsk = getenv("DATEMSK")) == NULL) || *datemsk == '\0') {
712fe8fb19SBen Gras 		getdate_err = 1;
722fe8fb19SBen Gras 		return (NULL);
732fe8fb19SBen Gras 	}
742fe8fb19SBen Gras 
752fe8fb19SBen Gras 	if (stat(datemsk, &sb) < 0) {
762fe8fb19SBen Gras 		getdate_err = 3;
772fe8fb19SBen Gras 		return (NULL);
782fe8fb19SBen Gras 	}
792fe8fb19SBen Gras 
802fe8fb19SBen Gras 	if ((sb.st_mode & S_IFMT) != S_IFREG) {
812fe8fb19SBen Gras 		getdate_err = 4;
822fe8fb19SBen Gras 		return (NULL);
832fe8fb19SBen Gras 	}
842fe8fb19SBen Gras 
85*0a6a1f1dSLionel Sambuc 	if ((fp = fopen(datemsk, "re")) == NULL) {
862fe8fb19SBen Gras 		getdate_err = 2;
872fe8fb19SBen Gras 		return (NULL);
882fe8fb19SBen Gras 	}
892fe8fb19SBen Gras 
902fe8fb19SBen Gras 	/* loop through datemsk file */
912fe8fb19SBen Gras 	errno = 0;
922fe8fb19SBen Gras 	rp = NULL;
932fe8fb19SBen Gras 	while ((line = fparseln(fp, NULL, &lineno, NULL, 0)) != NULL) {
942fe8fb19SBen Gras 		/* initialize tmp with sentinels */
952fe8fb19SBen Gras 		rtm.tm_sec = rtm.tm_min = rtm.tm_hour = TMSENTINEL;
962fe8fb19SBen Gras 		rtm.tm_mday = rtm.tm_mon = rtm.tm_year = TMSENTINEL;
972fe8fb19SBen Gras 		rtm.tm_wday = rtm.tm_yday = rtm.tm_isdst = TMSENTINEL;
982fe8fb19SBen Gras 		rtm.tm_gmtoff = 0;
992fe8fb19SBen Gras 		rtm.tm_zone = NULL;
1002fe8fb19SBen Gras 		rp = strptime(str, line, rtmp);
1012fe8fb19SBen Gras 		free(line);
1022fe8fb19SBen Gras 		if (rp != NULL)
1032fe8fb19SBen Gras 			break;
1042fe8fb19SBen Gras 		errno = 0;
1052fe8fb19SBen Gras 	}
1062fe8fb19SBen Gras 	if (errno != 0 || ferror(fp)) {
1072fe8fb19SBen Gras 		if (errno == ENOMEM)
1082fe8fb19SBen Gras 			getdate_err = 6;
1092fe8fb19SBen Gras 		else
1102fe8fb19SBen Gras 			getdate_err = 5;
1112fe8fb19SBen Gras 		fclose(fp);
1122fe8fb19SBen Gras 		return (NULL);
1132fe8fb19SBen Gras 	}
1142fe8fb19SBen Gras 	if (feof(fp) || (rp != NULL && *rp != '\0')) {
1152fe8fb19SBen Gras 		getdate_err = 7;
1162fe8fb19SBen Gras 		return (NULL);
1172fe8fb19SBen Gras 	}
1182fe8fb19SBen Gras 	fclose(fp);
1192fe8fb19SBen Gras 
1202fe8fb19SBen Gras 	time(&now);
1212fe8fb19SBen Gras 	tmp = localtime(&now);
1222fe8fb19SBen Gras 	tmnow = *tmp;
1232fe8fb19SBen Gras 
1242fe8fb19SBen Gras 	/*
1252fe8fb19SBen Gras 	 * This implementation does not accept setting the broken-down time
1262fe8fb19SBen Gras 	 * to anything other than the localtime().  It is not possible to
1272fe8fb19SBen Gras 	 * change the scanned timezone with %Z.
1282fe8fb19SBen Gras 	 *
1292fe8fb19SBen Gras 	 * Note IRIX and Solaris accept only the current zone for %Z.
1302fe8fb19SBen Gras 	 * XXX Is there any implementation that matches the standard?
1312fe8fb19SBen Gras 	 * XXX (Or am I reading the standard wrong?)
1322fe8fb19SBen Gras 	 *
1332fe8fb19SBen Gras 	 * Note: Neither XPG 6 (POSIX 2004) nor XPG 7 (POSIX 2008)
1342fe8fb19SBen Gras 	 * requires strptime(3) support for %Z.
1352fe8fb19SBen Gras 	 */
1362fe8fb19SBen Gras 
1372fe8fb19SBen Gras 	/*
1382fe8fb19SBen Gras 	 * Given only a weekday find the first matching weekday starting
1392fe8fb19SBen Gras 	 * with the current weekday and moving into the future.
1402fe8fb19SBen Gras 	 */
1412fe8fb19SBen Gras 	if (rtm.tm_wday != TMSENTINEL && rtm.tm_year == TMSENTINEL &&
1422fe8fb19SBen Gras 	    rtm.tm_mon == TMSENTINEL && rtm.tm_mday == TMSENTINEL) {
1432fe8fb19SBen Gras 		rtm.tm_year = tmnow.tm_year;
1442fe8fb19SBen Gras 		rtm.tm_mon = tmnow.tm_mon;
1452fe8fb19SBen Gras 		rtm.tm_mday = tmnow.tm_mday +
1462fe8fb19SBen Gras 			(rtm.tm_wday - tmnow.tm_wday + 7) % 7;
1472fe8fb19SBen Gras 	}
1482fe8fb19SBen Gras 
1492fe8fb19SBen Gras 	/*
1502fe8fb19SBen Gras 	 * Given only a month (and no year) find the first matching month
1512fe8fb19SBen Gras 	 * starting with the current month and moving into the future.
1522fe8fb19SBen Gras 	 */
1532fe8fb19SBen Gras 	if (rtm.tm_mon != TMSENTINEL) {
1542fe8fb19SBen Gras 		if (rtm.tm_year == TMSENTINEL) {
1552fe8fb19SBen Gras 			rtm.tm_year = tmnow.tm_year +
1562fe8fb19SBen Gras 				((rtm.tm_mon < tmnow.tm_mon)? 1 : 0);
1572fe8fb19SBen Gras 		}
1582fe8fb19SBen Gras 		if (rtm.tm_mday == TMSENTINEL) {
1592fe8fb19SBen Gras 			/* assume the first of the month */
1602fe8fb19SBen Gras 			rtm.tm_mday = 1;
1612fe8fb19SBen Gras 			/*
1622fe8fb19SBen Gras 			 * XXX This isn't documented! Just observed behavior.
1632fe8fb19SBen Gras 			 *
1642fe8fb19SBen Gras 			 * Given the weekday find the first matching weekday
1652fe8fb19SBen Gras 			 * starting with the weekday of the first day of the
1662fe8fb19SBen Gras 			 * the month and moving into the future.
1672fe8fb19SBen Gras 			 */
1682fe8fb19SBen Gras 			if (rtm.tm_wday != TMSENTINEL) {
1692fe8fb19SBen Gras 				struct tm tm;
1702fe8fb19SBen Gras 
1712fe8fb19SBen Gras 				memset(&tm, 0, sizeof(struct tm));
1722fe8fb19SBen Gras 				tm.tm_year = rtm.tm_year;
1732fe8fb19SBen Gras 				tm.tm_mon = rtm.tm_mon;
1742fe8fb19SBen Gras 				tm.tm_mday = 1;
1752fe8fb19SBen Gras 				mktime(&tm);
1762fe8fb19SBen Gras 				rtm.tm_mday +=
1772fe8fb19SBen Gras 					(rtm.tm_wday - tm.tm_wday + 7) % 7;
1782fe8fb19SBen Gras 			}
1792fe8fb19SBen Gras 		}
1802fe8fb19SBen Gras 	}
1812fe8fb19SBen Gras 
1822fe8fb19SBen Gras 	/*
1832fe8fb19SBen Gras 	 * Given no time of day assume the current time of day.
1842fe8fb19SBen Gras 	 */
1852fe8fb19SBen Gras 	if (rtm.tm_hour == TMSENTINEL &&
1862fe8fb19SBen Gras 	    rtm.tm_min == TMSENTINEL && rtm.tm_sec == TMSENTINEL) {
1872fe8fb19SBen Gras 		rtm.tm_hour = tmnow.tm_hour;
1882fe8fb19SBen Gras 		rtm.tm_min = tmnow.tm_min;
1892fe8fb19SBen Gras 		rtm.tm_sec = tmnow.tm_sec;
1902fe8fb19SBen Gras 	}
1912fe8fb19SBen Gras 	/*
1922fe8fb19SBen Gras 	 * Given an hour and no date, find the first matching hour starting
1932fe8fb19SBen Gras 	 * with the current hour and moving into the future
1942fe8fb19SBen Gras 	 */
1952fe8fb19SBen Gras 	if (rtm.tm_hour != TMSENTINEL &&
1962fe8fb19SBen Gras 	    rtm.tm_year == TMSENTINEL && rtm.tm_mon == TMSENTINEL &&
1972fe8fb19SBen Gras 	    rtm.tm_mday == TMSENTINEL) {
1982fe8fb19SBen Gras 		rtm.tm_year = tmnow.tm_year;
1992fe8fb19SBen Gras 		rtm.tm_mon = tmnow.tm_mon;
2002fe8fb19SBen Gras 		rtm.tm_mday = tmnow.tm_mday;
2012fe8fb19SBen Gras 		if (rtm.tm_hour < tmnow.tm_hour)
2022fe8fb19SBen Gras 			rtm.tm_hour += 24;
2032fe8fb19SBen Gras 	}
2042fe8fb19SBen Gras 
2052fe8fb19SBen Gras 	/*
2062fe8fb19SBen Gras 	 * Set to 'sane' values; mktime(3) does funny things otherwise.
2072fe8fb19SBen Gras 	 * No hours, no minutes, no seconds, no service.
2082fe8fb19SBen Gras 	 */
2092fe8fb19SBen Gras 	if (rtm.tm_hour == TMSENTINEL)
2102fe8fb19SBen Gras 		rtm.tm_hour = 0;
2112fe8fb19SBen Gras 	if (rtm.tm_min == TMSENTINEL)
2122fe8fb19SBen Gras 		rtm.tm_min = 0;
2132fe8fb19SBen Gras 	if (rtm.tm_sec == TMSENTINEL)
2142fe8fb19SBen Gras 		rtm.tm_sec = 0;
2152fe8fb19SBen Gras 
2162fe8fb19SBen Gras 	/*
2172fe8fb19SBen Gras 	 * Given only a year the values of month, day of month, day of year,
2182fe8fb19SBen Gras 	 * week day and is daylight (summer) time are unspecified.
2192fe8fb19SBen Gras 	 * (Specified on the Solaris man page not POSIX.)
2202fe8fb19SBen Gras 	 */
2212fe8fb19SBen Gras 	if (rtm.tm_year != TMSENTINEL &&
2222fe8fb19SBen Gras 	    rtm.tm_mon == TMSENTINEL && rtm.tm_mday == TMSENTINEL) {
2232fe8fb19SBen Gras 		rtm.tm_mon = 0;
2242fe8fb19SBen Gras 		rtm.tm_mday = 1;
2252fe8fb19SBen Gras 		/*
2262fe8fb19SBen Gras 		 * XXX More undocumented functionality but observed.
2272fe8fb19SBen Gras 		 *
2282fe8fb19SBen Gras 		 * Given the weekday find the first matching weekday
2292fe8fb19SBen Gras 		 * starting with the weekday of the first day of the
2302fe8fb19SBen Gras 		 * month and moving into the future.
2312fe8fb19SBen Gras 		 */
2322fe8fb19SBen Gras 		if (rtm.tm_wday != TMSENTINEL) {
2332fe8fb19SBen Gras 			struct tm tm;
2342fe8fb19SBen Gras 
2352fe8fb19SBen Gras 			memset(&tm, 0, sizeof(struct tm));
2362fe8fb19SBen Gras 			tm.tm_year = rtm.tm_year;
2372fe8fb19SBen Gras 			tm.tm_mon = rtm.tm_mon;
2382fe8fb19SBen Gras 			tm.tm_mday = 1;
2392fe8fb19SBen Gras 			mktime(&tm);
2402fe8fb19SBen Gras 			rtm.tm_mday += (rtm.tm_wday - tm.tm_wday + 7) % 7;
2412fe8fb19SBen Gras 		}
2422fe8fb19SBen Gras 	}
2432fe8fb19SBen Gras 
2442fe8fb19SBen Gras 	/*
2452fe8fb19SBen Gras 	 * Given only the century but no year within, the current year
2462fe8fb19SBen Gras 	 * is assumed.  (Specified on the Solaris man page not POSIX.)
2472fe8fb19SBen Gras 	 *
2482fe8fb19SBen Gras 	 * Warning ugly end case
2492fe8fb19SBen Gras 	 *
2502fe8fb19SBen Gras 	 * This is more work since strptime(3) doesn't "do the right thing".
2512fe8fb19SBen Gras 	 */
2522fe8fb19SBen Gras 	if (rtm.tm_year != TMSENTINEL && (rtm.tm_year - 1900) >= 0) {
2532fe8fb19SBen Gras 		rtm.tm_year -= 1900;
2542fe8fb19SBen Gras 		rtm.tm_year += (tmnow.tm_year % 100);
2552fe8fb19SBen Gras 	}
2562fe8fb19SBen Gras 
2572fe8fb19SBen Gras 	/*
2582fe8fb19SBen Gras 	 * mktime() will normalize all values and also check that the
2592fe8fb19SBen Gras 	 * value will fit into a time_t.
2602fe8fb19SBen Gras 	 *
2612fe8fb19SBen Gras 	 * This is only for POSIX correctness.	A date >= 1900 is
2622fe8fb19SBen Gras 	 * really ok, but using a time_t limits things.
2632fe8fb19SBen Gras 	 */
2642fe8fb19SBen Gras 	if (mktime(rtmp) < 0) {
2652fe8fb19SBen Gras 		getdate_err = 8;
2662fe8fb19SBen Gras 		return (NULL);
2672fe8fb19SBen Gras 	}
2682fe8fb19SBen Gras 
2692fe8fb19SBen Gras 	return (rtmp);
2702fe8fb19SBen Gras }
271