-
Notifications
You must be signed in to change notification settings - Fork 10
/
timegm.c
120 lines (105 loc) · 2.93 KB
/
timegm.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include "timegm.h"
/*
* UTC version of mktime(3)
*/
/*
* This code is not portable, but works on most Unix-like systems.
* If the local timezone has no summer time, using mktime(3) function
* and adjusting offset would be usable (adjusting leap seconds
* is still required, though), but the assumption is not always true.
*
* Anyway, no portable and correct implementation of UTC to time_t
* conversion exists....
*/
#define EPOCH_YEAR 1970
#define TM_YEAR_BASE 1900
static time_t
sub_mkgmt(struct tm *tm)
{
int y, nleapdays;
time_t t;
/* days before the month */
static const unsigned short moff[12] = {
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};
/*
* XXX: This code assumes the given time to be normalized.
* Normalizing here is impossible in case the given time is a leap
* second but the local time library is ignorant of leap seconds.
*/
/* minimal sanity checking not to access outside of the array */
if ((unsigned) tm->tm_mon >= 12)
return (time_t) -1;
if (tm->tm_year < EPOCH_YEAR - TM_YEAR_BASE)
return (time_t) -1;
y = tm->tm_year + TM_YEAR_BASE - (tm->tm_mon < 2);
nleapdays = y / 4 - y / 100 + y / 400 -
((EPOCH_YEAR-1) / 4 - (EPOCH_YEAR-1) / 100 + (EPOCH_YEAR-1) / 400);
t = ((((time_t) (tm->tm_year - (EPOCH_YEAR - TM_YEAR_BASE)) * 365 +
moff[tm->tm_mon] + tm->tm_mday - 1 + nleapdays) * 24 +
tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec;
return (t < 0 ? (time_t) -1 : t);
}
time_t
timegm(struct tm *tm)
{
time_t t, t2;
struct tm *tm2;
int sec;
/* Do the first guess. */
if ((t = sub_mkgmt(tm)) == (time_t) -1)
return (time_t) -1;
/* save value in case *tm is overwritten by gmtime() */
sec = tm->tm_sec;
tm2 = gmtime(&t);
if ((t2 = sub_mkgmt(tm2)) == (time_t) -1)
return (time_t) -1;
if (t2 < t || tm2->tm_sec != sec) {
/*
* Adjust for leap seconds.
*
* real time_t time
* |
* tm
* / ... (a) first sub_mkgmt() conversion
* t
* |
* tm2
* / ... (b) second sub_mkgmt() conversion
* t2
* --->time
*/
/*
* Do the second guess, assuming (a) and (b) are almost equal.
*/
t += t - t2;
tm2 = gmtime(&t);
/*
* Either (a) or (b), may include one or two extra
* leap seconds. Try t, t + 2, t - 2, t + 1, and t - 1.
*/
if (tm2->tm_sec == sec
|| (t += 2, tm2 = gmtime(&t), tm2->tm_sec == sec)
|| (t -= 4, tm2 = gmtime(&t), tm2->tm_sec == sec)
|| (t += 3, tm2 = gmtime(&t), tm2->tm_sec == sec)
|| (t -= 2, tm2 = gmtime(&t), tm2->tm_sec == sec))
; /* found */
else {
/*
* Not found.
*/
if (sec >= 60)
/*
* The given time is a leap second
* (sec 60 or 61), but the time library
* is ignorant of the leap second.
*/
; /* treat sec 60 as 59,
sec 61 as 0 of the next minute */
else
/* The given time may not be normalized. */
t++; /* restore t */
}
}
return (t < 0 ? (time_t) -1 : t);
}