1 /* $NetBSD: getdate.c,v 1.3 2014/09/18 13:58:20 christos 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 <sys/stat.h> 32 33 #include <errno.h> 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <time.h> 38 #include <unistd.h> 39 #include <util.h> 40 41 #define TMSENTINEL (-1) 42 43 /* 44 * getdate_err is set to one of the following values on error. 45 * 46 * 1 The DATEMSK environment variable is null or undefined. 47 * 2 The template file cannot be opened for reading. 48 * 3 Failed to get file status information. 49 * 4 Template file is not a regular file. 50 * 5 Encountered an error while reading the template file. 51 * 6 Cannot allocate memory. 52 * 7 Input string does not match any line in the template. 53 * 8 Input string is invalid (for example, February 31) or could not 54 * be represented in a time_t. 55 */ 56 57 int getdate_err; 58 59 struct tm * 60 getdate(const char *str) 61 { 62 char *datemsk, *line, *rp; 63 FILE *fp; 64 struct stat sb; 65 static struct tm rtm, tmnow; 66 struct tm *tmp, *rtmp = &rtm; 67 size_t lineno = 0; 68 time_t now; 69 70 if (((datemsk = getenv("DATEMSK")) == NULL) || *datemsk == '\0') { 71 getdate_err = 1; 72 return (NULL); 73 } 74 75 if (stat(datemsk, &sb) < 0) { 76 getdate_err = 3; 77 return (NULL); 78 } 79 80 if ((sb.st_mode & S_IFMT) != S_IFREG) { 81 getdate_err = 4; 82 return (NULL); 83 } 84 85 if ((fp = fopen(datemsk, "re")) == NULL) { 86 getdate_err = 2; 87 return (NULL); 88 } 89 90 /* loop through datemsk file */ 91 errno = 0; 92 rp = NULL; 93 while ((line = fparseln(fp, NULL, &lineno, NULL, 0)) != NULL) { 94 /* initialize tmp with sentinels */ 95 rtm.tm_sec = rtm.tm_min = rtm.tm_hour = TMSENTINEL; 96 rtm.tm_mday = rtm.tm_mon = rtm.tm_year = TMSENTINEL; 97 rtm.tm_wday = rtm.tm_yday = rtm.tm_isdst = TMSENTINEL; 98 rtm.tm_gmtoff = 0; 99 rtm.tm_zone = NULL; 100 rp = strptime(str, line, rtmp); 101 free(line); 102 if (rp != NULL) 103 break; 104 errno = 0; 105 } 106 if (errno != 0 || ferror(fp)) { 107 if (errno == ENOMEM) 108 getdate_err = 6; 109 else 110 getdate_err = 5; 111 fclose(fp); 112 return (NULL); 113 } 114 if (feof(fp) || (rp != NULL && *rp != '\0')) { 115 getdate_err = 7; 116 return (NULL); 117 } 118 fclose(fp); 119 120 time(&now); 121 tmp = localtime(&now); 122 tmnow = *tmp; 123 124 /* 125 * This implementation does not accept setting the broken-down time 126 * to anything other than the localtime(). It is not possible to 127 * change the scanned timezone with %Z. 128 * 129 * Note IRIX and Solaris accept only the current zone for %Z. 130 * XXX Is there any implementation that matches the standard? 131 * XXX (Or am I reading the standard wrong?) 132 * 133 * Note: Neither XPG 6 (POSIX 2004) nor XPG 7 (POSIX 2008) 134 * requires strptime(3) support for %Z. 135 */ 136 137 /* 138 * Given only a weekday find the first matching weekday starting 139 * with the current weekday and moving into the future. 140 */ 141 if (rtm.tm_wday != TMSENTINEL && rtm.tm_year == TMSENTINEL && 142 rtm.tm_mon == TMSENTINEL && rtm.tm_mday == TMSENTINEL) { 143 rtm.tm_year = tmnow.tm_year; 144 rtm.tm_mon = tmnow.tm_mon; 145 rtm.tm_mday = tmnow.tm_mday + 146 (rtm.tm_wday - tmnow.tm_wday + 7) % 7; 147 } 148 149 /* 150 * Given only a month (and no year) find the first matching month 151 * starting with the current month and moving into the future. 152 */ 153 if (rtm.tm_mon != TMSENTINEL) { 154 if (rtm.tm_year == TMSENTINEL) { 155 rtm.tm_year = tmnow.tm_year + 156 ((rtm.tm_mon < tmnow.tm_mon)? 1 : 0); 157 } 158 if (rtm.tm_mday == TMSENTINEL) { 159 /* assume the first of the month */ 160 rtm.tm_mday = 1; 161 /* 162 * XXX This isn't documented! Just observed behavior. 163 * 164 * Given the weekday find the first matching weekday 165 * starting with the weekday of the first day of the 166 * the month and moving into the future. 167 */ 168 if (rtm.tm_wday != TMSENTINEL) { 169 struct tm tm; 170 171 memset(&tm, 0, sizeof(struct tm)); 172 tm.tm_year = rtm.tm_year; 173 tm.tm_mon = rtm.tm_mon; 174 tm.tm_mday = 1; 175 mktime(&tm); 176 rtm.tm_mday += 177 (rtm.tm_wday - tm.tm_wday + 7) % 7; 178 } 179 } 180 } 181 182 /* 183 * Given no time of day assume the current time of day. 184 */ 185 if (rtm.tm_hour == TMSENTINEL && 186 rtm.tm_min == TMSENTINEL && rtm.tm_sec == TMSENTINEL) { 187 rtm.tm_hour = tmnow.tm_hour; 188 rtm.tm_min = tmnow.tm_min; 189 rtm.tm_sec = tmnow.tm_sec; 190 } 191 /* 192 * Given an hour and no date, find the first matching hour starting 193 * with the current hour and moving into the future 194 */ 195 if (rtm.tm_hour != TMSENTINEL && 196 rtm.tm_year == TMSENTINEL && rtm.tm_mon == TMSENTINEL && 197 rtm.tm_mday == TMSENTINEL) { 198 rtm.tm_year = tmnow.tm_year; 199 rtm.tm_mon = tmnow.tm_mon; 200 rtm.tm_mday = tmnow.tm_mday; 201 if (rtm.tm_hour < tmnow.tm_hour) 202 rtm.tm_hour += 24; 203 } 204 205 /* 206 * Set to 'sane' values; mktime(3) does funny things otherwise. 207 * No hours, no minutes, no seconds, no service. 208 */ 209 if (rtm.tm_hour == TMSENTINEL) 210 rtm.tm_hour = 0; 211 if (rtm.tm_min == TMSENTINEL) 212 rtm.tm_min = 0; 213 if (rtm.tm_sec == TMSENTINEL) 214 rtm.tm_sec = 0; 215 216 /* 217 * Given only a year the values of month, day of month, day of year, 218 * week day and is daylight (summer) time are unspecified. 219 * (Specified on the Solaris man page not POSIX.) 220 */ 221 if (rtm.tm_year != TMSENTINEL && 222 rtm.tm_mon == TMSENTINEL && rtm.tm_mday == TMSENTINEL) { 223 rtm.tm_mon = 0; 224 rtm.tm_mday = 1; 225 /* 226 * XXX More undocumented functionality but observed. 227 * 228 * Given the weekday find the first matching weekday 229 * starting with the weekday of the first day of the 230 * month and moving into the future. 231 */ 232 if (rtm.tm_wday != TMSENTINEL) { 233 struct tm tm; 234 235 memset(&tm, 0, sizeof(struct tm)); 236 tm.tm_year = rtm.tm_year; 237 tm.tm_mon = rtm.tm_mon; 238 tm.tm_mday = 1; 239 mktime(&tm); 240 rtm.tm_mday += (rtm.tm_wday - tm.tm_wday + 7) % 7; 241 } 242 } 243 244 /* 245 * Given only the century but no year within, the current year 246 * is assumed. (Specified on the Solaris man page not POSIX.) 247 * 248 * Warning ugly end case 249 * 250 * This is more work since strptime(3) doesn't "do the right thing". 251 */ 252 if (rtm.tm_year != TMSENTINEL && (rtm.tm_year - 1900) >= 0) { 253 rtm.tm_year -= 1900; 254 rtm.tm_year += (tmnow.tm_year % 100); 255 } 256 257 /* 258 * mktime() will normalize all values and also check that the 259 * value will fit into a time_t. 260 * 261 * This is only for POSIX correctness. A date >= 1900 is 262 * really ok, but using a time_t limits things. 263 */ 264 if (mktime(rtmp) < 0) { 265 getdate_err = 8; 266 return (NULL); 267 } 268 269 return (rtmp); 270 } 271