• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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