This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Remove all of localtime64's own includes. Perl's already done that and we're loading...
[perl5.git] / localtime64.c
CommitLineData
a272e669
MS
1/*
2
3Copyright (c) 2007-2008 Michael G Schwern
4
5This software originally derived from Paul Sheer's pivotal_gmtime_r.c.
6
7The MIT License:
8
9Permission is hereby granted, free of charge, to any person obtaining a copy
10of this software and associated documentation files (the "Software"), to deal
11in the Software without restriction, including without limitation the rights
12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13copies of the Software, and to permit persons to whom the Software is
14furnished to do so, subject to the following conditions:
15
16The above copyright notice and this permission notice shall be included in
17all copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25THE SOFTWARE.
26
27*/
28
29/*
30
31Programmers who have available to them 64-bit time values as a 'long
32long' type can use localtime64_r() and gmtime64_r() which correctly
33converts the time even on 32-bit systems. Whether you have 64-bit time
34values will depend on the operating system.
35
36localtime64_r() is a 64-bit equivalent of localtime_r().
37
38gmtime64_r() is a 64-bit equivalent of gmtime_r().
39
40*/
41
a272e669
MS
42static const int days_in_month[2][12] = {
43 {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
44 {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
45};
46
47static const int julian_days_by_month[2][12] = {
48 {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
49 {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335},
50};
51
52static const int length_of_year[2] = { 365, 366 };
53
54/* Number of days in a 400 year Gregorian cycle */
55static const int years_in_gregorian_cycle = 400;
56static const int days_in_gregorian_cycle = (365 * 400) + 100 - 4 + 1;
57
58/* 28 year calendar cycle between 2010 and 2037 */
59static const int safe_years[28] = {
60 2016, 2017, 2018, 2019,
61 2020, 2021, 2022, 2023,
62 2024, 2025, 2026, 2027,
63 2028, 2029, 2030, 2031,
64 2032, 2033, 2034, 2035,
65 2036, 2037, 2010, 2011,
66 2012, 2013, 2014, 2015
67};
68
69static const int dow_year_start[28] = {
70 5, 0, 1, 2, /* 2016 - 2019 */
71 3, 5, 6, 0,
72 1, 3, 4, 5,
73 6, 1, 2, 3,
74 4, 6, 0, 1,
75 2, 4, 5, 6, /* 2036, 2037, 2010, 2011 */
76 0, 2, 3, 4 /* 2012, 2013, 2014, 2015 */
77};
78
9af24521
MS
79/* Let's assume people are going to be looking for dates in the future.
80 Let's provide some cheats so you can skip ahead.
81 This has a 4x speed boost when near 2008.
82*/
83/* Number of days since epoch on Jan 1st, 2008 GMT */
84#define CHEAT_DAYS (1199145600 / 24 / 60 / 60)
85#define CHEAT_YEARS 108
a272e669
MS
86
87#define IS_LEAP(n) ((!(((n) + 1900) % 400) || (!(((n) + 1900) % 4) && (((n) + 1900) % 100))) != 0)
88#define WRAP(a,b,m) ((a) = ((a) < 0 ) ? ((b)--, (a) + (m)) : (a))
89
a64acb40
MS
90#define SHOULD_USE_SYSTEM_LOCALTIME(a) ( USE_SYSTEM_LOCALTIME && (a) <= SYSTEM_LOCALTIME_MAX )
91#define SHOULD_USE_SYSTEM_GMTIME(a) ( USE_SYSTEM_GMTIME && (a) <= SYSTEM_GMTIME_MAX )
92
93
9af24521 94int _is_exception_century(Int64 year)
a272e669
MS
95{
96 int is_exception = ((year % 100 == 0) && !(year % 400 == 0));
97 /* printf("is_exception_century: %s\n", is_exception ? "yes" : "no"); */
98
99 return(is_exception);
100}
101
9af24521
MS
102
103/* timegm() is a GNU extension, so emulate it here if we need it */
104#ifdef HAS_TIMEGM
105# define TIMEGM(n) timegm(n);
106#else
107# define TIMEGM(n) _my_timegm(n);
a272e669
MS
108#endif
109
9af24521
MS
110time_t _my_timegm(struct tm *date) {
111 int days = 0;
112 int seconds = 0;
113 time_t time;
114 int year;
a272e669 115
9af24521
MS
116 if( date->tm_year > 70 ) {
117 year = 70;
118 while( year < date->tm_year ) {
119 days += length_of_year[IS_LEAP(year)];
120 year++;
a272e669
MS
121 }
122 }
9af24521
MS
123 else if ( date->tm_year < 70 ) {
124 year = 69;
125 do {
126 days -= length_of_year[IS_LEAP(year)];
127 year--;
128 } while( year >= date->tm_year );
129 }
130
131 days += julian_days_by_month[IS_LEAP(date->tm_year)][date->tm_mon];
132 days += date->tm_mday - 1;
133
134 seconds += date->tm_hour * 60 * 60;
135 seconds += date->tm_min * 60;
136 seconds += date->tm_sec;
137
138 time = (time_t)(days * 60 * 60 * 24) + seconds;
139
140 return(time);
141}
142
143
a64acb40
MS
144#ifdef NDEBUG
145#define CHECK_TM(a)
146#else
147#define CHECK_TM(a) _check_tm(a);
148
9af24521
MS
149void _check_tm(struct tm *tm)
150{
151 int is_leap = IS_LEAP(tm->tm_year);
152
153 /* Don't forget leap seconds */
154 assert(tm->tm_sec >= 0);
155 assert(tm->tm_sec <= 61);
156
157 assert(tm->tm_min >= 0);
158 assert(tm->tm_min <= 59);
159
160 assert(tm->tm_hour >= 0);
161 assert(tm->tm_hour <= 23);
162
163 assert(tm->tm_mday >= 1);
164 assert(tm->tm_mday <= days_in_month[is_leap][tm->tm_mon]);
165
166 assert(tm->tm_mon >= 0);
167 assert(tm->tm_mon <= 11);
168
169 assert(tm->tm_wday >= 0);
170 assert(tm->tm_wday <= 6);
171
172 assert(tm->tm_yday >= 0);
173 assert(tm->tm_yday <= length_of_year[is_leap]);
174
175#ifdef HAS_TM_TM_GMTOFF
176 assert(tm->tm_gmtoff >= -24 * 60 * 60);
177 assert(tm->tm_gmtoff <= 24 * 60 * 60);
178#endif
a272e669 179}
a64acb40
MS
180#endif
181
a272e669
MS
182
183/* The exceptional centuries without leap years cause the cycle to
184 shift by 16
185*/
9af24521 186int _cycle_offset(Int64 year)
a272e669 187{
9af24521
MS
188 const Int64 start_year = 2000;
189 Int64 year_diff = year - start_year - 1;
190 Int64 exceptions = year_diff / 100;
a272e669
MS
191 exceptions -= year_diff / 400;
192
193 assert( year >= 2001 );
194
195 /* printf("year: %d, exceptions: %d\n", year, exceptions); */
196
197 return exceptions * 16;
198}
199
200/* For a given year after 2038, pick the latest possible matching
201 year in the 28 year calendar cycle.
202*/
203#define SOLAR_CYCLE_LENGTH 28
9af24521 204int _safe_year(Int64 year)
a272e669
MS
205{
206 int safe_year;
9af24521 207 Int64 year_cycle = year + _cycle_offset(year);
a272e669
MS
208
209 /* Change non-leap xx00 years to an equivalent */
210 if( _is_exception_century(year) )
211 year_cycle += 11;
212
213 year_cycle %= SOLAR_CYCLE_LENGTH;
214
215 safe_year = safe_years[year_cycle];
216
217 assert(safe_year <= 2037 && safe_year >= 2010);
218
219 /*
220 printf("year: %d, year_cycle: %d, safe_year: %d\n",
221 year, year_cycle, safe_year);
222 */
223
224 return safe_year;
225}
226
227struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p)
228{
229 int v_tm_sec, v_tm_min, v_tm_hour, v_tm_mon, v_tm_wday;
9af24521 230 Int64 v_tm_tday;
a272e669 231 int leap;
9af24521 232 Int64 m;
a272e669 233 Time64_T time = *in_time;
9af24521 234 Int64 year = 70;
a272e669 235
a64acb40
MS
236 /* Use the system gmtime() if time_t is small enough */
237 if( SHOULD_USE_SYSTEM_GMTIME(*in_time) ) {
238 time_t safe_time = *in_time;
239 localtime_r(&safe_time, p);
240 CHECK_TM(p)
241 return p;
242 }
243
9af24521 244#ifdef HAS_TM_TM_GMTOFF
a272e669
MS
245 p->tm_gmtoff = 0;
246#endif
247 p->tm_isdst = 0;
248
9af24521 249#ifdef HAS_TM_TM_ZONE
a272e669
MS
250 p->tm_zone = "UTC";
251#endif
252
253 v_tm_sec = time % 60;
254 time /= 60;
255 v_tm_min = time % 60;
256 time /= 60;
257 v_tm_hour = time % 24;
258 time /= 24;
259 v_tm_tday = time;
260 WRAP (v_tm_sec, v_tm_min, 60);
261 WRAP (v_tm_min, v_tm_hour, 60);
262 WRAP (v_tm_hour, v_tm_tday, 24);
263 if ((v_tm_wday = (v_tm_tday + 4) % 7) < 0)
264 v_tm_wday += 7;
265 m = v_tm_tday;
a272e669 266
9af24521
MS
267 if (m >= CHEAT_DAYS) {
268 year = CHEAT_YEARS;
269 m -= CHEAT_DAYS;
270 }
271
272 if (m >= 0) {
a272e669
MS
273 /* Gregorian cycles, this is huge optimization for distant times */
274 while (m >= (Time64_T) days_in_gregorian_cycle) {
275 m -= (Time64_T) days_in_gregorian_cycle;
276 year += years_in_gregorian_cycle;
277 }
278
279 /* Years */
280 leap = IS_LEAP (year);
281 while (m >= (Time64_T) length_of_year[leap]) {
282 m -= (Time64_T) length_of_year[leap];
283 year++;
284 leap = IS_LEAP (year);
285 }
286
287 /* Months */
288 v_tm_mon = 0;
289 while (m >= (Time64_T) days_in_month[leap][v_tm_mon]) {
290 m -= (Time64_T) days_in_month[leap][v_tm_mon];
291 v_tm_mon++;
292 }
293 } else {
9af24521 294 year--;
a272e669
MS
295
296 /* Gregorian cycles */
297 while (m < (Time64_T) -days_in_gregorian_cycle) {
298 m += (Time64_T) days_in_gregorian_cycle;
299 year -= years_in_gregorian_cycle;
300 }
301
302 /* Years */
303 leap = IS_LEAP (year);
304 while (m < (Time64_T) -length_of_year[leap]) {
305 m += (Time64_T) length_of_year[leap];
306 year--;
307 leap = IS_LEAP (year);
308 }
309
310 /* Months */
311 v_tm_mon = 11;
312 while (m < (Time64_T) -days_in_month[leap][v_tm_mon]) {
313 m += (Time64_T) days_in_month[leap][v_tm_mon];
314 v_tm_mon--;
315 }
316 m += (Time64_T) days_in_month[leap][v_tm_mon];
317 }
318
319 p->tm_year = year;
320 if( p->tm_year != year ) {
9af24521 321#ifdef EOVERFLOW
a272e669 322 errno = EOVERFLOW;
9af24521 323#endif
a272e669
MS
324 return NULL;
325 }
326
327 p->tm_mday = (int) m + 1;
328 p->tm_yday = julian_days_by_month[leap][v_tm_mon] + m;
329 p->tm_sec = v_tm_sec, p->tm_min = v_tm_min, p->tm_hour = v_tm_hour,
330 p->tm_mon = v_tm_mon, p->tm_wday = v_tm_wday;
331
a64acb40 332 CHECK_TM(p)
a272e669
MS
333
334 return p;
335}
336
337
338struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm)
339{
340 time_t safe_time;
341 struct tm gm_tm;
9af24521 342 Int64 orig_year;
a272e669
MS
343 int month_diff;
344
a64acb40
MS
345 /* Use the system localtime() if time_t is small enough */
346 if( SHOULD_USE_SYSTEM_LOCALTIME(*time) ) {
347 safe_time = *time;
348 localtime_r(&safe_time, local_tm);
349 CHECK_TM(local_tm)
350 return local_tm;
351 }
352
a272e669
MS
353 gmtime64_r(time, &gm_tm);
354 orig_year = gm_tm.tm_year;
355
356 if (gm_tm.tm_year > (2037 - 1900))
357 gm_tm.tm_year = _safe_year(gm_tm.tm_year + 1900) - 1900;
358
9af24521 359 safe_time = TIMEGM(&gm_tm);
a272e669
MS
360 localtime_r(&safe_time, local_tm);
361
362 local_tm->tm_year = orig_year;
363 month_diff = local_tm->tm_mon - gm_tm.tm_mon;
364
365 /* When localtime is Dec 31st previous year and
366 gmtime is Jan 1st next year.
367 */
368 if( month_diff == 11 ) {
369 local_tm->tm_year--;
370 }
371
372 /* When localtime is Jan 1st, next year and
373 gmtime is Dec 31st, previous year.
374 */
375 if( month_diff == -11 ) {
376 local_tm->tm_year++;
377 }
378
379 /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st
380 in a non-leap xx00. There is one point in the cycle
381 we can't account for which the safe xx00 year is a leap
382 year. So we need to correct for Dec 31st comming out as
383 the 366th day of the year.
384 */
385 if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 )
386 local_tm->tm_yday--;
387
a64acb40 388 CHECK_TM(local_tm)
a272e669
MS
389
390 return local_tm;
391}