1 /* Convert struct partime into time_t. */ 2 3 /* Copyright 1992, 1993, 1994, 1995 Paul Eggert 4 Distributed under license by the Free Software Foundation, Inc. 5 6 This file is part of RCS. 7 8 RCS is free software; you can redistribute it and/or modify 9 it under the terms of the GNU General Public License as published by 10 the Free Software Foundation; either version 2, or (at your option) 11 any later version. 12 13 RCS is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 GNU General Public License for more details. 17 18 You should have received a copy of the GNU General Public License 19 along with RCS; see the file COPYING. 20 If not, write to the Free Software Foundation, 21 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 22 23 Report problems and direct all questions to: 24 25 rcs-bugs@cs.purdue.edu 26 */ 27 28 /* 29 * $FreeBSD: src/gnu/usr.bin/rcs/lib/maketime.c,v 1.7 1999/08/27 23:36:43 peter Exp $ 30 */ 31 32 #if has_conf_h 33 # include "conf.h" 34 #else 35 # ifdef __STDC__ 36 # define P(x) x 37 # else 38 # define const 39 # define P(x) () 40 # endif 41 # include <sys/param.h> 42 # include <stdlib.h> 43 # include <time.h> 44 #endif 45 46 #include "partime.h" 47 #include "maketime.h" 48 49 static int isleap P((int)); 50 static int month_days P((struct tm const*)); 51 static time_t maketime P((struct partime const*,time_t)); 52 53 /* 54 * For maximum portability, use only localtime and gmtime. 55 * Make no assumptions about the time_t epoch or the range of time_t values. 56 * Avoid mktime because it's not universal and because there's no easy, 57 * portable way for mktime to yield the inverse of gmtime. 58 */ 59 60 #define TM_YEAR_ORIGIN 1900 61 62 static int 63 isleap(y) 64 int y; 65 { 66 return (y&3) == 0 && (y%100 != 0 || y%400 == 0); 67 } 68 69 static int const month_yday[] = { 70 /* days in year before start of months 0-12 */ 71 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 72 }; 73 74 /* Yield the number of days in TM's month. */ 75 static int 76 month_days(tm) 77 struct tm const *tm; 78 { 79 int m = tm->tm_mon; 80 return month_yday[m+1] - month_yday[m] 81 + (m==1 && isleap(tm->tm_year + TM_YEAR_ORIGIN)); 82 } 83 84 /* 85 * Convert UNIXTIME to struct tm form. 86 * Use gmtime if available and if !LOCALZONE, localtime otherwise. 87 */ 88 struct tm * 89 time2tm(unixtime, localzone) 90 time_t unixtime; 91 int localzone; 92 { 93 struct tm *tm; 94 # if TZ_must_be_set 95 static char const *TZ; 96 if (!TZ && !(TZ = getenv("TZ"))) 97 faterror("The TZ environment variable is not set; please set it to your timezone"); 98 # endif 99 if (localzone || !(tm = gmtime(&unixtime))) 100 tm = localtime(&unixtime); 101 return tm; 102 } 103 104 /* Yield A - B, measured in seconds. */ 105 time_t 106 difftm(a, b) 107 struct tm const *a, *b; 108 { 109 int ay = a->tm_year + (TM_YEAR_ORIGIN - 1); 110 int by = b->tm_year + (TM_YEAR_ORIGIN - 1); 111 int difference_in_day_of_year = a->tm_yday - b->tm_yday; 112 int intervening_leap_days = ( 113 ((ay >> 2) - (by >> 2)) 114 - (ay/100 - by/100) 115 + ((ay/100 >> 2) - (by/100 >> 2)) 116 ); 117 time_t difference_in_years = ay - by; 118 time_t difference_in_days = ( 119 difference_in_years*365 120 + (intervening_leap_days + difference_in_day_of_year) 121 ); 122 return 123 ( 124 ( 125 24*difference_in_days 126 + (a->tm_hour - b->tm_hour) 127 )*60 + (a->tm_min - b->tm_min) 128 )*60 + (a->tm_sec - b->tm_sec); 129 } 130 131 /* 132 * Adjust time T by adding SECONDS. SECONDS must be at most 24 hours' worth. 133 * Adjust only T's year, mon, mday, hour, min and sec members; 134 * plus adjust wday if it is defined. 135 */ 136 void 137 adjzone(t, seconds) 138 register struct tm *t; 139 long seconds; 140 { 141 /* 142 * This code can be off by a second if SECONDS is not a multiple of 60, 143 * if T is local time, and if a leap second happens during this minute. 144 * But this bug has never occurred, and most likely will not ever occur. 145 * Liberia, the last country for which SECONDS % 60 was nonzero, 146 * switched to UTC in May 1972; the first leap second was in June 1972. 147 */ 148 int leap_second = t->tm_sec == 60; 149 long sec = seconds + (t->tm_sec - leap_second); 150 if (sec < 0) { 151 if ((t->tm_min -= (59-sec)/60) < 0) { 152 if ((t->tm_hour -= (59-t->tm_min)/60) < 0) { 153 t->tm_hour += 24; 154 if (TM_DEFINED(t->tm_wday) && --t->tm_wday < 0) 155 t->tm_wday = 6; 156 if (--t->tm_mday <= 0) { 157 if (--t->tm_mon < 0) { 158 --t->tm_year; 159 t->tm_mon = 11; 160 } 161 t->tm_mday = month_days(t); 162 } 163 } 164 t->tm_min += 24 * 60; 165 } 166 sec += 24L * 60 * 60; 167 } else 168 if (60 <= (t->tm_min += sec/60)) 169 if (24 <= (t->tm_hour += t->tm_min/60)) { 170 t->tm_hour -= 24; 171 if (TM_DEFINED(t->tm_wday) && ++t->tm_wday == 7) 172 t->tm_wday = 0; 173 if (month_days(t) < ++t->tm_mday) { 174 if (11 < ++t->tm_mon) { 175 ++t->tm_year; 176 t->tm_mon = 0; 177 } 178 t->tm_mday = 1; 179 } 180 } 181 t->tm_min %= 60; 182 t->tm_sec = (int) (sec%60) + leap_second; 183 } 184 185 /* 186 * Convert TM to time_t, using localtime if LOCALZONE and gmtime otherwise. 187 * Use only TM's year, mon, mday, hour, min, and sec members. 188 * Ignore TM's old tm_yday and tm_wday, but fill in their correct values. 189 * Yield -1 on failure (e.g. a member out of range). 190 * Posix 1003.1-1990 doesn't allow leap seconds, but some implementations 191 * have them anyway, so allow them if localtime/gmtime does. 192 */ 193 time_t 194 tm2time(tm, localzone) 195 struct tm *tm; 196 int localzone; 197 { 198 /* Cache the most recent t,tm pairs; 1 for gmtime, 1 for localtime. */ 199 static time_t t_cache[2]; 200 static struct tm tm_cache[2]; 201 202 time_t d, gt; 203 struct tm const *gtm; 204 /* 205 * The maximum number of iterations should be enough to handle any 206 * combinations of leap seconds, time zone rule changes, and solar time. 207 * 4 is probably enough; we use a bigger number just to be safe. 208 */ 209 int remaining_tries = 8; 210 211 /* Avoid subscript errors. */ 212 if (12 <= (unsigned)tm->tm_mon) 213 return -1; 214 215 tm->tm_yday = month_yday[tm->tm_mon] + tm->tm_mday 216 - (tm->tm_mon<2 || ! isleap(tm->tm_year + TM_YEAR_ORIGIN)); 217 218 /* Make a first guess. */ 219 gt = t_cache[localzone]; 220 gtm = gt ? &tm_cache[localzone] : time2tm(gt,localzone); 221 222 /* Repeatedly use the error from the guess to improve the guess. */ 223 while ((d = difftm(tm, gtm)) != 0) { 224 if (--remaining_tries == 0) 225 return -1; 226 gt += d; 227 gtm = time2tm(gt,localzone); 228 } 229 t_cache[localzone] = gt; 230 tm_cache[localzone] = *gtm; 231 232 /* 233 * Check that the guess actually matches; 234 * overflow can cause difftm to yield 0 even on differing times, 235 * or tm may have members out of range (e.g. bad leap seconds). 236 */ 237 if ( (tm->tm_year ^ gtm->tm_year) 238 | (tm->tm_mon ^ gtm->tm_mon) 239 | (tm->tm_mday ^ gtm->tm_mday) 240 | (tm->tm_hour ^ gtm->tm_hour) 241 | (tm->tm_min ^ gtm->tm_min) 242 | (tm->tm_sec ^ gtm->tm_sec)) 243 return -1; 244 245 tm->tm_wday = gtm->tm_wday; 246 return gt; 247 } 248 249 /* 250 * Check *PT and convert it to time_t. 251 * If it is incompletely specified, use DEFAULT_TIME to fill it out. 252 * Use localtime if PT->zone is the special value TM_LOCAL_ZONE. 253 * Yield -1 on failure. 254 * ISO 8601 day-of-year and week numbers are not yet supported. 255 */ 256 static time_t 257 maketime(pt, default_time) 258 struct partime const *pt; 259 time_t default_time; 260 { 261 int localzone, wday; 262 struct tm tm; 263 struct tm *tm0 = 0; 264 time_t r; 265 266 tm0 = 0; /* Keep gcc -Wall happy. */ 267 localzone = pt->zone==TM_LOCAL_ZONE; 268 269 tm = pt->tm; 270 271 if (TM_DEFINED(pt->ymodulus) || !TM_DEFINED(tm.tm_year)) { 272 /* Get tm corresponding to current time. */ 273 tm0 = time2tm(default_time, localzone); 274 if (!localzone) 275 adjzone(tm0, pt->zone); 276 } 277 278 if (TM_DEFINED(pt->ymodulus)) 279 tm.tm_year += 280 rounddown(tm0->tm_year + TM_YEAR_ORIGIN, pt->ymodulus); 281 else if (!TM_DEFINED(tm.tm_year)) { 282 /* Set default year, month, day from current time. */ 283 tm.tm_year = tm0->tm_year + TM_YEAR_ORIGIN; 284 if (!TM_DEFINED(tm.tm_mon)) { 285 tm.tm_mon = tm0->tm_mon; 286 if (!TM_DEFINED(tm.tm_mday)) 287 tm.tm_mday = tm0->tm_mday; 288 } 289 } 290 291 /* Convert from partime year (Gregorian) to Posix year. */ 292 tm.tm_year -= TM_YEAR_ORIGIN; 293 294 /* Set remaining default fields to be their minimum values. */ 295 if (!TM_DEFINED(tm.tm_mon)) tm.tm_mon = 0; 296 if (!TM_DEFINED(tm.tm_mday)) tm.tm_mday = 1; 297 if (!TM_DEFINED(tm.tm_hour)) tm.tm_hour = 0; 298 if (!TM_DEFINED(tm.tm_min)) tm.tm_min = 0; 299 if (!TM_DEFINED(tm.tm_sec)) tm.tm_sec = 0; 300 301 if (!localzone) 302 adjzone(&tm, -pt->zone); 303 wday = tm.tm_wday; 304 305 /* Convert and fill in the rest of the tm. */ 306 r = tm2time(&tm, localzone); 307 308 /* Check weekday. */ 309 if (r != -1 && TM_DEFINED(wday) && wday != tm.tm_wday) 310 return -1; 311 312 return r; 313 } 314 315 /* Parse a free-format date in SOURCE, yielding a Unix format time. */ 316 time_t 317 str2time(source, default_time, default_zone) 318 char const *source; 319 time_t default_time; 320 long default_zone; 321 { 322 struct partime pt; 323 324 if (*partime(source, &pt)) 325 return -1; 326 if (pt.zone == TM_UNDEFINED_ZONE) 327 pt.zone = default_zone; 328 return maketime(&pt, default_time); 329 } 330 331 #if TEST 332 #include <stdio.h> 333 int 334 main(argc, argv) int argc; char **argv; 335 { 336 time_t default_time = time((time_t *)0); 337 long default_zone = argv[1] ? atol(argv[1]) : 0; 338 char buf[1000]; 339 while (fgets(buf, 1000, stdin)) { 340 time_t t = str2time(buf, default_time, default_zone); 341 printf("%s", asctime(gmtime(&t))); 342 } 343 return 0; 344 } 345 #endif 346