1 #include "time_impl.h"
2 #include <stdint.h>
3 #include <limits.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <sys/mman.h>
7 #include "libc.h"
8 #include <pthread.h>
9
10 long __timezone = 0;
11 int __daylight = 0;
12 char *__tzname[2] = { 0, 0 };
13
14 weak_alias(__timezone, timezone);
15 weak_alias(__daylight, daylight);
16 weak_alias(__tzname, tzname);
17
18 #ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
19 #define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP PTHREAD_MUTEX_INITIALIZER
20 #endif
21
22 static pthread_mutex_t locallock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
23
LOCK(void)24 static int LOCK(void)
25 {
26 return pthread_mutex_lock(&locallock);
27 }
28
UNLOCK(void)29 static void UNLOCK(void)
30 {
31 (void)pthread_mutex_unlock(&locallock);
32 }
33
34
35 const char __utc[] = "UTC";
36
37 static int dst_off;
38 static int r0[5], r1[5];
39
40 static const unsigned char *zi, *trans, *index_local, *types, *abbrevs, *abbrevs_end;
41
42 #define VEC(...) ((const unsigned char[]){__VA_ARGS__})
43
zi_read32(const unsigned char * z)44 static uint32_t zi_read32(const unsigned char *z)
45 {
46 return (unsigned)z[0]<<24 | z[1]<<16 | z[2]<<8 | z[3];
47 }
48
do_tzset(void)49 static void do_tzset(void)
50 {
51 }
52
53 /* Search zoneinfo rules to find the one that applies to the given time,
54 * and determine alternate opposite-DST-status rule that may be needed. */
55
scan_trans(long long t,int local,size_t * alt)56 static size_t scan_trans(long long t, int local, size_t *alt)
57 {
58 int scale = 3 - (trans == zi+44);
59 uint64_t x;
60 int off = 0;
61
62 size_t a = 0, n = (index_local-trans)>>scale, m;
63
64 if (!n) {
65 if (alt) *alt = 0;
66 return 0;
67 }
68
69 /* Binary search for 'most-recent rule before t'. */
70 while (n > 1) {
71 m = a + n/2;
72 x = zi_read32(trans + (m<<scale));
73 if (scale == 3) x = x<<32 | zi_read32(trans + (m<<scale) + 4);
74 else x = (int32_t)x;
75 if (local) off = (int32_t)zi_read32(types + 6 * index_local[m-1]);
76 if (t - off < (int64_t)x) {
77 n /= 2;
78 } else {
79 a = m;
80 n -= n/2;
81 }
82 }
83
84 /* First and last entry are special. First means to use lowest-index_local
85 * non-DST type. Last means to apply POSIX-style rule if available. */
86 n = (index_local-trans)>>scale;
87 if (a == n-1) return -1;
88 if (a == 0) {
89 x = zi_read32(trans + (a<<scale));
90 if (scale == 3) x = x<<32 | zi_read32(trans + (a<<scale) + 4);
91 else x = (int32_t)x;
92 if (local) off = (int32_t)zi_read32(types + 6 * index_local[a-1]);
93 if (t - off < (int64_t)x) {
94 for (a=0; a<(abbrevs-types)/6; a++) {
95 if (types[6*a+4] != types[4]) break;
96 }
97 if (a == (abbrevs-types)/6) a = 0;
98 if (types[6*a+4]) {
99 *alt = a;
100 return 0;
101 } else {
102 *alt = 0;
103 return a;
104 }
105 }
106 }
107
108 /* Try to find a neighboring opposite-DST-status rule. */
109 if (alt) {
110 if (a && types[6*index_local[a-1]+4] != types[6*index_local[a]+4])
111 *alt = index_local[a-1];
112 else if (a+1<n && types[6*index_local[a+1]+4] != types[6*index_local[a]+4])
113 *alt = index_local[a+1];
114 else
115 *alt = index_local[a];
116 }
117
118 return index_local[a];
119 }
120
days_in_month(int m,int is_leap)121 static int days_in_month(int m, int is_leap)
122 {
123 if (m==2) return 28+is_leap;
124 else return 30+((0xad5>>(m-1))&1);
125 }
126
127 /* Convert a POSIX DST rule plus year to seconds since epoch. */
128
rule_to_secs(const int * rule,int year)129 static long long rule_to_secs(const int *rule, int year)
130 {
131 int is_leap;
132 long long t = __year_to_secs(year, &is_leap);
133 int x, m, n, d;
134 if (rule[0]!='M') {
135 x = rule[1];
136 if (rule[0]=='J' && (x < 60 || !is_leap)) x--;
137 t += 86400 * x;
138 } else {
139 m = rule[1];
140 n = rule[2];
141 d = rule[3];
142 t += __month_to_secs(m-1, is_leap);
143 int wday = (int)((t + 4*86400) % (7*86400)) / 86400;
144 int days = d - wday;
145 if (days < 0) days += 7;
146 if (n == 5 && days+28 >= days_in_month(m, is_leap)) n = 4;
147 t += 86400 * (days + 7*(n-1));
148 }
149 t += rule[4];
150 return t;
151 }
152
153 /* Determine the time zone in effect for a given time in seconds since the
154 * epoch. It can be given in local or universal time. The results will
155 * indicate whether DST is in effect at the queried time, and will give both
156 * the GMT offset for the active zone/DST rule and the opposite DST. This
157 * enables a caller to efficiently adjust for the case where an explicit
158 * DST specification mismatches what would be in effect at the time. */
159
__secs_to_zone(long long t,int local,int * isdst,long * offset,long * oppoff,const char ** zonename)160 void __secs_to_zone(long long t, int local, int *isdst, long *offset, long *oppoff, const char **zonename)
161 {
162 LOCK();
163
164 do_tzset();
165
166 if (zi) {
167 size_t alt, i = scan_trans(t, local, &alt);
168 if (i != -1) {
169 *isdst = types[6*i+4];
170 *offset = (int32_t)zi_read32(types+6*i);
171 *zonename = (const char *)abbrevs + types[6*i+5];
172 if (oppoff) *oppoff = (int32_t)zi_read32(types+6*alt);
173 UNLOCK();
174 return;
175 }
176 }
177
178 if (!__daylight) goto std;
179
180 /* FIXME: may be broken if DST changes right at year boundary?
181 * Also, this could be more efficient.*/
182 long long y = t / 31556952 + 70;
183 while (__year_to_secs(y, 0) > t) y--;
184 while (__year_to_secs(y+1, 0) < t) y++;
185
186 long long t0 = rule_to_secs(r0, y);
187 long long t1 = rule_to_secs(r1, y);
188
189 if (!local) {
190 t0 += __timezone;
191 t1 += dst_off;
192 }
193 if (t0 < t1) {
194 if (t >= t0 && t < t1) goto dst;
195 goto std;
196 } else {
197 if (t >= t1 && t < t0) goto std;
198 goto dst;
199 }
200 std:
201 *isdst = 0;
202 *offset = -__timezone;
203 if (oppoff) *oppoff = -dst_off;
204 *zonename = __tzname[0];
205 UNLOCK();
206 return;
207 dst:
208 *isdst = 1;
209 *offset = -dst_off;
210 if (oppoff) *oppoff = -__timezone;
211 *zonename = __tzname[1];
212 UNLOCK();
213 }
214
__tzset(void)215 static void __tzset(void)
216 {
217 LOCK();
218 do_tzset();
219 UNLOCK();
220 }
221
222 weak_alias(__tzset, tzset);
223
__tm_to_tzname(const struct tm * tm)224 const char *__tm_to_tzname(const struct tm *tm)
225 {
226 const void *p = tm->__tm_zone;
227 LOCK();
228 do_tzset();
229 if (p != __utc && p != __tzname[0] && p != __tzname[1] &&
230 (!zi || (uintptr_t)p-(uintptr_t)abbrevs >= abbrevs_end - abbrevs))
231 p = "";
232 UNLOCK();
233 return p;
234 }
235