xref: /freebsd/crypto/krb5/src/util/support/gmt_mktime.c (revision b670c9bafc0e31c7609969bf374b2e80bdc00211)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* This code placed in the public domain by Mark W. Eichin */
3 
4 #include "autoconf.h"
5 #include <stdio.h>
6 
7 #ifdef HAVE_SYS_TYPES_H
8 #include <sys/types.h>
9 #endif
10 #ifdef HAVE_SYS_TIME_H
11 #include <sys/time.h>
12 #endif
13 #include <time.h>
14 
15 #include "k5-gmt_mktime.h"
16 
17 #if !HAVE_TIMEGM || TEST_LEAP
18 static time_t gmt_mktime(struct tm *t);
19 #endif
20 
21 /*
22  * Use the nonstandard timegm() (if available) to convert broken-down
23  * UTC times into time_t values.  Use our custom gmt_mktime() if
24  * timegm() is not available.
25  *
26  * We use gmtime() (or gmtime_r()) when encoding ASN.1 GeneralizedTime
27  * values.  On systems where a "right" (leap-second-aware) time zone
28  * is configured, gmtime() adjusts for the presence of accumulated
29  * leap seconds in the input time_t value.  POSIX requires that time_t
30  * values omit leap seconds; systems configured to include leap
31  * seconds in their time_t values are non-conforming and will have
32  * difficulties exchanging timestamp information with other systems.
33  *
34  * We use krb5int_gmt_mktime() for decoding ASN.1 GeneralizedTime
35  * values.  If timegm() is not available, krb5int_gmt_mktime() won't
36  * be the inverse of gmtime() on a system that counts leap seconds.  A
37  * system configured with a "right" time zone probably has timegm()
38  * available; without it, an application would have no reliable way of
39  * converting broken-down UTC times into time_t values.
40  */
41 time_t
42 krb5int_gmt_mktime(struct tm *t)
43 {
44 #if HAVE_TIMEGM
45     return timegm(t);
46 #else
47     return gmt_mktime(t);
48 #endif
49 }
50 
51 #if !HAVE_TIMEGM || TEST_LEAP
52 
53 /* take a struct tm, return seconds from GMT epoch */
54 /* like mktime, this ignores tm_wday and tm_yday. */
55 /* unlike mktime, this does not set them... it only passes a return value. */
56 
57 static const int days_in_month[12] = {
58     0,                              /* jan 31 */
59     31,                             /* feb 28 */
60     59,                             /* mar 31 */
61     90,                             /* apr 30 */
62     120,                            /* may 31 */
63     151,                            /* jun 30 */
64     181,                            /* jul 31 */
65     212,                            /* aug 31 */
66     243,                            /* sep 30 */
67     273,                            /* oct 31 */
68     304,                            /* nov 30 */
69     334                             /* dec 31 */
70 };
71 
72 #define hasleapday(year) (year%400?(year%100?(year%4?0:1):0):1)
73 
74 static time_t
75 gmt_mktime(struct tm *t)
76 {
77     uint32_t accum;
78 
79 #define assert_time(cnd) if(!(cnd)) return (time_t) -1
80 
81     /*
82      * For 32-bit unsigned time values starting on 1/1/1970, the range is:
83      * time 0x00000000 -> Thu Jan  1 00:00:00 1970
84      * time 0xffffffff -> Sun Feb  7 06:28:15 2106
85      *
86      * We can't encode all dates in 2106, and we're not doing overflow checking
87      * for such cases.
88      */
89     assert_time(t->tm_year>=70);
90     assert_time(t->tm_year<=206);
91 
92     assert_time(t->tm_mon>=0);
93     assert_time(t->tm_mon<=11);
94     assert_time(t->tm_mday>=1);
95     assert_time(t->tm_mday<=31);
96     assert_time(t->tm_hour>=0);
97     assert_time(t->tm_hour<=23);
98     assert_time(t->tm_min>=0);
99     assert_time(t->tm_min<=59);
100     assert_time(t->tm_sec>=0);
101     assert_time(t->tm_sec<=62);
102 
103 #undef assert_time
104 
105 
106     accum = t->tm_year - 70;
107     accum *= 365;                 /* 365 days/normal year */
108 
109     /* add in leap day for all previous years */
110     if (t->tm_year >= 70)
111         accum += (t->tm_year - 69) / 4;
112     else
113         accum -= (72 - t->tm_year) / 4;
114     /* add in leap day for this year */
115     if(t->tm_mon >= 2)            /* march or later */
116         if(hasleapday((t->tm_year + 1900))) accum += 1;
117 
118     accum += days_in_month[t->tm_mon];
119     accum += t->tm_mday-1;        /* days of month are the only 1-based field */
120     accum *= 24;                  /* 24 hour/day */
121     accum += t->tm_hour;
122     accum *= 60;                  /* 60 minute/hour */
123     accum += t->tm_min;
124     accum *= 60;                  /* 60 seconds/minute */
125     accum += t->tm_sec;
126 
127     return accum;
128 }
129 #endif /* !HAVE_TIMEGM || TEST_LEAP */
130 
131 #ifdef TEST_LEAP
132 int
133 main (int argc, char *argv[])
134 {
135     int yr;
136     time_t t;
137     struct tm tm = {
138         .tm_mon = 0, .tm_mday = 1,
139         .tm_hour = 0, .tm_min = 0, .tm_sec = 0,
140     };
141     for (yr = 60; yr <= 104; yr++)
142     {
143         printf ("1/1/%d%c -> ", 1900 + yr, hasleapday((1900+yr)) ? '*' : ' ');
144         tm.tm_year = yr;
145         t = gmt_mktime (&tm);
146         if (t == (time_t) -1)
147             printf ("-1\n");
148         else
149         {
150             long u;
151             if (t % (24 * 60 * 60))
152                 printf ("(not integral multiple of days) ");
153             u = t / (24 * 60 * 60);
154             printf ("%3ld*365%+ld\t0x%08lx\n",
155                     (long) (u / 365), (long) (u % 365),
156                     (long) t);
157         }
158     }
159     t = 0x80000000, printf ("time 0x%lx -> %s", t, ctime (&t));
160     t = 0x7fffffff, printf ("time 0x%lx -> %s", t, ctime (&t));
161     return 0;
162 }
163 #endif
164