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