xref: /illumos-gate/usr/src/lib/libc/port/locale/getdate.c (revision 6b5e5868)
14297a3b0SGarrett D'Amore /*
2*6b5e5868SGarrett D'Amore  * Copyright 2010 Nexenta Systems, Inc.  All rights reserved.
34297a3b0SGarrett D'Amore  * Copyright (c) 2009 The NetBSD Foundation, Inc.
44297a3b0SGarrett D'Amore  * All rights reserved.
54297a3b0SGarrett D'Amore  *
64297a3b0SGarrett D'Amore  * This code is derived from software contributed to The NetBSD Foundation
74297a3b0SGarrett D'Amore  * by Brian Ginsbach.
84297a3b0SGarrett D'Amore  *
94297a3b0SGarrett D'Amore  * Redistribution and use in source and binary forms, with or without
104297a3b0SGarrett D'Amore  * modification, are permitted provided that the following conditions
114297a3b0SGarrett D'Amore  * are met:
124297a3b0SGarrett D'Amore  * 1. Redistributions of source code must retain the above copyright
134297a3b0SGarrett D'Amore  *    notice, this list of conditions and the following disclaimer.
144297a3b0SGarrett D'Amore  * 2. Redistributions in binary form must reproduce the above copyright
154297a3b0SGarrett D'Amore  *    notice, this list of conditions and the following disclaimer in the
164297a3b0SGarrett D'Amore  *    documentation and/or other materials provided with the distribution.
174297a3b0SGarrett D'Amore  *
184297a3b0SGarrett D'Amore  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
194297a3b0SGarrett D'Amore  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
204297a3b0SGarrett D'Amore  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
214297a3b0SGarrett D'Amore  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
224297a3b0SGarrett D'Amore  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
234297a3b0SGarrett D'Amore  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
244297a3b0SGarrett D'Amore  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
254297a3b0SGarrett D'Amore  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
264297a3b0SGarrett D'Amore  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
274297a3b0SGarrett D'Amore  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
284297a3b0SGarrett D'Amore  * POSSIBILITY OF SUCH DAMAGE.
294297a3b0SGarrett D'Amore  */
304297a3b0SGarrett D'Amore 
314297a3b0SGarrett D'Amore #include "lint.h"
324297a3b0SGarrett D'Amore #include "file64.h"
334297a3b0SGarrett D'Amore #include <sys/types.h>
344297a3b0SGarrett D'Amore #include <errno.h>
354297a3b0SGarrett D'Amore #include <stdio.h>
364297a3b0SGarrett D'Amore #include <stdlib.h>
374297a3b0SGarrett D'Amore #include <string.h>
384297a3b0SGarrett D'Amore #include <time.h>
394297a3b0SGarrett D'Amore #include <unistd.h>
404297a3b0SGarrett D'Amore #include <sys/stat.h>
414297a3b0SGarrett D'Amore 
424297a3b0SGarrett D'Amore #define	TMSENTINEL	(-1)
434297a3b0SGarrett D'Amore 
444297a3b0SGarrett D'Amore 
454297a3b0SGarrett D'Amore /*
464297a3b0SGarrett D'Amore  * getdate_err is set to one of the following values on error.
474297a3b0SGarrett D'Amore  *
484297a3b0SGarrett D'Amore  * 1	The DATEMSK environment variable is null or undefined.
494297a3b0SGarrett D'Amore  * 2	The template file cannot be opened for reading.
504297a3b0SGarrett D'Amore  * 3	Failed to get file status information.
514297a3b0SGarrett D'Amore  * 4	Template file is not a regular file.
524297a3b0SGarrett D'Amore  * 5	Encountered an error while reading the template file.
534297a3b0SGarrett D'Amore  * 6	Cannot allocate memory.
544297a3b0SGarrett D'Amore  * 7	Input string does not match any line in the template.
554297a3b0SGarrett D'Amore  * 8	Input string is invalid (for example, February 31) or could not
564297a3b0SGarrett D'Amore  *	be represented in a time_t.
574297a3b0SGarrett D'Amore  *
584297a3b0SGarrett D'Amore  * Note that on Solaris, getdate_err is possibly a function, to account
594297a3b0SGarrett D'Amore  * for reentrancy.  See the code for getdate_err.c for details.
604297a3b0SGarrett D'Amore  */
614297a3b0SGarrett D'Amore 
624297a3b0SGarrett D'Amore 
634297a3b0SGarrett D'Amore #pragma	weak	_getdate = getdate
644297a3b0SGarrett D'Amore struct tm *
getdate(const char * str)654297a3b0SGarrett D'Amore getdate(const char *str)
664297a3b0SGarrett D'Amore {
674297a3b0SGarrett D'Amore 	char *datemsk, *line, *rp;
684297a3b0SGarrett D'Amore 	FILE *fp;
694297a3b0SGarrett D'Amore 	struct stat sb;
704297a3b0SGarrett D'Amore 	static struct tm rtm, tmnow;
714297a3b0SGarrett D'Amore 	struct tm *tmp, *rtmp = &rtm;
724297a3b0SGarrett D'Amore 	time_t now;
734297a3b0SGarrett D'Amore 	char buf[514];
744297a3b0SGarrett D'Amore 
754297a3b0SGarrett D'Amore 	if (((datemsk = getenv("DATEMSK")) == NULL) || *datemsk == '\0') {
764297a3b0SGarrett D'Amore 		getdate_err = 1;
774297a3b0SGarrett D'Amore 		return (NULL);
784297a3b0SGarrett D'Amore 	}
794297a3b0SGarrett D'Amore 
804297a3b0SGarrett D'Amore 	if (stat(datemsk, &sb) < 0) {
814297a3b0SGarrett D'Amore 		getdate_err = 3;
824297a3b0SGarrett D'Amore 		return (NULL);
834297a3b0SGarrett D'Amore 	}
844297a3b0SGarrett D'Amore 
854297a3b0SGarrett D'Amore 	if ((sb.st_mode & S_IFMT) != S_IFREG) {
864297a3b0SGarrett D'Amore 		getdate_err = 4;
874297a3b0SGarrett D'Amore 		return (NULL);
884297a3b0SGarrett D'Amore 	}
894297a3b0SGarrett D'Amore 
904297a3b0SGarrett D'Amore 	if ((fp = fopen(datemsk, "r")) == NULL) {
914297a3b0SGarrett D'Amore 		getdate_err = 2;
924297a3b0SGarrett D'Amore 		return (NULL);
934297a3b0SGarrett D'Amore 	}
944297a3b0SGarrett D'Amore 
954297a3b0SGarrett D'Amore 	/* loop through datemsk file */
964297a3b0SGarrett D'Amore 	errno = 0;
974297a3b0SGarrett D'Amore 	rp = NULL;
984297a3b0SGarrett D'Amore 
994297a3b0SGarrett D'Amore 	/*
1004297a3b0SGarrett D'Amore 	 * The NetBSD implementation supports a rich flexible file format
1014297a3b0SGarrett D'Amore 	 * with embedded escapes, etc.  We don't need any of that.  Solaris
1024297a3b0SGarrett D'Amore 	 * just reads the template file and (undocumented!) requires that
1034297a3b0SGarrett D'Amore 	 * each line not exceed 512 bytes, using a fixed buffer.   We could
1044297a3b0SGarrett D'Amore 	 * improve on that, but this may grow the stack unreasonably, so
1054297a3b0SGarrett D'Amore 	 * we keep it to the same 512 limit.  Some day we can be smarter.
1064297a3b0SGarrett D'Amore 	 * (Note FreeBSD doesn't even have getdate(), and IMO nobody sane
1074297a3b0SGarrett D'Amore 	 * should be using this crufty API.  strptime is better.)
1084297a3b0SGarrett D'Amore 	 */
1094297a3b0SGarrett D'Amore 
1104297a3b0SGarrett D'Amore 	(void) memset(buf, 0, sizeof (buf));
1114297a3b0SGarrett D'Amore 	while ((line = fgets(buf, sizeof (buf), fp)) != NULL) {
1124297a3b0SGarrett D'Amore 		/*
1134297a3b0SGarrett D'Amore 		 * If the buffer consumed the entire string, then
1144297a3b0SGarrett D'Amore 		 * the input line was too long.  We just check to
1154297a3b0SGarrett D'Amore 		 * see if the 2nd to last byte is set.  If it isn't,
1164297a3b0SGarrett D'Amore 		 * then we hit a null byte first, and the line is
1174297a3b0SGarrett D'Amore 		 * short enough.
1184297a3b0SGarrett D'Amore 		 */
1194297a3b0SGarrett D'Amore 		if (buf[sizeof (buf) - 2] != 0) {
1204297a3b0SGarrett D'Amore 			getdate_err = 5;
1214297a3b0SGarrett D'Amore 			(void) fclose(fp);
1224297a3b0SGarrett D'Amore 			return (NULL);
1234297a3b0SGarrett D'Amore 		}
1244297a3b0SGarrett D'Amore 		/* initialize tmp with sentinels */
1254297a3b0SGarrett D'Amore 		rtm.tm_sec = rtm.tm_min = rtm.tm_hour = TMSENTINEL;
1264297a3b0SGarrett D'Amore 		rtm.tm_mday = rtm.tm_mon = rtm.tm_year = TMSENTINEL;
1274297a3b0SGarrett D'Amore 		rtm.tm_wday = rtm.tm_yday = rtm.tm_isdst = TMSENTINEL;
1284297a3b0SGarrett D'Amore 		rp = strptime(str, line, rtmp);
1294297a3b0SGarrett D'Amore 		if (rp != NULL)
1304297a3b0SGarrett D'Amore 			break;
1314297a3b0SGarrett D'Amore 		errno = 0;
1324297a3b0SGarrett D'Amore 	}
1334297a3b0SGarrett D'Amore 	if (errno != 0 || ferror(fp)) {
1344297a3b0SGarrett D'Amore 		if (errno == ENOMEM)
1354297a3b0SGarrett D'Amore 			getdate_err = 6;
1364297a3b0SGarrett D'Amore 		else
1374297a3b0SGarrett D'Amore 			getdate_err = 5;
1384297a3b0SGarrett D'Amore 		(void) fclose(fp);
1394297a3b0SGarrett D'Amore 		return (NULL);
1404297a3b0SGarrett D'Amore 	}
1414297a3b0SGarrett D'Amore 	if (feof(fp) || (rp != NULL && *rp != '\0')) {
1424297a3b0SGarrett D'Amore 		getdate_err = 7;
1434297a3b0SGarrett D'Amore 		return (NULL);
1444297a3b0SGarrett D'Amore 	}
1454297a3b0SGarrett D'Amore 	(void) fclose(fp);
1464297a3b0SGarrett D'Amore 
1474297a3b0SGarrett D'Amore 	(void) time(&now);
1484297a3b0SGarrett D'Amore 	tmp = localtime(&now);
1494297a3b0SGarrett D'Amore 	tmnow = *tmp;
1504297a3b0SGarrett D'Amore 
1514297a3b0SGarrett D'Amore 	/*
1524297a3b0SGarrett D'Amore 	 * This implementation does not accept setting the broken-down time
1534297a3b0SGarrett D'Amore 	 * to anything other than the localtime().  It is not possible to
1544297a3b0SGarrett D'Amore 	 * change the scanned timezone with %Z.
1554297a3b0SGarrett D'Amore 	 *
1564297a3b0SGarrett D'Amore 	 * Note IRIX and Solaris accept only the current zone for %Z.
1574297a3b0SGarrett D'Amore 	 * XXX Is there any implementation that matches the standard?
1584297a3b0SGarrett D'Amore 	 * XXX (Or am I reading the standard wrong?)
1594297a3b0SGarrett D'Amore 	 *
1604297a3b0SGarrett D'Amore 	 * Note: Neither XPG 6 (POSIX 2004) nor XPG 7 (POSIX 2008)
1614297a3b0SGarrett D'Amore 	 * requires strptime(3) support for %Z.
1624297a3b0SGarrett D'Amore 	 */
1634297a3b0SGarrett D'Amore 
1644297a3b0SGarrett D'Amore 	/*
1654297a3b0SGarrett D'Amore 	 * Given only a weekday find the first matching weekday starting
1664297a3b0SGarrett D'Amore 	 * with the current weekday and moving into the future.
1674297a3b0SGarrett D'Amore 	 */
1684297a3b0SGarrett D'Amore 	if (rtm.tm_wday != TMSENTINEL && rtm.tm_year == TMSENTINEL &&
1694297a3b0SGarrett D'Amore 	    rtm.tm_mon == TMSENTINEL && rtm.tm_mday == TMSENTINEL) {
1704297a3b0SGarrett D'Amore 		rtm.tm_year = tmnow.tm_year;
1714297a3b0SGarrett D'Amore 		rtm.tm_mon = tmnow.tm_mon;
1724297a3b0SGarrett D'Amore 		rtm.tm_mday = tmnow.tm_mday +
1734297a3b0SGarrett D'Amore 		    (rtm.tm_wday - tmnow.tm_wday + 7) % 7;
1744297a3b0SGarrett D'Amore 	}
1754297a3b0SGarrett D'Amore 
1764297a3b0SGarrett D'Amore 	/*
1774297a3b0SGarrett D'Amore 	 * Given only a month (and no year) find the first matching month
1784297a3b0SGarrett D'Amore 	 * starting with the current month and moving into the future.
1794297a3b0SGarrett D'Amore 	 */
1804297a3b0SGarrett D'Amore 	if (rtm.tm_mon != TMSENTINEL) {
1814297a3b0SGarrett D'Amore 		if (rtm.tm_year == TMSENTINEL) {
1824297a3b0SGarrett D'Amore 			rtm.tm_year = tmnow.tm_year +
1834297a3b0SGarrett D'Amore 			    ((rtm.tm_mon < tmnow.tm_mon)? 1 : 0);
1844297a3b0SGarrett D'Amore 		}
1854297a3b0SGarrett D'Amore 		if (rtm.tm_mday == TMSENTINEL) {
1864297a3b0SGarrett D'Amore 			/* assume the first of the month */
1874297a3b0SGarrett D'Amore 			rtm.tm_mday = 1;
1884297a3b0SGarrett D'Amore 			/*
1894297a3b0SGarrett D'Amore 			 * XXX This isn't documented! Just observed behavior.
1904297a3b0SGarrett D'Amore 			 *
1914297a3b0SGarrett D'Amore 			 * Given the weekday find the first matching weekday
1924297a3b0SGarrett D'Amore 			 * starting with the weekday of the first day of the
1934297a3b0SGarrett D'Amore 			 * the month and moving into the future.
1944297a3b0SGarrett D'Amore 			 */
1954297a3b0SGarrett D'Amore 			if (rtm.tm_wday != TMSENTINEL) {
1964297a3b0SGarrett D'Amore 				struct tm tm;
1974297a3b0SGarrett D'Amore 
1984297a3b0SGarrett D'Amore 				(void) memset(&tm, 0, sizeof (struct tm));
1994297a3b0SGarrett D'Amore 				tm.tm_year = rtm.tm_year;
2004297a3b0SGarrett D'Amore 				tm.tm_mon = rtm.tm_mon;
2014297a3b0SGarrett D'Amore 				tm.tm_mday = 1;
2024297a3b0SGarrett D'Amore 				(void) mktime(&tm);
2034297a3b0SGarrett D'Amore 				rtm.tm_mday +=
2044297a3b0SGarrett D'Amore 				    (rtm.tm_wday - tm.tm_wday + 7) % 7;
2054297a3b0SGarrett D'Amore 			}
2064297a3b0SGarrett D'Amore 		}
2074297a3b0SGarrett D'Amore 	}
2084297a3b0SGarrett D'Amore 
2094297a3b0SGarrett D'Amore 	/*
2104297a3b0SGarrett D'Amore 	 * Given no time of day assume the current time of day.
2114297a3b0SGarrett D'Amore 	 */
2124297a3b0SGarrett D'Amore 	if (rtm.tm_hour == TMSENTINEL &&
2134297a3b0SGarrett D'Amore 	    rtm.tm_min == TMSENTINEL && rtm.tm_sec == TMSENTINEL) {
2144297a3b0SGarrett D'Amore 		rtm.tm_hour = tmnow.tm_hour;
2154297a3b0SGarrett D'Amore 		rtm.tm_min = tmnow.tm_min;
2164297a3b0SGarrett D'Amore 		rtm.tm_sec = tmnow.tm_sec;
2174297a3b0SGarrett D'Amore 	}
2184297a3b0SGarrett D'Amore 	/*
2194297a3b0SGarrett D'Amore 	 * Given an hour and no date, find the first matching hour starting
2204297a3b0SGarrett D'Amore 	 * with the current hour and moving into the future
2214297a3b0SGarrett D'Amore 	 */
2224297a3b0SGarrett D'Amore 	if (rtm.tm_hour != TMSENTINEL &&
2234297a3b0SGarrett D'Amore 	    rtm.tm_year == TMSENTINEL && rtm.tm_mon == TMSENTINEL &&
2244297a3b0SGarrett D'Amore 	    rtm.tm_mday == TMSENTINEL) {
2254297a3b0SGarrett D'Amore 		rtm.tm_year = tmnow.tm_year;
2264297a3b0SGarrett D'Amore 		rtm.tm_mon = tmnow.tm_mon;
2274297a3b0SGarrett D'Amore 		rtm.tm_mday = tmnow.tm_mday;
2284297a3b0SGarrett D'Amore 		if (rtm.tm_hour < tmnow.tm_hour)
2294297a3b0SGarrett D'Amore 			rtm.tm_hour += 24;
2304297a3b0SGarrett D'Amore 	}
2314297a3b0SGarrett D'Amore 
2324297a3b0SGarrett D'Amore 	/*
2334297a3b0SGarrett D'Amore 	 * Set to 'sane' values; mktime(3) does funny things otherwise.
2344297a3b0SGarrett D'Amore 	 * No hours, no minutes, no seconds, no service.
2354297a3b0SGarrett D'Amore 	 */
2364297a3b0SGarrett D'Amore 	if (rtm.tm_hour == TMSENTINEL)
2374297a3b0SGarrett D'Amore 		rtm.tm_hour = 0;
2384297a3b0SGarrett D'Amore 	if (rtm.tm_min == TMSENTINEL)
2394297a3b0SGarrett D'Amore 		rtm.tm_min = 0;
2404297a3b0SGarrett D'Amore 	if (rtm.tm_sec == TMSENTINEL)
2414297a3b0SGarrett D'Amore 		rtm.tm_sec = 0;
2424297a3b0SGarrett D'Amore 
2434297a3b0SGarrett D'Amore 	/*
2444297a3b0SGarrett D'Amore 	 * Given only a year the values of month, day of month, day of year,
2454297a3b0SGarrett D'Amore 	 * week day and is daylight (summer) time are unspecified.
2464297a3b0SGarrett D'Amore 	 * (Specified on the Solaris man page not POSIX.)
2474297a3b0SGarrett D'Amore 	 */
2484297a3b0SGarrett D'Amore 	if (rtm.tm_year != TMSENTINEL &&
2494297a3b0SGarrett D'Amore 	    rtm.tm_mon == TMSENTINEL && rtm.tm_mday == TMSENTINEL) {
2504297a3b0SGarrett D'Amore 		rtm.tm_mon = 0;
2514297a3b0SGarrett D'Amore 		rtm.tm_mday = 1;
2524297a3b0SGarrett D'Amore 		/*
2534297a3b0SGarrett D'Amore 		 * XXX More undocumented functionality but observed.
2544297a3b0SGarrett D'Amore 		 *
2554297a3b0SGarrett D'Amore 		 * Given the weekday find the first matching weekday
2564297a3b0SGarrett D'Amore 		 * starting with the weekday of the first day of the
2574297a3b0SGarrett D'Amore 		 * month and moving into the future.
2584297a3b0SGarrett D'Amore 		 */
2594297a3b0SGarrett D'Amore 		if (rtm.tm_wday != TMSENTINEL) {
2604297a3b0SGarrett D'Amore 			struct tm tm;
2614297a3b0SGarrett D'Amore 
2624297a3b0SGarrett D'Amore 			(void) memset(&tm, 0, sizeof (struct tm));
2634297a3b0SGarrett D'Amore 			tm.tm_year = rtm.tm_year;
2644297a3b0SGarrett D'Amore 			tm.tm_mon = rtm.tm_mon;
2654297a3b0SGarrett D'Amore 			tm.tm_mday = 1;
2664297a3b0SGarrett D'Amore 			(void) mktime(&tm);
2674297a3b0SGarrett D'Amore 			rtm.tm_mday += (rtm.tm_wday - tm.tm_wday + 7) % 7;
2684297a3b0SGarrett D'Amore 		}
2694297a3b0SGarrett D'Amore 	}
2704297a3b0SGarrett D'Amore 
2714297a3b0SGarrett D'Amore 	/*
2724297a3b0SGarrett D'Amore 	 * Given only the century but no year within, the current year
2734297a3b0SGarrett D'Amore 	 * is assumed.  (Specified on the Solaris man page not POSIX.)
2744297a3b0SGarrett D'Amore 	 *
2754297a3b0SGarrett D'Amore 	 * Warning ugly end case
2764297a3b0SGarrett D'Amore 	 *
2774297a3b0SGarrett D'Amore 	 * This is more work since strptime(3) doesn't "do the right thing".
2784297a3b0SGarrett D'Amore 	 */
2794297a3b0SGarrett D'Amore 	if (rtm.tm_year != TMSENTINEL && (rtm.tm_year - 1900) >= 0) {
2804297a3b0SGarrett D'Amore 		rtm.tm_year -= 1900;
2814297a3b0SGarrett D'Amore 		rtm.tm_year += (tmnow.tm_year % 100);
2824297a3b0SGarrett D'Amore 	}
2834297a3b0SGarrett D'Amore 
2844297a3b0SGarrett D'Amore 	/*
2854297a3b0SGarrett D'Amore 	 * mktime() will normalize all values and also check that the
2864297a3b0SGarrett D'Amore 	 * value will fit into a time_t.
2874297a3b0SGarrett D'Amore 	 *
2884297a3b0SGarrett D'Amore 	 * This is only for POSIX correctness.	A date >= 1900 is
2894297a3b0SGarrett D'Amore 	 * really ok, but using a time_t limits things.
2904297a3b0SGarrett D'Amore 	 */
2914297a3b0SGarrett D'Amore 	if (mktime(rtmp) < 0) {
2924297a3b0SGarrett D'Amore 		getdate_err = 8;
2934297a3b0SGarrett D'Amore 		return (NULL);
2944297a3b0SGarrett D'Amore 	}
2954297a3b0SGarrett D'Amore 
2964297a3b0SGarrett D'Amore 	return (rtmp);
2974297a3b0SGarrett D'Amore }
298