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