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