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