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