This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Rename localtime64.[ch] to time64.[ch] to mirror change in y2038, and the file isn...
[perl5.git] / time64.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
7643e68f 42#include "time64.h"
af9b2bf5 43
a272e669
MS
44static const int days_in_month[2][12] = {
45 {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
46 {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
47};
48
49static const int julian_days_by_month[2][12] = {
50 {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
51 {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335},
52};
53
54static const int length_of_year[2] = { 365, 366 };
55
56/* Number of days in a 400 year Gregorian cycle */
57static const int years_in_gregorian_cycle = 400;
58static const int days_in_gregorian_cycle = (365 * 400) + 100 - 4 + 1;
59
60/* 28 year calendar cycle between 2010 and 2037 */
61static const int safe_years[28] = {
62 2016, 2017, 2018, 2019,
63 2020, 2021, 2022, 2023,
64 2024, 2025, 2026, 2027,
65 2028, 2029, 2030, 2031,
66 2032, 2033, 2034, 2035,
67 2036, 2037, 2010, 2011,
68 2012, 2013, 2014, 2015
69};
70
ea722b76
MS
71#define SOLAR_CYCLE_LENGTH 28
72static const int dow_year_start[SOLAR_CYCLE_LENGTH] = {
003c3b95
MS
73 5, 0, 1, 2, /* 0 2016 - 2019 */
74 3, 5, 6, 0, /* 4 */
75 1, 3, 4, 5, /* 8 */
76 6, 1, 2, 3, /* 12 */
77 4, 6, 0, 1, /* 16 */
78 2, 4, 5, 6, /* 20 2036, 2037, 2010, 2011 */
79 0, 2, 3, 4 /* 24 2012, 2013, 2014, 2015 */
a272e669
MS
80};
81
9af24521
MS
82/* Let's assume people are going to be looking for dates in the future.
83 Let's provide some cheats so you can skip ahead.
84 This has a 4x speed boost when near 2008.
85*/
86/* Number of days since epoch on Jan 1st, 2008 GMT */
87#define CHEAT_DAYS (1199145600 / 24 / 60 / 60)
88#define CHEAT_YEARS 108
a272e669
MS
89
90#define IS_LEAP(n) ((!(((n) + 1900) % 400) || (!(((n) + 1900) % 4) && (((n) + 1900) % 100))) != 0)
91#define WRAP(a,b,m) ((a) = ((a) < 0 ) ? ((b)--, (a) + (m)) : (a))
92
7bda3dfc
MS
93#define SHOULD_USE_SYSTEM_LOCALTIME(a) ( \
94 USE_SYSTEM_LOCALTIME && \
95 (a) <= SYSTEM_LOCALTIME_MAX && \
96 (a) >= SYSTEM_LOCALTIME_MIN \
97)
98#define SHOULD_USE_SYSTEM_GMTIME(a) ( \
99 USE_SYSTEM_GMTIME && \
100 (a) <= SYSTEM_GMTIME_MAX && \
101 (a) >= SYSTEM_GMTIME_MIN \
102)
a64acb40
MS
103
104
9af24521 105int _is_exception_century(Int64 year)
a272e669
MS
106{
107 int is_exception = ((year % 100 == 0) && !(year % 400 == 0));
108 /* printf("is_exception_century: %s\n", is_exception ? "yes" : "no"); */
109
110 return(is_exception);
111}
112
9af24521
MS
113
114/* timegm() is a GNU extension, so emulate it here if we need it */
115#ifdef HAS_TIMEGM
116# define TIMEGM(n) timegm(n);
117#else
ea722b76 118# define TIMEGM(n) ((time_t)timegm64(n));
a272e669
MS
119#endif
120
ea722b76
MS
121Time64_T timegm64(struct tm *date) {
122 int days = 0;
123 Int64 seconds = 0;
124 Int64 year;
a272e669 125
9af24521
MS
126 if( date->tm_year > 70 ) {
127 year = 70;
128 while( year < date->tm_year ) {
129 days += length_of_year[IS_LEAP(year)];
130 year++;
a272e669
MS
131 }
132 }
9af24521
MS
133 else if ( date->tm_year < 70 ) {
134 year = 69;
135 do {
136 days -= length_of_year[IS_LEAP(year)];
137 year--;
138 } while( year >= date->tm_year );
139 }
140
141 days += julian_days_by_month[IS_LEAP(date->tm_year)][date->tm_mon];
142 days += date->tm_mday - 1;
143
ea722b76
MS
144 /* Avoid overflowing the days integer */
145 seconds = days;
146 seconds = seconds * 60 * 60 * 24;
147
9af24521
MS
148 seconds += date->tm_hour * 60 * 60;
149 seconds += date->tm_min * 60;
150 seconds += date->tm_sec;
151
ea722b76 152 return((Time64_T)seconds);
9af24521
MS
153}
154
155
af9b2bf5 156int _check_tm(struct tm *tm)
9af24521 157{
9af24521 158 /* Don't forget leap seconds */
af9b2bf5 159 assert(tm->tm_sec >= 0);
9af24521
MS
160 assert(tm->tm_sec <= 61);
161
af9b2bf5 162 assert(tm->tm_min >= 0);
9af24521
MS
163 assert(tm->tm_min <= 59);
164
165 assert(tm->tm_hour >= 0);
166 assert(tm->tm_hour <= 23);
167
168 assert(tm->tm_mday >= 1);
af9b2bf5 169 assert(tm->tm_mday <= days_in_month[IS_LEAP(tm->tm_year)][tm->tm_mon]);
9af24521
MS
170
171 assert(tm->tm_mon >= 0);
172 assert(tm->tm_mon <= 11);
173
174 assert(tm->tm_wday >= 0);
175 assert(tm->tm_wday <= 6);
176
177 assert(tm->tm_yday >= 0);
af9b2bf5 178 assert(tm->tm_yday <= length_of_year[IS_LEAP(tm->tm_year)]);
9af24521
MS
179
180#ifdef HAS_TM_TM_GMTOFF
181 assert(tm->tm_gmtoff >= -24 * 60 * 60);
182 assert(tm->tm_gmtoff <= 24 * 60 * 60);
183#endif
af9b2bf5
MS
184
185 return 1;
a272e669 186}
a64acb40 187
a272e669
MS
188
189/* The exceptional centuries without leap years cause the cycle to
190 shift by 16
191*/
750c447b 192Year _cycle_offset(Year year)
a272e669 193{
750c447b
MS
194 const Year start_year = 2000;
195 Year year_diff = year - start_year;
196 Year exceptions;
003c3b95
MS
197
198 if( year > start_year )
199 year_diff--;
200
750c447b
MS
201 exceptions = year_diff / 100;
202 exceptions -= year_diff / 400;
a272e669 203
003c3b95
MS
204 /*
205 fprintf(stderr, "# year: %lld, exceptions: %lld, year_diff: %lld\n",
206 year, exceptions, year_diff);
207 */
a272e669
MS
208
209 return exceptions * 16;
210}
211
212/* For a given year after 2038, pick the latest possible matching
213 year in the 28 year calendar cycle.
ea722b76
MS
214
215 A matching year...
216 1) Starts on the same day of the week.
217 2) Has the same leap year status.
218
219 This is so the calendars match up.
220
221 Also the previous year must match. When doing Jan 1st you might
222 wind up on Dec 31st the previous year when doing a -UTC time zone.
003c3b95
MS
223
224 Finally, the next year must have the same start day of week. This
225 is for Dec 31st with a +UTC time zone.
226 It doesn't need the same leap year status since we only care about
227 January 1st.
a272e669 228*/
750c447b 229int _safe_year(Year year)
a272e669
MS
230{
231 int safe_year;
750c447b 232 Year year_cycle = year + _cycle_offset(year);
a272e669
MS
233
234 /* Change non-leap xx00 years to an equivalent */
235 if( _is_exception_century(year) )
236 year_cycle += 11;
237
003c3b95
MS
238 /* Also xx01 years, since the previous year will be wrong */
239 if( _is_exception_century(year - 1) )
240 year_cycle += 17;
241
a272e669 242 year_cycle %= SOLAR_CYCLE_LENGTH;
ea722b76
MS
243 if( year_cycle < 0 )
244 year_cycle = SOLAR_CYCLE_LENGTH + year_cycle;
a272e669 245
003c3b95
MS
246 assert( year_cycle >= 0 );
247 assert( year_cycle < SOLAR_CYCLE_LENGTH );
a272e669
MS
248 safe_year = safe_years[year_cycle];
249
250 assert(safe_year <= 2037 && safe_year >= 2010);
251
252 /*
253 printf("year: %d, year_cycle: %d, safe_year: %d\n",
254 year, year_cycle, safe_year);
255 */
256
257 return safe_year;
258}
259
750c447b 260
948ea7a9
MS
261/* Simulate localtime_r() to the best of our ability */
262struct tm * fake_localtime_r(const time_t *clock, struct tm *result) {
263 const struct tm *static_result = localtime(clock);
264
265 assert(result != NULL);
266
267 if( static_result == NULL ) {
268 memset(result, 0, sizeof(*result));
269 return NULL;
270 }
271 else {
272 memcpy(result, static_result, sizeof(*result));
273 return result;
274 }
275}
276
277
278/* Simulate gmtime_r() to the best of our ability */
279struct tm * fake_gmtime_r(const time_t *clock, struct tm *result) {
280 const struct tm *static_result = gmtime(clock);
281
282 assert(result != NULL);
283
284 if( static_result == NULL ) {
285 memset(result, 0, sizeof(*result));
286 return NULL;
287 }
288 else {
289 memcpy(result, static_result, sizeof(*result));
290 return result;
291 }
292}
293
294
a272e669
MS
295struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p)
296{
297 int v_tm_sec, v_tm_min, v_tm_hour, v_tm_mon, v_tm_wday;
9af24521 298 Int64 v_tm_tday;
a272e669 299 int leap;
9af24521 300 Int64 m;
a272e669 301 Time64_T time = *in_time;
750c447b 302 Year year = 70;
a272e669 303
948ea7a9
MS
304 assert(p != NULL);
305
a64acb40
MS
306 /* Use the system gmtime() if time_t is small enough */
307 if( SHOULD_USE_SYSTEM_GMTIME(*in_time) ) {
308 time_t safe_time = *in_time;
948ea7a9 309 GMTIME_R(&safe_time, p);
af9b2bf5 310 assert(_check_tm(p));
a64acb40
MS
311 return p;
312 }
313
9af24521 314#ifdef HAS_TM_TM_GMTOFF
a272e669
MS
315 p->tm_gmtoff = 0;
316#endif
317 p->tm_isdst = 0;
318
9af24521 319#ifdef HAS_TM_TM_ZONE
a272e669
MS
320 p->tm_zone = "UTC";
321#endif
322
750c447b 323 v_tm_sec = (int)(time % 60);
a272e669 324 time /= 60;
750c447b 325 v_tm_min = (int)(time % 60);
a272e669 326 time /= 60;
750c447b 327 v_tm_hour = (int)(time % 24);
a272e669
MS
328 time /= 24;
329 v_tm_tday = time;
750c447b 330
a272e669
MS
331 WRAP (v_tm_sec, v_tm_min, 60);
332 WRAP (v_tm_min, v_tm_hour, 60);
333 WRAP (v_tm_hour, v_tm_tday, 24);
750c447b
MS
334
335 v_tm_wday = (int)((v_tm_tday + 4) % 7);
336 if (v_tm_wday < 0)
a272e669
MS
337 v_tm_wday += 7;
338 m = v_tm_tday;
a272e669 339
9af24521
MS
340 if (m >= CHEAT_DAYS) {
341 year = CHEAT_YEARS;
342 m -= CHEAT_DAYS;
343 }
344
345 if (m >= 0) {
a272e669
MS
346 /* Gregorian cycles, this is huge optimization for distant times */
347 while (m >= (Time64_T) days_in_gregorian_cycle) {
348 m -= (Time64_T) days_in_gregorian_cycle;
349 year += years_in_gregorian_cycle;
350 }
351
352 /* Years */
353 leap = IS_LEAP (year);
354 while (m >= (Time64_T) length_of_year[leap]) {
355 m -= (Time64_T) length_of_year[leap];
356 year++;
357 leap = IS_LEAP (year);
358 }
359
360 /* Months */
361 v_tm_mon = 0;
362 while (m >= (Time64_T) days_in_month[leap][v_tm_mon]) {
363 m -= (Time64_T) days_in_month[leap][v_tm_mon];
364 v_tm_mon++;
365 }
366 } else {
9af24521 367 year--;
a272e669
MS
368
369 /* Gregorian cycles */
370 while (m < (Time64_T) -days_in_gregorian_cycle) {
371 m += (Time64_T) days_in_gregorian_cycle;
372 year -= years_in_gregorian_cycle;
373 }
374
375 /* Years */
376 leap = IS_LEAP (year);
377 while (m < (Time64_T) -length_of_year[leap]) {
378 m += (Time64_T) length_of_year[leap];
379 year--;
380 leap = IS_LEAP (year);
381 }
382
383 /* Months */
384 v_tm_mon = 11;
385 while (m < (Time64_T) -days_in_month[leap][v_tm_mon]) {
386 m += (Time64_T) days_in_month[leap][v_tm_mon];
387 v_tm_mon--;
388 }
389 m += (Time64_T) days_in_month[leap][v_tm_mon];
390 }
391
392 p->tm_year = year;
393 if( p->tm_year != year ) {
9af24521 394#ifdef EOVERFLOW
a272e669 395 errno = EOVERFLOW;
9af24521 396#endif
a272e669
MS
397 return NULL;
398 }
399
400 p->tm_mday = (int) m + 1;
750c447b 401 p->tm_yday = (int) julian_days_by_month[leap][v_tm_mon] + m;
a272e669
MS
402 p->tm_sec = v_tm_sec, p->tm_min = v_tm_min, p->tm_hour = v_tm_hour,
403 p->tm_mon = v_tm_mon, p->tm_wday = v_tm_wday;
404
af9b2bf5 405 assert(_check_tm(p));
a272e669
MS
406
407 return p;
408}
409
410
411struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm)
412{
413 time_t safe_time;
414 struct tm gm_tm;
750c447b 415 Year orig_year;
a272e669
MS
416 int month_diff;
417
948ea7a9
MS
418 assert(local_tm != NULL);
419
a64acb40
MS
420 /* Use the system localtime() if time_t is small enough */
421 if( SHOULD_USE_SYSTEM_LOCALTIME(*time) ) {
422 safe_time = *time;
948ea7a9 423 LOCALTIME_R(&safe_time, local_tm);
af9b2bf5 424 assert(_check_tm(local_tm));
a64acb40
MS
425 return local_tm;
426 }
427
af832814
MS
428 if( gmtime64_r(time, &gm_tm) == NULL )
429 return NULL;
430
a272e669
MS
431 orig_year = gm_tm.tm_year;
432
c07fe26c
MS
433 if (gm_tm.tm_year > (2037 - 1900) ||
434 gm_tm.tm_year < (1902 - 1900)
435 )
436 {
a272e669 437 gm_tm.tm_year = _safe_year(gm_tm.tm_year + 1900) - 1900;
c07fe26c 438 }
a272e669 439
9af24521 440 safe_time = TIMEGM(&gm_tm);
948ea7a9 441 if( LOCALTIME_R(&safe_time, local_tm) == NULL )
af832814 442 return NULL;
a272e669
MS
443
444 local_tm->tm_year = orig_year;
af832814
MS
445 if( local_tm->tm_year != orig_year ) {
446#ifdef EOVERFLOW
447 errno = EOVERFLOW;
448#endif
449 return NULL;
450 }
451
452
a272e669
MS
453 month_diff = local_tm->tm_mon - gm_tm.tm_mon;
454
455 /* When localtime is Dec 31st previous year and
456 gmtime is Jan 1st next year.
457 */
458 if( month_diff == 11 ) {
459 local_tm->tm_year--;
460 }
461
462 /* When localtime is Jan 1st, next year and
463 gmtime is Dec 31st, previous year.
464 */
465 if( month_diff == -11 ) {
466 local_tm->tm_year++;
467 }
468
469 /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st
470 in a non-leap xx00. There is one point in the cycle
471 we can't account for which the safe xx00 year is a leap
472 year. So we need to correct for Dec 31st comming out as
473 the 366th day of the year.
474 */
475 if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 )
476 local_tm->tm_yday--;
477
af9b2bf5 478 assert(_check_tm(local_tm));
a272e669
MS
479
480 return local_tm;
481}