111894Speter/* Convert struct partime into time_t. */ 29Sjkh 311894Speter/* Copyright 1992, 1993, 1994, 1995 Paul Eggert 411894Speter Distributed under license by the Free Software Foundation, Inc. 59Sjkh 611894SpeterThis file is part of RCS. 79Sjkh 811894SpeterRCS is free software; you can redistribute it and/or modify 911894Speterit under the terms of the GNU General Public License as published by 1011894Speterthe Free Software Foundation; either version 2, or (at your option) 1111894Speterany later version. 129Sjkh 1311894SpeterRCS is distributed in the hope that it will be useful, 1411894Speterbut WITHOUT ANY WARRANTY; without even the implied warranty of 1511894SpeterMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1611894SpeterGNU General Public License for more details. 179Sjkh 1811894SpeterYou should have received a copy of the GNU General Public License 1911894Speteralong with RCS; see the file COPYING. 2011894SpeterIf not, write to the Free Software Foundation, 2111894Speter59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 229Sjkh 2311894SpeterReport problems and direct all questions to: 249Sjkh 2511894Speter rcs-bugs@cs.purdue.edu 269Sjkh 2711894Speter*/ 289Sjkh 2911894Speter#if has_conf_h 3011894Speter# include "conf.h" 3111894Speter#else 3211894Speter# ifdef __STDC__ 3311894Speter# define P(x) x 3411894Speter# else 3511894Speter# define const 3611894Speter# define P(x) () 3711894Speter# endif 3811894Speter# include <stdlib.h> 3911894Speter# include <time.h> 409Sjkh#endif 419Sjkh 4211894Speter#include "partime.h" 4311894Speter#include "maketime.h" 449Sjkh 4511894Speterchar const maketId[] 4650472Speter = "$FreeBSD$"; 479Sjkh 4811894Speterstatic int isleap P((int)); 4911894Speterstatic int month_days P((struct tm const*)); 5011894Speterstatic time_t maketime P((struct partime const*,time_t)); 519Sjkh 5211894Speter/* 5311894Speter* For maximum portability, use only localtime and gmtime. 5411894Speter* Make no assumptions about the time_t epoch or the range of time_t values. 5511894Speter* Avoid mktime because it's not universal and because there's no easy, 5611894Speter* portable way for mktime to yield the inverse of gmtime. 5711894Speter*/ 589Sjkh 5911894Speter#define TM_YEAR_ORIGIN 1900 609Sjkh 6111894Speter static int 6211894Speterisleap(y) 6311894Speter int y; 6411894Speter{ 6511894Speter return (y&3) == 0 && (y%100 != 0 || y%400 == 0); 6611894Speter} 679Sjkh 6811894Speterstatic int const month_yday[] = { 6911894Speter /* days in year before start of months 0-12 */ 7011894Speter 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 7111894Speter}; 729Sjkh 7311894Speter/* Yield the number of days in TM's month. */ 7411894Speter static int 7511894Spetermonth_days(tm) 7611894Speter struct tm const *tm; 7711894Speter{ 7811894Speter int m = tm->tm_mon; 7911894Speter return month_yday[m+1] - month_yday[m] 8011894Speter + (m==1 && isleap(tm->tm_year + TM_YEAR_ORIGIN)); 819Sjkh} 829Sjkh 839Sjkh/* 8411894Speter* Convert UNIXTIME to struct tm form. 8511894Speter* Use gmtime if available and if !LOCALZONE, localtime otherwise. 869Sjkh*/ 8711894Speter struct tm * 8811894Spetertime2tm(unixtime, localzone) 899Sjkh time_t unixtime; 9011894Speter int localzone; 919Sjkh{ 9211894Speter struct tm *tm; 939Sjkh# if TZ_must_be_set 949Sjkh static char const *TZ; 959Sjkh if (!TZ && !(TZ = getenv("TZ"))) 9611894Speter faterror("The TZ environment variable is not set; please set it to your timezone"); 979Sjkh# endif 9811894Speter if (localzone || !(tm = gmtime(&unixtime))) 9911894Speter tm = localtime(&unixtime); 1009Sjkh return tm; 1019Sjkh} 1029Sjkh 10311894Speter/* Yield A - B, measured in seconds. */ 10411894Speter time_t 10511894Speterdifftm(a, b) 10611894Speter struct tm const *a, *b; 10711894Speter{ 10811894Speter int ay = a->tm_year + (TM_YEAR_ORIGIN - 1); 10911894Speter int by = b->tm_year + (TM_YEAR_ORIGIN - 1); 11011894Speter int difference_in_day_of_year = a->tm_yday - b->tm_yday; 11111894Speter int intervening_leap_days = ( 11211894Speter ((ay >> 2) - (by >> 2)) 11311894Speter - (ay/100 - by/100) 11411894Speter + ((ay/100 >> 2) - (by/100 >> 2)) 11511894Speter ); 11611894Speter time_t difference_in_years = ay - by; 11711894Speter time_t difference_in_days = ( 11811894Speter difference_in_years*365 11911894Speter + (intervening_leap_days + difference_in_day_of_year) 12011894Speter ); 12111894Speter return 12211894Speter ( 12311894Speter ( 12411894Speter 24*difference_in_days 12511894Speter + (a->tm_hour - b->tm_hour) 12611894Speter )*60 + (a->tm_min - b->tm_min) 12711894Speter )*60 + (a->tm_sec - b->tm_sec); 12811894Speter} 12911894Speter 1309Sjkh/* 13111894Speter* Adjust time T by adding SECONDS. SECONDS must be at most 24 hours' worth. 13211894Speter* Adjust only T's year, mon, mday, hour, min and sec members; 13311894Speter* plus adjust wday if it is defined. 1349Sjkh*/ 1359Sjkh void 13611894Speteradjzone(t, seconds) 13711894Speter register struct tm *t; 13811894Speter long seconds; 1399Sjkh{ 14011894Speter /* 14111894Speter * This code can be off by a second if SECONDS is not a multiple of 60, 14211894Speter * if T is local time, and if a leap second happens during this minute. 14311894Speter * But this bug has never occurred, and most likely will not ever occur. 14411894Speter * Liberia, the last country for which SECONDS % 60 was nonzero, 14511894Speter * switched to UTC in May 1972; the first leap second was in June 1972. 14611894Speter */ 14711894Speter int leap_second = t->tm_sec == 60; 14811894Speter long sec = seconds + (t->tm_sec - leap_second); 14911894Speter if (sec < 0) { 15011894Speter if ((t->tm_min -= (59-sec)/60) < 0) { 15111894Speter if ((t->tm_hour -= (59-t->tm_min)/60) < 0) { 15211894Speter t->tm_hour += 24; 15311894Speter if (TM_DEFINED(t->tm_wday) && --t->tm_wday < 0) 15411894Speter t->tm_wday = 6; 15511894Speter if (--t->tm_mday <= 0) { 15611894Speter if (--t->tm_mon < 0) { 15711894Speter --t->tm_year; 15811894Speter t->tm_mon = 11; 15911894Speter } 16011894Speter t->tm_mday = month_days(t); 16111894Speter } 16211894Speter } 16311894Speter t->tm_min += 24 * 60; 16411894Speter } 16511894Speter sec += 24L * 60 * 60; 16611894Speter } else 16711894Speter if (60 <= (t->tm_min += sec/60)) 16811894Speter if (24 <= (t->tm_hour += t->tm_min/60)) { 16911894Speter t->tm_hour -= 24; 17011894Speter if (TM_DEFINED(t->tm_wday) && ++t->tm_wday == 7) 17111894Speter t->tm_wday = 0; 17211894Speter if (month_days(t) < ++t->tm_mday) { 17311894Speter if (11 < ++t->tm_mon) { 17411894Speter ++t->tm_year; 17511894Speter t->tm_mon = 0; 17611894Speter } 17711894Speter t->tm_mday = 1; 17811894Speter } 17911894Speter } 18011894Speter t->tm_min %= 60; 18111894Speter t->tm_sec = (int) (sec%60) + leap_second; 1829Sjkh} 1839Sjkh 18411894Speter/* 18511894Speter* Convert TM to time_t, using localtime if LOCALZONE and gmtime otherwise. 18611894Speter* Use only TM's year, mon, mday, hour, min, and sec members. 18711894Speter* Ignore TM's old tm_yday and tm_wday, but fill in their correct values. 18811894Speter* Yield -1 on failure (e.g. a member out of range). 18911894Speter* Posix 1003.1-1990 doesn't allow leap seconds, but some implementations 19011894Speter* have them anyway, so allow them if localtime/gmtime does. 19111894Speter*/ 19211894Speter time_t 19311894Spetertm2time(tm, localzone) 19411894Speter struct tm *tm; 19511894Speter int localzone; 19611894Speter{ 19711894Speter /* Cache the most recent t,tm pairs; 1 for gmtime, 1 for localtime. */ 19811894Speter static time_t t_cache[2]; 19911894Speter static struct tm tm_cache[2]; 2009Sjkh 20111894Speter time_t d, gt; 20211894Speter struct tm const *gtm; 20311894Speter /* 20411894Speter * The maximum number of iterations should be enough to handle any 20511894Speter * combinations of leap seconds, time zone rule changes, and solar time. 20611894Speter * 4 is probably enough; we use a bigger number just to be safe. 20711894Speter */ 20811894Speter int remaining_tries = 8; 2099Sjkh 21011894Speter /* Avoid subscript errors. */ 21111894Speter if (12 <= (unsigned)tm->tm_mon) 21211894Speter return -1; 21311894Speter 21411894Speter tm->tm_yday = month_yday[tm->tm_mon] + tm->tm_mday 21511894Speter - (tm->tm_mon<2 || ! isleap(tm->tm_year + TM_YEAR_ORIGIN)); 21611894Speter 21711894Speter /* Make a first guess. */ 21811894Speter gt = t_cache[localzone]; 21911894Speter gtm = gt ? &tm_cache[localzone] : time2tm(gt,localzone); 22011894Speter 22111894Speter /* Repeatedly use the error from the guess to improve the guess. */ 22211894Speter while ((d = difftm(tm, gtm)) != 0) { 22311894Speter if (--remaining_tries == 0) 22411894Speter return -1; 22511894Speter gt += d; 22611894Speter gtm = time2tm(gt,localzone); 22711894Speter } 22811894Speter t_cache[localzone] = gt; 22911894Speter tm_cache[localzone] = *gtm; 23011894Speter 23111894Speter /* 23211894Speter * Check that the guess actually matches; 23311894Speter * overflow can cause difftm to yield 0 even on differing times, 23411894Speter * or tm may have members out of range (e.g. bad leap seconds). 23511894Speter */ 23611894Speter if ( (tm->tm_year ^ gtm->tm_year) 23711894Speter | (tm->tm_mon ^ gtm->tm_mon) 23811894Speter | (tm->tm_mday ^ gtm->tm_mday) 23911894Speter | (tm->tm_hour ^ gtm->tm_hour) 24011894Speter | (tm->tm_min ^ gtm->tm_min) 24111894Speter | (tm->tm_sec ^ gtm->tm_sec)) 24211894Speter return -1; 24311894Speter 24411894Speter tm->tm_wday = gtm->tm_wday; 24511894Speter return gt; 24611894Speter} 24711894Speter 24811894Speter/* 24911894Speter* Check *PT and convert it to time_t. 25011894Speter* If it is incompletely specified, use DEFAULT_TIME to fill it out. 25111894Speter* Use localtime if PT->zone is the special value TM_LOCAL_ZONE. 25211894Speter* Yield -1 on failure. 25311894Speter* ISO 8601 day-of-year and week numbers are not yet supported. 25411894Speter*/ 2559Sjkh static time_t 25611894Spetermaketime(pt, default_time) 25711894Speter struct partime const *pt; 25811894Speter time_t default_time; 2599Sjkh{ 26011894Speter int localzone, wday; 26111894Speter struct tm tm; 26211894Speter struct tm *tm0 = 0; 26311894Speter time_t r; 2649Sjkh 26511894Speter tm0 = 0; /* Keep gcc -Wall happy. */ 26611894Speter localzone = pt->zone==TM_LOCAL_ZONE; 26711894Speter 26811894Speter tm = pt->tm; 26911894Speter 27011894Speter if (TM_DEFINED(pt->ymodulus) || !TM_DEFINED(tm.tm_year)) { 27111894Speter /* Get tm corresponding to current time. */ 27211894Speter tm0 = time2tm(default_time, localzone); 27311894Speter if (!localzone) 27411894Speter adjzone(tm0, pt->zone); 27511894Speter } 27611894Speter 27711894Speter if (TM_DEFINED(pt->ymodulus)) 27811894Speter tm.tm_year += 27911894Speter (tm0->tm_year + TM_YEAR_ORIGIN)/pt->ymodulus * pt->ymodulus; 28011894Speter else if (!TM_DEFINED(tm.tm_year)) { 28111894Speter /* Set default year, month, day from current time. */ 28211894Speter tm.tm_year = tm0->tm_year + TM_YEAR_ORIGIN; 28311894Speter if (!TM_DEFINED(tm.tm_mon)) { 28411894Speter tm.tm_mon = tm0->tm_mon; 28511894Speter if (!TM_DEFINED(tm.tm_mday)) 28611894Speter tm.tm_mday = tm0->tm_mday; 28711894Speter } 28811894Speter } 28911894Speter 29011894Speter /* Convert from partime year (Gregorian) to Posix year. */ 29111894Speter tm.tm_year -= TM_YEAR_ORIGIN; 29211894Speter 29311894Speter /* Set remaining default fields to be their minimum values. */ 29411894Speter if (!TM_DEFINED(tm.tm_mon)) tm.tm_mon = 0; 29511894Speter if (!TM_DEFINED(tm.tm_mday)) tm.tm_mday = 1; 29611894Speter if (!TM_DEFINED(tm.tm_hour)) tm.tm_hour = 0; 29711894Speter if (!TM_DEFINED(tm.tm_min)) tm.tm_min = 0; 29811894Speter if (!TM_DEFINED(tm.tm_sec)) tm.tm_sec = 0; 29911894Speter 30011894Speter if (!localzone) 30111894Speter adjzone(&tm, -pt->zone); 30211894Speter wday = tm.tm_wday; 30311894Speter 30411894Speter /* Convert and fill in the rest of the tm. */ 30511894Speter r = tm2time(&tm, localzone); 30611894Speter 30711894Speter /* Check weekday. */ 30811894Speter if (r != -1 && TM_DEFINED(wday) && wday != tm.tm_wday) 30911894Speter return -1; 31011894Speter 31111894Speter return r; 3129Sjkh} 3139Sjkh 31411894Speter/* Parse a free-format date in SOURCE, yielding a Unix format time. */ 31511894Speter time_t 31611894Speterstr2time(source, default_time, default_zone) 3179Sjkh char const *source; 31811894Speter time_t default_time; 31911894Speter long default_zone; 3209Sjkh{ 32111894Speter struct partime pt; 32211894Speter 32311894Speter if (*partime(source, &pt)) 32411894Speter return -1; 32511894Speter if (pt.zone == TM_UNDEFINED_ZONE) 32611894Speter pt.zone = default_zone; 32711894Speter return maketime(&pt, default_time); 3289Sjkh} 3299Sjkh 33011894Speter#if TEST 33111894Speter#include <stdio.h> 3329Sjkh int 33311894Spetermain(argc, argv) int argc; char **argv; 3349Sjkh{ 33511894Speter time_t default_time = time((time_t *)0); 33611894Speter long default_zone = argv[1] ? atol(argv[1]) : 0; 33711894Speter char buf[1000]; 33812553Sjkh while (fgets(buf, 1000, stdin)) { 33911894Speter time_t t = str2time(buf, default_time, default_zone); 34011894Speter printf("%s", asctime(gmtime(&t))); 34111894Speter } 34211894Speter return 0; 3439Sjkh} 34411894Speter#endif 345