1 /* $NetBSD: getdate.c,v 1.4 2018/01/04 20:57:29 kamil Exp $ */ 2 /* 3 * Copyright (c) 2009 The NetBSD Foundation, Inc. 4 * All rights reserved. 5 * 6 * This code is derived from software contributed to The NetBSD Foundation 7 * by Brian Ginsbach. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 19 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 21 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 22 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "namespace.h" 32 33 #include <sys/stat.h> 34 35 #include <errno.h> 36 #include <stdio.h> 37 #include <stdlib.h> 38 #include <string.h> 39 #include <time.h> 40 #include <unistd.h> 41 #include <util.h> 42 43 #define TMSENTINEL (-1) 44 45 /* 46 * getdate_err is set to one of the following values on error. 47 * 48 * 1 The DATEMSK environment variable is null or undefined. 49 * 2 The template file cannot be opened for reading. 50 * 3 Failed to get file status information. 51 * 4 Template file is not a regular file. 52 * 5 Encountered an error while reading the template file. 53 * 6 Cannot allocate memory. 54 * 7 Input string does not match any line in the template. 55 * 8 Input string is invalid (for example, February 31) or could not 56 * be represented in a time_t. 57 */ 58 59 int getdate_err; 60 61 struct tm * 62 getdate(const char *str) 63 { 64 char *datemsk, *line, *rp; 65 FILE *fp; 66 struct stat sb; 67 static struct tm rtm, tmnow; 68 struct tm *tmp, *rtmp = &rtm; 69 size_t lineno = 0; 70 time_t now; 71 72 if (((datemsk = getenv("DATEMSK")) == NULL) || *datemsk == '\0') { 73 getdate_err = 1; 74 return (NULL); 75 } 76 77 if (stat(datemsk, &sb) < 0) { 78 getdate_err = 3; 79 return (NULL); 80 } 81 82 if ((sb.st_mode & S_IFMT) != S_IFREG) { 83 getdate_err = 4; 84 return (NULL); 85 } 86 87 if ((fp = fopen(datemsk, "re")) == NULL) { 88 getdate_err = 2; 89 return (NULL); 90 } 91 92 /* loop through datemsk file */ 93 errno = 0; 94 rp = NULL; 95 while ((line = fparseln(fp, NULL, &lineno, NULL, 0)) != NULL) { 96 /* initialize tmp with sentinels */ 97 rtm.tm_sec = rtm.tm_min = rtm.tm_hour = TMSENTINEL; 98 rtm.tm_mday = rtm.tm_mon = rtm.tm_year = TMSENTINEL; 99 rtm.tm_wday = rtm.tm_yday = rtm.tm_isdst = TMSENTINEL; 100 rtm.tm_gmtoff = 0; 101 rtm.tm_zone = NULL; 102 rp = strptime(str, line, rtmp); 103 free(line); 104 if (rp != NULL) 105 break; 106 errno = 0; 107 } 108 if (errno != 0 || ferror(fp)) { 109 if (errno == ENOMEM) 110 getdate_err = 6; 111 else 112 getdate_err = 5; 113 fclose(fp); 114 return (NULL); 115 } 116 if (feof(fp) || (rp != NULL && *rp != '\0')) { 117 getdate_err = 7; 118 return (NULL); 119 } 120 fclose(fp); 121 122 time(&now); 123 tmp = localtime(&now); 124 tmnow = *tmp; 125 126 /* 127 * This implementation does not accept setting the broken-down time 128 * to anything other than the localtime(). It is not possible to 129 * change the scanned timezone with %Z. 130 * 131 * Note IRIX and Solaris accept only the current zone for %Z. 132 * XXX Is there any implementation that matches the standard? 133 * XXX (Or am I reading the standard wrong?) 134 * 135 * Note: Neither XPG 6 (POSIX 2004) nor XPG 7 (POSIX 2008) 136 * requires strptime(3) support for %Z. 137 */ 138 139 /* 140 * Given only a weekday find the first matching weekday starting 141 * with the current weekday and moving into the future. 142 */ 143 if (rtm.tm_wday != TMSENTINEL && rtm.tm_year == TMSENTINEL && 144 rtm.tm_mon == TMSENTINEL && rtm.tm_mday == TMSENTINEL) { 145 rtm.tm_year = tmnow.tm_year; 146 rtm.tm_mon = tmnow.tm_mon; 147 rtm.tm_mday = tmnow.tm_mday + 148 (rtm.tm_wday - tmnow.tm_wday + 7) % 7; 149 } 150 151 /* 152 * Given only a month (and no year) find the first matching month 153 * starting with the current month and moving into the future. 154 */ 155 if (rtm.tm_mon != TMSENTINEL) { 156 if (rtm.tm_year == TMSENTINEL) { 157 rtm.tm_year = tmnow.tm_year + 158 ((rtm.tm_mon < tmnow.tm_mon)? 1 : 0); 159 } 160 if (rtm.tm_mday == TMSENTINEL) { 161 /* assume the first of the month */ 162 rtm.tm_mday = 1; 163 /* 164 * XXX This isn't documented! Just observed behavior. 165 * 166 * Given the weekday find the first matching weekday 167 * starting with the weekday of the first day of the 168 * the month and moving into the future. 169 */ 170 if (rtm.tm_wday != TMSENTINEL) { 171 struct tm tm; 172 173 memset(&tm, 0, sizeof(struct tm)); 174 tm.tm_year = rtm.tm_year; 175 tm.tm_mon = rtm.tm_mon; 176 tm.tm_mday = 1; 177 mktime(&tm); 178 rtm.tm_mday += 179 (rtm.tm_wday - tm.tm_wday + 7) % 7; 180 } 181 } 182 } 183 184 /* 185 * Given no time of day assume the current time of day. 186 */ 187 if (rtm.tm_hour == TMSENTINEL && 188 rtm.tm_min == TMSENTINEL && rtm.tm_sec == TMSENTINEL) { 189 rtm.tm_hour = tmnow.tm_hour; 190 rtm.tm_min = tmnow.tm_min; 191 rtm.tm_sec = tmnow.tm_sec; 192 } 193 /* 194 * Given an hour and no date, find the first matching hour starting 195 * with the current hour and moving into the future 196 */ 197 if (rtm.tm_hour != TMSENTINEL && 198 rtm.tm_year == TMSENTINEL && rtm.tm_mon == TMSENTINEL && 199 rtm.tm_mday == TMSENTINEL) { 200 rtm.tm_year = tmnow.tm_year; 201 rtm.tm_mon = tmnow.tm_mon; 202 rtm.tm_mday = tmnow.tm_mday; 203 if (rtm.tm_hour < tmnow.tm_hour) 204 rtm.tm_hour += 24; 205 } 206 207 /* 208 * Set to 'sane' values; mktime(3) does funny things otherwise. 209 * No hours, no minutes, no seconds, no service. 210 */ 211 if (rtm.tm_hour == TMSENTINEL) 212 rtm.tm_hour = 0; 213 if (rtm.tm_min == TMSENTINEL) 214 rtm.tm_min = 0; 215 if (rtm.tm_sec == TMSENTINEL) 216 rtm.tm_sec = 0; 217 218 /* 219 * Given only a year the values of month, day of month, day of year, 220 * week day and is daylight (summer) time are unspecified. 221 * (Specified on the Solaris man page not POSIX.) 222 */ 223 if (rtm.tm_year != TMSENTINEL && 224 rtm.tm_mon == TMSENTINEL && rtm.tm_mday == TMSENTINEL) { 225 rtm.tm_mon = 0; 226 rtm.tm_mday = 1; 227 /* 228 * XXX More undocumented functionality but observed. 229 * 230 * Given the weekday find the first matching weekday 231 * starting with the weekday of the first day of the 232 * month and moving into the future. 233 */ 234 if (rtm.tm_wday != TMSENTINEL) { 235 struct tm tm; 236 237 memset(&tm, 0, sizeof(struct tm)); 238 tm.tm_year = rtm.tm_year; 239 tm.tm_mon = rtm.tm_mon; 240 tm.tm_mday = 1; 241 mktime(&tm); 242 rtm.tm_mday += (rtm.tm_wday - tm.tm_wday + 7) % 7; 243 } 244 } 245 246 /* 247 * Given only the century but no year within, the current year 248 * is assumed. (Specified on the Solaris man page not POSIX.) 249 * 250 * Warning ugly end case 251 * 252 * This is more work since strptime(3) doesn't "do the right thing". 253 */ 254 if (rtm.tm_year != TMSENTINEL && (rtm.tm_year - 1900) >= 0) { 255 rtm.tm_year -= 1900; 256 rtm.tm_year += (tmnow.tm_year % 100); 257 } 258 259 /* 260 * mktime() will normalize all values and also check that the 261 * value will fit into a time_t. 262 * 263 * This is only for POSIX correctness. A date >= 1900 is 264 * really ok, but using a time_t limits things. 265 */ 266 if (mktime(rtmp) < 0) { 267 getdate_err = 8; 268 return (NULL); 269 } 270 271 return (rtmp); 272 } 273