1 /* Convert struct partime into time_t. */ 2 3 /* Copyright 1992, 1993, 1994, 1995, 1997 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 #if has_conf_h 30 # include <conf.h> 31 #else 32 # if HAVE_CONFIG_H 33 # include <config.h> 34 # else 35 # ifndef __STDC__ 36 # define const 37 # endif 38 # endif 39 /* MIPS RISCOS4.52 defines time_t in <sys/types.h> not <time.h>. */ 40 # include <sys/types.h> 41 # if HAVE_LIMITS_H 42 # include <limits.h> 43 # endif 44 # ifndef LONG_MIN 45 # define LONG_MIN (-1-2147483647L) 46 # endif 47 # if STDC_HEADERS 48 # include <stdlib.h> 49 # endif 50 # include <time.h> 51 # ifdef __STDC__ 52 # define P(x) x 53 # else 54 # define P(x) () 55 # endif 56 #endif 57 58 #include <partime.h> 59 #include <maketime.h> 60 61 char const maketId[] = 62 "$Id: maketime.c,v 5.15 1997/06/17 16:54:36 eggert Exp $"; 63 64 static int isleap P ((int)); 65 static int month_days P ((struct tm const *)); 66 static time_t maketime P ((struct partime const *, time_t)); 67 68 /* For maximum portability, use only localtime and gmtime. 69 Make no assumptions about the time_t epoch or the range of time_t values. 70 Avoid mktime because it's not universal and because there's no easy, 71 portable way for mktime to yield the inverse of gmtime. */ 72 73 #define TM_YEAR_ORIGIN 1900 74 75 static int 76 isleap (y) 77 int y; 78 { 79 return (y & 3) == 0 && (y % 100 != 0 || y % 400 == 0); 80 } 81 82 /* days in year before start of months 0-12 */ 83 static int const month_yday[] = 84 { 85 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 86 }; 87 88 /* Yield the number of days in TM's month. */ 89 static int 90 month_days (tm) 91 struct tm const *tm; 92 { 93 int m = tm->tm_mon; 94 return (month_yday[m + 1] - month_yday[m] 95 + (m == 1 && isleap (tm->tm_year + TM_YEAR_ORIGIN))); 96 } 97 98 /* Convert UNIXTIME to struct tm form. 99 Use gmtime if available and if !LOCALZONE, localtime otherwise. */ 100 struct tm * 101 time2tm (unixtime, localzone) 102 time_t unixtime; 103 int localzone; 104 { 105 struct tm *tm; 106 #ifdef TZ_is_unset 107 static char const *TZ; 108 if (!TZ && !(TZ = getenv ("TZ"))) 109 TZ_is_unset ("The TZ environment variable is not set; please set it to your timezone"); 110 #endif 111 if (localzone || !(tm = gmtime (&unixtime))) 112 tm = localtime (&unixtime); 113 return tm; 114 } 115 116 /* Yield A - B, measured in seconds. */ 117 time_t 118 difftm (a, b) 119 struct tm const *a; 120 struct tm const *b; 121 { 122 int ay = a->tm_year + (TM_YEAR_ORIGIN - 1); 123 int by = b->tm_year + (TM_YEAR_ORIGIN - 1); 124 int ac = ay / 100 - (ay % 100 < 0); 125 int bc = by / 100 - (by % 100 < 0); 126 int difference_in_day_of_year = a->tm_yday - b->tm_yday; 127 int intervening_leap_days = (((ay >> 2) - (by >> 2)) 128 - (ac - bc) 129 + ((ac >> 2) - (bc >> 2))); 130 time_t difference_in_years = ay - by; 131 time_t difference_in_days 132 = (difference_in_years * 365 133 + (intervening_leap_days + difference_in_day_of_year)); 134 return (((((difference_in_days * 24 135 + (a->tm_hour - b->tm_hour)) 136 * 60) 137 + (a->tm_min - b->tm_min)) 138 * 60) 139 + (a->tm_sec - b->tm_sec)); 140 } 141 142 /* 143 * Adjust time T by adding SECONDS. SECONDS must be at most 24 hours' worth. 144 * Adjust only T's year, mon, mday, hour, min and sec members; 145 * plus adjust wday if it is defined. 146 */ 147 void 148 adjzone (t, seconds) 149 register struct tm *t; 150 long seconds; 151 { 152 /* 153 * This code can be off by a second if SECONDS is not a multiple of 60, 154 * if T is local time, and if a leap second happens during this minute. 155 * But this bug has never occurred, and most likely will not ever occur. 156 * Liberia, the last country for which SECONDS % 60 was nonzero, 157 * switched to UTC in May 1972; the first leap second was in June 1972. 158 */ 159 int leap_second = t->tm_sec == 60; 160 long sec = seconds + (t->tm_sec - leap_second); 161 if (sec < 0) 162 { 163 if ((t->tm_min -= (59 - sec) / 60) < 0) 164 { 165 if ((t->tm_hour -= (59 - t->tm_min) / 60) < 0) 166 { 167 t->tm_hour += 24; 168 if (TM_DEFINED (t->tm_wday) && --t->tm_wday < 0) 169 t->tm_wday = 6; 170 if (--t->tm_mday <= 0) 171 { 172 if (--t->tm_mon < 0) 173 { 174 --t->tm_year; 175 t->tm_mon = 11; 176 } 177 t->tm_mday = month_days (t); 178 } 179 } 180 t->tm_min += 24 * 60; 181 } 182 sec += 24L * 60 * 60; 183 } 184 else if (60 <= (t->tm_min += sec / 60)) 185 if (24 <= (t->tm_hour += t->tm_min / 60)) 186 { 187 t->tm_hour -= 24; 188 if (TM_DEFINED (t->tm_wday) && ++t->tm_wday == 7) 189 t->tm_wday = 0; 190 if (month_days (t) < ++t->tm_mday) 191 { 192 if (11 < ++t->tm_mon) 193 { 194 ++t->tm_year; 195 t->tm_mon = 0; 196 } 197 t->tm_mday = 1; 198 } 199 } 200 t->tm_min %= 60; 201 t->tm_sec = (int) (sec % 60) + leap_second; 202 } 203 204 /* 205 * Convert TM to time_t, using localtime if LOCALZONE and gmtime otherwise. 206 * Use only TM's year, mon, mday, hour, min, and sec members. 207 * Ignore TM's old tm_yday and tm_wday, but fill in their correct values. 208 * Yield -1 on failure (e.g. a member out of range). 209 * Posix 1003.1-1990 doesn't allow leap seconds, but some implementations 210 * have them anyway, so allow them if localtime/gmtime does. 211 */ 212 time_t 213 tm2time (tm, localzone) 214 struct tm *tm; 215 int localzone; 216 { 217 /* Cache the most recent t,tm pairs; 1 for gmtime, 1 for localtime. */ 218 static time_t t_cache[2]; 219 static struct tm tm_cache[2]; 220 221 time_t d, gt; 222 struct tm const *gtm; 223 /* 224 * The maximum number of iterations should be enough to handle any 225 * combinations of leap seconds, time zone rule changes, and solar time. 226 * 4 is probably enough; we use a bigger number just to be safe. 227 */ 228 int remaining_tries = 8; 229 230 /* Avoid subscript errors. */ 231 if (12 <= (unsigned) tm->tm_mon) 232 return -1; 233 234 tm->tm_yday = month_yday[tm->tm_mon] + tm->tm_mday 235 - (tm->tm_mon < 2 || !isleap (tm->tm_year + TM_YEAR_ORIGIN)); 236 237 /* Make a first guess. */ 238 gt = t_cache[localzone]; 239 gtm = gt ? &tm_cache[localzone] : time2tm (gt, localzone); 240 241 /* Repeatedly use the error from the guess to improve the guess. */ 242 while ((d = difftm (tm, gtm)) != 0) 243 { 244 if (--remaining_tries == 0) 245 return -1; 246 gt += d; 247 gtm = time2tm (gt, localzone); 248 } 249 250 /* 251 * Check that the guess actually matches; 252 * overflow can cause difftm to yield 0 even on differing times, 253 * or tm may have members out of range (e.g. bad leap seconds). 254 */ 255 #define TM_DIFFER(a,b) \ 256 ( \ 257 ((a)->tm_year ^ (b)->tm_year) | \ 258 ((a)->tm_mon ^ (b)->tm_mon) | \ 259 ((a)->tm_mday ^ (b)->tm_mday) | \ 260 ((a)->tm_hour ^ (b)->tm_hour) | \ 261 ((a)->tm_min ^ (b)->tm_min) | \ 262 ((a)->tm_sec ^ (b)->tm_sec) \ 263 ) 264 if (TM_DIFFER (tm, gtm)) 265 { 266 /* 267 * If gt is a leap second, try gt+1; if it is one greater than 268 * a leap second, try gt-1; otherwise, it doesn't matter. 269 * Leap seconds always fall at month end. 270 */ 271 int yd = tm->tm_year - gtm->tm_year; 272 gt += yd + (yd ? 0 : tm->tm_mon - gtm->tm_mon); 273 gtm = time2tm (gt, localzone); 274 if (TM_DIFFER (tm, gtm)) 275 return -1; 276 } 277 t_cache[localzone] = gt; 278 tm_cache[localzone] = *gtm; 279 280 tm->tm_wday = gtm->tm_wday; 281 return gt; 282 } 283 284 /* 285 * Check *PT and convert it to time_t. 286 * If it is incompletely specified, use DEFAULT_TIME to fill it out. 287 * Use localtime if PT->zone is the special value TM_LOCAL_ZONE. 288 * Yield -1 on failure. 289 * ISO 8601 day-of-year and week numbers are not yet supported. 290 */ 291 static time_t 292 maketime (pt, default_time) 293 struct partime const *pt; 294 time_t default_time; 295 { 296 int localzone, wday; 297 struct tm tm; 298 struct tm *tm0 = 0; 299 time_t r; 300 301 tm0 = 0; /* Keep gcc -Wall happy. */ 302 localzone = pt->zone == TM_LOCAL_ZONE; 303 304 tm = pt->tm; 305 306 if (TM_DEFINED (pt->ymodulus) || !TM_DEFINED (tm.tm_year)) 307 { 308 /* Get tm corresponding to default time. */ 309 tm0 = time2tm (default_time, localzone); 310 if (!localzone) 311 adjzone (tm0, pt->zone); 312 } 313 314 if (TM_DEFINED (pt->ymodulus)) 315 tm.tm_year += 316 (tm0->tm_year + TM_YEAR_ORIGIN) / pt->ymodulus * pt->ymodulus; 317 else if (!TM_DEFINED (tm.tm_year)) 318 { 319 /* Set default year, month, day from current time. */ 320 tm.tm_year = tm0->tm_year + TM_YEAR_ORIGIN; 321 if (!TM_DEFINED (tm.tm_mon)) 322 { 323 tm.tm_mon = tm0->tm_mon; 324 if (!TM_DEFINED (tm.tm_mday)) 325 tm.tm_mday = tm0->tm_mday; 326 } 327 } 328 329 /* Convert from partime year (Gregorian) to Posix year. */ 330 tm.tm_year -= TM_YEAR_ORIGIN; 331 332 /* Set remaining default fields to be their minimum values. */ 333 if (!TM_DEFINED (tm.tm_mon)) 334 tm.tm_mon = 0; 335 if (!TM_DEFINED (tm.tm_mday)) 336 tm.tm_mday = 1; 337 if (!TM_DEFINED (tm.tm_hour)) 338 tm.tm_hour = 0; 339 if (!TM_DEFINED (tm.tm_min)) 340 tm.tm_min = 0; 341 if (!TM_DEFINED (tm.tm_sec)) 342 tm.tm_sec = 0; 343 344 if (!localzone) 345 adjzone (&tm, -pt->zone); 346 wday = tm.tm_wday; 347 348 /* Convert and fill in the rest of the tm. */ 349 r = tm2time (&tm, localzone); 350 351 /* Check weekday. */ 352 if (r != -1 && TM_DEFINED (wday) && wday != tm.tm_wday) 353 return -1; 354 355 return r; 356 } 357 358 /* Parse a free-format date in SOURCE, yielding a Unix format time. */ 359 time_t 360 str2time (source, default_time, default_zone) 361 char const *source; 362 time_t default_time; 363 long default_zone; 364 { 365 struct partime pt; 366 367 if (*partime (source, &pt)) 368 return -1; 369 if (pt.zone == TM_UNDEFINED_ZONE) 370 pt.zone = default_zone; 371 return maketime (&pt, default_time); 372 } 373 374 #if TEST 375 #include <stdio.h> 376 int 377 main (argc, argv) 378 int argc; 379 char **argv; 380 { 381 time_t default_time = time ((time_t *) 0); 382 long default_zone = argv[1] ? atol (argv[1]) : 0; 383 char buf[1000]; 384 while (fgets (buf, sizeof (buf), stdin)) 385 { 386 time_t t = str2time (buf, default_time, default_zone); 387 printf ("%s", asctime (gmtime (&t))); 388 } 389 return 0; 390 } 391 #endif 392