aboutsummaryrefslogtreecommitdiff
path: root/maketime.c
diff options
context:
space:
mode:
Diffstat (limited to 'maketime.c')
-rw-r--r--maketime.c316
1 files changed, 213 insertions, 103 deletions
diff --git a/maketime.c b/maketime.c
index e66f9df0c375..985a6d56a49a 100644
--- a/maketime.c
+++ b/maketime.c
@@ -26,6 +26,11 @@
*/
+/* For maximum portability, use only localtime and gmtime.
+ Make no assumptions about the time_t epoch or the range of time_t values.
+ Avoid mktime because it's not universal and because there's no easy,
+ portable way for mktime to yield the inverse of gmtime. */
+
#if has_conf_h
# include <conf.h>
#else
@@ -58,18 +63,29 @@
#include <partime.h>
#include <maketime.h>
-char const maketId[] =
- "$Id: maketime.c,v 5.15 1997/06/17 16:54:36 eggert Exp $";
+char const maket_id[] =
+ "$Id: maketime.c,v 5.17 1999/08/29 11:12:37 eggert Exp $";
static int isleap P ((int));
static int month_days P ((struct tm const *));
static time_t maketime P ((struct partime const *, time_t));
-/* For maximum portability, use only localtime and gmtime.
- Make no assumptions about the time_t epoch or the range of time_t values.
- Avoid mktime because it's not universal and because there's no easy,
- portable way for mktime to yield the inverse of gmtime. */
+/* Suppose A1 + B1 = SUM1, using 2's complement arithmetic ignoring overflow.
+ Suppose A, B and SUM have the same respective signs as A1, B1, and SUM1.
+ Then this yields nonzero if overflow occurred during the addition.
+ Overflow occurs if A and B have the same sign, but A and SUM differ in sign.
+ Use `^' to test whether signs differ, and `< 0' to isolate the sign. */
+#define overflow_sum_sign(a, b, sum) ((~((a) ^ (b)) & ((a) ^ (sum))) < 0)
+
+/* Quotient and remainder when dividing A by B,
+ truncating towards minus infinity, where B is positive. */
+#define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
+#define MOD(a, b) ((a) % (b) + (b) * ((a) % (b) < 0))
+/* Number of days in 400 consecutive Gregorian years. */
+#define Y400_DAYS (365 * 400L + 100 - 4 + 1)
+
+/* Number of years to add to tm_year to get Gregorian year. */
#define TM_YEAR_ORIGIN 1900
static int
@@ -121,8 +137,8 @@ difftm (a, b)
{
int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
- int ac = ay / 100 - (ay % 100 < 0);
- int bc = by / 100 - (by % 100 < 0);
+ int ac = DIV (ay, 100);
+ int bc = DIV (by, 100);
int difference_in_day_of_year = a->tm_yday - b->tm_yday;
int intervening_leap_days = (((ay >> 2) - (by >> 2))
- (ac - bc)
@@ -139,76 +155,73 @@ difftm (a, b)
+ (a->tm_sec - b->tm_sec));
}
-/*
- * Adjust time T by adding SECONDS. SECONDS must be at most 24 hours' worth.
- * Adjust only T's year, mon, mday, hour, min and sec members;
- * plus adjust wday if it is defined.
- */
+/* Adjust time T by adding SECONDS.
+ The absolute value of SECONDS cannot exceed 59 * INT_MAX,
+ and also cannot exceed one month's worth of seconds;
+ this is enough to handle any POSIX or real-life daylight-saving offset.
+ Adjust only T's year, mon, mday, hour, min and sec members;
+ plus adjust wday if it is defined. */
void
adjzone (t, seconds)
register struct tm *t;
long seconds;
{
- /*
- * This code can be off by a second if SECONDS is not a multiple of 60,
- * if T is local time, and if a leap second happens during this minute.
- * But this bug has never occurred, and most likely will not ever occur.
- * Liberia, the last country for which SECONDS % 60 was nonzero,
- * switched to UTC in May 1972; the first leap second was in June 1972.
- */
+ int days = 0;
+
+ /* This code can be off by a second if SECONDS is not a multiple of 60,
+ if T is local time, and if a leap second happens during this minute.
+ But this bug has never occurred, and most likely will not ever occur.
+ Liberia, the last country for which SECONDS % 60 was nonzero,
+ switched to UTC in May 1972; the first leap second was in June 1972. */
int leap_second = t->tm_sec == 60;
long sec = seconds + (t->tm_sec - leap_second);
if (sec < 0)
{
- if ((t->tm_min -= (59 - sec) / 60) < 0)
+ if ((t->tm_min -= (59 - sec) / 60) < 0
+ && (t->tm_hour -= (59 - t->tm_min) / 60) < 0)
{
- if ((t->tm_hour -= (59 - t->tm_min) / 60) < 0)
+ days = - ((23 - t->tm_hour) / 24);
+ if ((t->tm_mday += days) <= 0)
{
- t->tm_hour += 24;
- if (TM_DEFINED (t->tm_wday) && --t->tm_wday < 0)
- t->tm_wday = 6;
- if (--t->tm_mday <= 0)
+ if (--t->tm_mon < 0)
{
- if (--t->tm_mon < 0)
- {
- --t->tm_year;
- t->tm_mon = 11;
- }
- t->tm_mday = month_days (t);
+ --t->tm_year;
+ t->tm_mon = 11;
}
+ t->tm_mday += month_days (t);
}
- t->tm_min += 24 * 60;
}
- sec += 24L * 60 * 60;
}
- else if (60 <= (t->tm_min += sec / 60))
- if (24 <= (t->tm_hour += t->tm_min / 60))
- {
- t->tm_hour -= 24;
- if (TM_DEFINED (t->tm_wday) && ++t->tm_wday == 7)
- t->tm_wday = 0;
- if (month_days (t) < ++t->tm_mday)
- {
- if (11 < ++t->tm_mon)
- {
- ++t->tm_year;
- t->tm_mon = 0;
- }
- t->tm_mday = 1;
- }
- }
- t->tm_min %= 60;
- t->tm_sec = (int) (sec % 60) + leap_second;
+ else
+ {
+ if (60 <= (t->tm_min += sec / 60)
+ && (24 <= (t->tm_hour += t->tm_min / 60)))
+ {
+ days = t->tm_hour / 24;
+ if (month_days (t) < (t->tm_mday += days))
+ {
+ if (11 < ++t->tm_mon)
+ {
+ ++t->tm_year;
+ t->tm_mon = 0;
+ }
+ t->tm_mday = 1;
+ }
+ }
+ }
+ if (TM_DEFINED (t->tm_wday))
+ t->tm_wday = MOD (t->tm_wday + days, 7);
+ t->tm_hour = MOD (t->tm_hour, 24);
+ t->tm_min = MOD (t->tm_min, 60);
+ t->tm_sec = (int) MOD (sec, 60) + leap_second;
}
-/*
- * Convert TM to time_t, using localtime if LOCALZONE and gmtime otherwise.
- * Use only TM's year, mon, mday, hour, min, and sec members.
- * Ignore TM's old tm_yday and tm_wday, but fill in their correct values.
- * Yield -1 on failure (e.g. a member out of range).
- * Posix 1003.1-1990 doesn't allow leap seconds, but some implementations
- * have them anyway, so allow them if localtime/gmtime does.
- */
+/* Convert TM to time_t, using localtime if LOCALZONE and gmtime otherwise.
+ Use only TM's year, mon, mday, hour, min, and sec members.
+ Ignore TM's old tm_yday and tm_wday, but fill in their correct values.
+ Yield -1 on failure (e.g. a member out of range).
+ POSIX 1003.1 doesn't allow leap seconds, but some implementations
+ have them anyway, so allow them if localtime/gmtime does. */
time_t
tm2time (tm, localzone)
struct tm *tm;
@@ -220,11 +233,9 @@ tm2time (tm, localzone)
time_t d, gt;
struct tm const *gtm;
- /*
- * The maximum number of iterations should be enough to handle any
- * combinations of leap seconds, time zone rule changes, and solar time.
- * 4 is probably enough; we use a bigger number just to be safe.
- */
+ /* The maximum number of iterations should be enough to handle any
+ combinations of leap seconds, time zone rule changes, and solar time.
+ 4 is probably enough; we use a bigger number just to be safe. */
int remaining_tries = 8;
/* Avoid subscript errors. */
@@ -247,11 +258,9 @@ tm2time (tm, localzone)
gtm = time2tm (gt, localzone);
}
- /*
- * Check that the guess actually matches;
- * overflow can cause difftm to yield 0 even on differing times,
- * or tm may have members out of range (e.g. bad leap seconds).
- */
+ /* Check that the guess actually matches;
+ overflow can cause difftm to yield 0 even on differing times,
+ or tm may have members out of range (e.g. bad leap seconds). */
#define TM_DIFFER(a,b) \
( \
((a)->tm_year ^ (b)->tm_year) | \
@@ -263,11 +272,9 @@ tm2time (tm, localzone)
)
if (TM_DIFFER (tm, gtm))
{
- /*
- * If gt is a leap second, try gt+1; if it is one greater than
- * a leap second, try gt-1; otherwise, it doesn't matter.
- * Leap seconds always fall at month end.
- */
+ /* If gt is a leap second, try gt+1; if it is one greater than
+ a leap second, try gt-1; otherwise, it doesn't matter.
+ Leap seconds always fall at month end. */
int yd = tm->tm_year - gtm->tm_year;
gt += yd + (yd ? 0 : tm->tm_mon - gtm->tm_mon);
gtm = time2tm (gt, localzone);
@@ -281,29 +288,32 @@ tm2time (tm, localzone)
return gt;
}
-/*
- * Check *PT and convert it to time_t.
- * If it is incompletely specified, use DEFAULT_TIME to fill it out.
- * Use localtime if PT->zone is the special value TM_LOCAL_ZONE.
- * Yield -1 on failure.
- * ISO 8601 day-of-year and week numbers are not yet supported.
- */
+/* Check *PT and convert it to time_t.
+ If it is incompletely specified, use DEFAULT_TIME to fill it out.
+ Use localtime if PT->zone is the special value TM_LOCAL_ZONE.
+ Yield -1 on failure.
+ ISO 8601 day-of-year and week numbers are not yet supported. */
static time_t
maketime (pt, default_time)
struct partime const *pt;
time_t default_time;
{
- int localzone, wday;
+ int localzone, wday, year;
struct tm tm;
struct tm *tm0 = 0;
time_t r;
+ int use_ordinal_day;
tm0 = 0; /* Keep gcc -Wall happy. */
localzone = pt->zone == TM_LOCAL_ZONE;
tm = pt->tm;
+ year = tm.tm_year;
+ wday = tm.tm_wday;
+ use_ordinal_day = (!TM_DEFINED (tm.tm_mday)
+ && TM_DEFINED (wday) && TM_DEFINED (pt->wday_ordinal));
- if (TM_DEFINED (pt->ymodulus) || !TM_DEFINED (tm.tm_year))
+ if (use_ordinal_day || TM_DEFINED (pt->ymodulus) || !TM_DEFINED (year))
{
/* Get tm corresponding to default time. */
tm0 = time2tm (default_time, localzone);
@@ -311,13 +321,25 @@ maketime (pt, default_time)
adjzone (tm0, pt->zone);
}
+ if (use_ordinal_day)
+ tm.tm_mday = (tm0->tm_mday
+ + ((wday - tm0->tm_wday + 7) % 7
+ + 7 * (pt->wday_ordinal - (pt->wday_ordinal != 0))));
+
if (TM_DEFINED (pt->ymodulus))
- tm.tm_year +=
- (tm0->tm_year + TM_YEAR_ORIGIN) / pt->ymodulus * pt->ymodulus;
- else if (!TM_DEFINED (tm.tm_year))
+ {
+ /* Yield a year closest to the default that has the given modulus. */
+ int year0 = tm0->tm_year + TM_YEAR_ORIGIN;
+ int y0 = MOD (year0, pt->ymodulus);
+ int d = 2 * (year - y0);
+ year += (((year0 - y0) / pt->ymodulus
+ + (pt->ymodulus < d ? -1 : d < -pt->ymodulus))
+ * pt->ymodulus);
+ }
+ else if (!TM_DEFINED (year))
{
/* Set default year, month, day from current time. */
- tm.tm_year = tm0->tm_year + TM_YEAR_ORIGIN;
+ year = tm0->tm_year + TM_YEAR_ORIGIN;
if (!TM_DEFINED (tm.tm_mon))
{
tm.tm_mon = tm0->tm_mon;
@@ -326,9 +348,6 @@ maketime (pt, default_time)
}
}
- /* Convert from partime year (Gregorian) to Posix year. */
- tm.tm_year -= TM_YEAR_ORIGIN;
-
/* Set remaining default fields to be their minimum values. */
if (!TM_DEFINED (tm.tm_mon))
tm.tm_mon = 0;
@@ -341,37 +360,125 @@ maketime (pt, default_time)
if (!TM_DEFINED (tm.tm_sec))
tm.tm_sec = 0;
+ tm.tm_year = year - TM_YEAR_ORIGIN;
+ if ((year < tm.tm_year) != (TM_YEAR_ORIGIN < 0))
+ return -1;
+
if (!localzone)
- adjzone (&tm, -pt->zone);
- wday = tm.tm_wday;
+ {
+ adjzone (&tm, -pt->zone);
+ wday = tm.tm_wday;
+ }
/* Convert and fill in the rest of the tm. */
r = tm2time (&tm, localzone);
+ if (r == -1)
+ return r;
/* Check weekday. */
- if (r != -1 && TM_DEFINED (wday) && wday != tm.tm_wday)
+ if (TM_DEFINED (wday) && wday != tm.tm_wday)
return -1;
- return r;
+ /* Add relative time, except for seconds.
+ We handle seconds separately, at the end,
+ so that leap seconds are handled properly. */
+ if (pt->tmr.tm_year | pt->tmr.tm_mon | pt->tmr.tm_mday
+ | pt->tmr.tm_hour | pt->tmr.tm_min)
+ {
+ int years = tm.tm_year + pt->tmr.tm_year;
+ int mons = tm.tm_mon + pt->tmr.tm_mon;
+ int mdays = tm.tm_mday + pt->tmr.tm_mday;
+ int hours = tm.tm_hour + pt->tmr.tm_hour;
+ int mins = tm.tm_min + pt->tmr.tm_min;
+
+ int carried_hours = DIV (mins, 60);
+ int hours1 = hours + carried_hours;
+ int carried_days = DIV (hours1, 24);
+ int mdays1 = mdays + carried_days;
+
+ int mon0 = MOD (mons, 12);
+ int carried_years0 = DIV (mons, 12);
+ int year0 = years + carried_years0;
+ int yday0 = (month_yday[mon0]
+ - (mon0 < 2 || !isleap (year0 + TM_YEAR_ORIGIN)));
+
+ int yday1 = yday0 + mdays1;
+ int carried_years1 = DIV (yday1, Y400_DAYS) * 400;
+ int year1 = year0 + carried_years1;
+ int yday2 = MOD (yday1, Y400_DAYS);
+ int leap;
+
+ if (overflow_sum_sign (tm.tm_year, pt->tmr.tm_year, years)
+ | overflow_sum_sign (tm.tm_mon, pt->tmr.tm_mon, mons)
+ | overflow_sum_sign (tm.tm_mday, pt->tmr.tm_mday, mdays)
+ | overflow_sum_sign (tm.tm_hour, pt->tmr.tm_hour, hours)
+ | overflow_sum_sign (tm.tm_min, pt->tmr.tm_min, mins)
+ | overflow_sum_sign (hours, carried_hours, hours1)
+ | overflow_sum_sign (mdays, carried_days, mdays1)
+ | overflow_sum_sign (years, carried_years0, year0)
+ | overflow_sum_sign (yday0, mdays1, yday1)
+ | overflow_sum_sign (year0, carried_years1, year1))
+ return -1;
+
+ for (;;)
+ {
+ int days_per_year = 365 + (leap = isleap (year1 + TM_YEAR_ORIGIN));
+ if (yday2 < days_per_year)
+ break;
+ yday2 -= days_per_year;
+ year1++;
+ }
+
+ tm.tm_year = year1;
+
+ {
+ int mon;
+ for (mon = 11;
+ (tm.tm_mday = (yday2 - month_yday[mon] + (mon < 2 || !leap))) <= 0;
+ mon--)
+ continue;
+ tm.tm_mon = mon;
+ }
+
+ tm.tm_hour = MOD (hours1, 24);
+ tm.tm_min = MOD (mins, 60);
+
+ r = tm2time (&tm, localzone);
+ if (r == -1)
+ return r;
+ }
+
+ /* Add the seconds' part of relative time. */
+ {
+ time_t rs = r + pt->tmr.tm_sec;
+ if ((pt->tmr.tm_sec < 0) != (rs < r))
+ return -1;
+ return rs;
+ }
}
-/* Parse a free-format date in SOURCE, yielding a Unix format time. */
+/* Parse a free-format date in *SOURCE, yielding a Unix format time.
+ Update *SOURCE to point to the first character after the date.
+ If *SOURCE is missing some information, take defaults from
+ DEFAULT_TIME and DEFAULT_ZONE. *SOURCE may even be the empty
+ string or an immediately invalid string, in which case the default
+ time and zone is used.
+ Return (time_t) -1 if the time is invalid or cannot be represented. */
time_t
str2time (source, default_time, default_zone)
- char const *source;
+ char const **source;
time_t default_time;
long default_zone;
{
struct partime pt;
- if (*partime (source, &pt))
- return -1;
+ *source = partime (*source, &pt);
if (pt.zone == TM_UNDEFINED_ZONE)
pt.zone = default_zone;
return maketime (&pt, default_time);
}
-#if TEST
+#ifdef TEST
#include <stdio.h>
int
main (argc, argv)
@@ -379,12 +486,15 @@ main (argc, argv)
char **argv;
{
time_t default_time = time ((time_t *) 0);
- long default_zone = argv[1] ? atol (argv[1]) : 0;
+ long default_zone = argv[1] ? atol (argv[1]) : TM_LOCAL_ZONE;
char buf[1000];
while (fgets (buf, sizeof (buf), stdin))
{
- time_t t = str2time (buf, default_time, default_zone);
- printf ("%s", asctime (gmtime (&t)));
+ char const *p = buf;
+ time_t t = str2time (&p, default_time, default_zone);
+ printf ("`%.*s' -> %s",
+ (int) (p - buf - (p[0] == '\0' && p[-1] == '\n')), buf,
+ asctime ((argv[1] ? gmtime : localtime) (&t)));
}
return 0;
}