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