This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Update from y2038
[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 */
806a119a 57static const Year years_in_gregorian_cycle = 400;
a272e669
MS
58static const int days_in_gregorian_cycle = (365 * 400) + 100 - 4 + 1;
59
60/* 28 year calendar cycle between 2010 and 2037 */
806a119a
MS
61#define SOLAR_CYCLE_LENGTH 28
62static const int safe_years[SOLAR_CYCLE_LENGTH] = {
a272e669
MS
63 2016, 2017, 2018, 2019,
64 2020, 2021, 2022, 2023,
65 2024, 2025, 2026, 2027,
66 2028, 2029, 2030, 2031,
67 2032, 2033, 2034, 2035,
68 2036, 2037, 2010, 2011,
69 2012, 2013, 2014, 2015
70};
71
ea722b76 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
b86b480f
MS
93#ifdef USE_SYSTEM_LOCALTIME
94# define SHOULD_USE_SYSTEM_LOCALTIME(a) ( \
7bda3dfc
MS
95 (a) <= SYSTEM_LOCALTIME_MAX && \
96 (a) >= SYSTEM_LOCALTIME_MIN \
97)
b86b480f
MS
98#else
99# define SHOULD_USE_SYSTEM_LOCALTIME(a) (0)
100#endif
101
102#ifdef USE_SYSTEM_GMTIME
103# define SHOULD_USE_SYSTEM_GMTIME(a) ( \
7bda3dfc
MS
104 (a) <= SYSTEM_GMTIME_MAX && \
105 (a) >= SYSTEM_GMTIME_MIN \
106)
b86b480f
MS
107#else
108# define SHOULD_USE_SYSTEM_GMTIME(a) (0)
109#endif
a64acb40
MS
110
111
b86b480f 112static int is_exception_century(Year year)
a272e669
MS
113{
114 int is_exception = ((year % 100 == 0) && !(year % 400 == 0));
115 /* printf("is_exception_century: %s\n", is_exception ? "yes" : "no"); */
116
117 return(is_exception);
118}
119
9af24521 120
806a119a 121Time64_T timegm64(struct TM *date) {
b86b480f
MS
122 int days = 0;
123 Time64_T seconds = 0;
124 Year 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
b86b480f 152 return(seconds);
9af24521
MS
153}
154
155
806a119a 156static int 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*/
806a119a 192static Year 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*/
806a119a 229static int safe_year(Year year)
a272e669
MS
230{
231 int safe_year;
806a119a 232 Year year_cycle = year + cycle_offset(year);
a272e669
MS
233
234 /* Change non-leap xx00 years to an equivalent */
806a119a 235 if( is_exception_century(year) )
a272e669
MS
236 year_cycle += 11;
237
003c3b95 238 /* Also xx01 years, since the previous year will be wrong */
806a119a 239 if( is_exception_century(year - 1) )
003c3b95
MS
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
806a119a
MS
261void copy_tm_to_TM(const struct tm *src, struct TM *dest) {
262 if( src == NULL ) {
263 memset(dest, 0, sizeof(*dest));
264 }
265 else {
266# ifdef USE_TM64
267 dest->tm_sec = src->tm_sec;
268 dest->tm_min = src->tm_min;
269 dest->tm_hour = src->tm_hour;
270 dest->tm_mday = src->tm_mday;
271 dest->tm_mon = src->tm_mon;
272 dest->tm_year = (Year)src->tm_year;
273 dest->tm_wday = src->tm_wday;
274 dest->tm_yday = src->tm_yday;
275 dest->tm_isdst = src->tm_isdst;
276
277# ifdef HAS_TM_TM_GMTOFF
278 dest->tm_gmtoff = src->tm_gmtoff;
279# endif
280
281# ifdef HAS_TM_TM_ZONE
282 dest->tm_zone = src->tm_zone;
283# endif
284
285# else
286 /* They're the same type */
287 memcpy(dest, src, sizeof(*dest));
288# endif
289 }
290}
291
292
293void copy_TM_to_tm(const struct TM *src, struct tm *dest) {
294 if( src == NULL ) {
295 memset(dest, 0, sizeof(*dest));
296 }
297 else {
298# ifdef USE_TM64
299 dest->tm_sec = src->tm_sec;
300 dest->tm_min = src->tm_min;
301 dest->tm_hour = src->tm_hour;
302 dest->tm_mday = src->tm_mday;
303 dest->tm_mon = src->tm_mon;
304 dest->tm_year = (int)src->tm_year;
305 dest->tm_wday = src->tm_wday;
306 dest->tm_yday = src->tm_yday;
307 dest->tm_isdst = src->tm_isdst;
308
309# ifdef HAS_TM_TM_GMTOFF
310 dest->tm_gmtoff = src->tm_gmtoff;
311# endif
312
313# ifdef HAS_TM_TM_ZONE
314 dest->tm_zone = src->tm_zone;
315# endif
316
317# else
318 /* They're the same type */
319 memcpy(dest, src, sizeof(*dest));
320# endif
321 }
322}
323
324
948ea7a9
MS
325/* Simulate localtime_r() to the best of our ability */
326struct tm * fake_localtime_r(const time_t *clock, struct tm *result) {
327 const struct tm *static_result = localtime(clock);
328
329 assert(result != NULL);
330
331 if( static_result == NULL ) {
332 memset(result, 0, sizeof(*result));
333 return NULL;
334 }
335 else {
336 memcpy(result, static_result, sizeof(*result));
337 return result;
338 }
339}
340
341
342/* Simulate gmtime_r() to the best of our ability */
343struct tm * fake_gmtime_r(const time_t *clock, struct tm *result) {
344 const struct tm *static_result = gmtime(clock);
345
346 assert(result != NULL);
347
348 if( static_result == NULL ) {
349 memset(result, 0, sizeof(*result));
350 return NULL;
351 }
352 else {
353 memcpy(result, static_result, sizeof(*result));
354 return result;
355 }
356}
357
358
806a119a 359struct TM *gmtime64_r (const Time64_T *in_time, struct TM *p)
a272e669
MS
360{
361 int v_tm_sec, v_tm_min, v_tm_hour, v_tm_mon, v_tm_wday;
b86b480f 362 Time64_T v_tm_tday;
a272e669 363 int leap;
b86b480f 364 Time64_T m;
a272e669 365 Time64_T time = *in_time;
750c447b 366 Year year = 70;
806a119a 367 int cycles = 0;
a272e669 368
948ea7a9
MS
369 assert(p != NULL);
370
a64acb40
MS
371 /* Use the system gmtime() if time_t is small enough */
372 if( SHOULD_USE_SYSTEM_GMTIME(*in_time) ) {
373 time_t safe_time = *in_time;
806a119a
MS
374 struct tm safe_date;
375 GMTIME_R(&safe_time, &safe_date);
376
377 copy_tm_to_TM(&safe_date, p);
378 assert(check_tm(p));
379
a64acb40
MS
380 return p;
381 }
382
9af24521 383#ifdef HAS_TM_TM_GMTOFF
a272e669
MS
384 p->tm_gmtoff = 0;
385#endif
386 p->tm_isdst = 0;
387
9af24521 388#ifdef HAS_TM_TM_ZONE
a272e669
MS
389 p->tm_zone = "UTC";
390#endif
391
750c447b 392 v_tm_sec = (int)(time % 60);
a272e669 393 time /= 60;
750c447b 394 v_tm_min = (int)(time % 60);
a272e669 395 time /= 60;
750c447b 396 v_tm_hour = (int)(time % 24);
a272e669
MS
397 time /= 24;
398 v_tm_tday = time;
750c447b 399
a272e669
MS
400 WRAP (v_tm_sec, v_tm_min, 60);
401 WRAP (v_tm_min, v_tm_hour, 60);
402 WRAP (v_tm_hour, v_tm_tday, 24);
750c447b
MS
403
404 v_tm_wday = (int)((v_tm_tday + 4) % 7);
405 if (v_tm_wday < 0)
a272e669
MS
406 v_tm_wday += 7;
407 m = v_tm_tday;
a272e669 408
9af24521
MS
409 if (m >= CHEAT_DAYS) {
410 year = CHEAT_YEARS;
411 m -= CHEAT_DAYS;
412 }
413
414 if (m >= 0) {
a272e669 415 /* Gregorian cycles, this is huge optimization for distant times */
b86b480f 416 cycles = m / (Time64_T) days_in_gregorian_cycle;
806a119a
MS
417 if( cycles ) {
418 m -= (cycles * (Time64_T) days_in_gregorian_cycle);
419 year += (cycles * years_in_gregorian_cycle);
a272e669
MS
420 }
421
422 /* Years */
423 leap = IS_LEAP (year);
424 while (m >= (Time64_T) length_of_year[leap]) {
425 m -= (Time64_T) length_of_year[leap];
426 year++;
427 leap = IS_LEAP (year);
428 }
429
430 /* Months */
431 v_tm_mon = 0;
432 while (m >= (Time64_T) days_in_month[leap][v_tm_mon]) {
433 m -= (Time64_T) days_in_month[leap][v_tm_mon];
434 v_tm_mon++;
435 }
436 } else {
9af24521 437 year--;
a272e669
MS
438
439 /* Gregorian cycles */
b86b480f 440 cycles = (m / (Time64_T) days_in_gregorian_cycle) + 1;
806a119a
MS
441 if( cycles ) {
442 m -= (cycles * (Time64_T) days_in_gregorian_cycle);
443 year += (cycles * years_in_gregorian_cycle);
a272e669
MS
444 }
445
446 /* Years */
447 leap = IS_LEAP (year);
448 while (m < (Time64_T) -length_of_year[leap]) {
449 m += (Time64_T) length_of_year[leap];
450 year--;
451 leap = IS_LEAP (year);
452 }
453
454 /* Months */
455 v_tm_mon = 11;
456 while (m < (Time64_T) -days_in_month[leap][v_tm_mon]) {
457 m += (Time64_T) days_in_month[leap][v_tm_mon];
458 v_tm_mon--;
459 }
460 m += (Time64_T) days_in_month[leap][v_tm_mon];
461 }
462
463 p->tm_year = year;
464 if( p->tm_year != year ) {
9af24521 465#ifdef EOVERFLOW
a272e669 466 errno = EOVERFLOW;
9af24521 467#endif
a272e669
MS
468 return NULL;
469 }
470
b86b480f 471 /* At this point m is less than a year so casting to an int is safe */
a272e669 472 p->tm_mday = (int) m + 1;
b86b480f
MS
473 p->tm_yday = julian_days_by_month[leap][v_tm_mon] + (int)m;
474 p->tm_sec = v_tm_sec;
475 p->tm_min = v_tm_min;
476 p->tm_hour = v_tm_hour;
477 p->tm_mon = v_tm_mon;
478 p->tm_wday = v_tm_wday;
a272e669 479
806a119a 480 assert(check_tm(p));
a272e669
MS
481
482 return p;
483}
484
485
806a119a 486struct TM *localtime64_r (const Time64_T *time, struct TM *local_tm)
a272e669
MS
487{
488 time_t safe_time;
806a119a
MS
489 struct tm safe_date;
490 struct TM gm_tm;
750c447b 491 Year orig_year;
a272e669
MS
492 int month_diff;
493
948ea7a9
MS
494 assert(local_tm != NULL);
495
a64acb40
MS
496 /* Use the system localtime() if time_t is small enough */
497 if( SHOULD_USE_SYSTEM_LOCALTIME(*time) ) {
498 safe_time = *time;
806a119a
MS
499
500 LOCALTIME_R(&safe_time, &safe_date);
501
502 copy_tm_to_TM(&safe_date, local_tm);
503 assert(check_tm(local_tm));
504
a64acb40
MS
505 return local_tm;
506 }
507
af832814
MS
508 if( gmtime64_r(time, &gm_tm) == NULL )
509 return NULL;
510
a272e669
MS
511 orig_year = gm_tm.tm_year;
512
c07fe26c
MS
513 if (gm_tm.tm_year > (2037 - 1900) ||
514 gm_tm.tm_year < (1902 - 1900)
515 )
516 {
b86b480f 517 gm_tm.tm_year = safe_year((Year)(gm_tm.tm_year + 1900)) - 1900;
c07fe26c 518 }
a272e669 519
806a119a
MS
520 safe_time = timegm64(&gm_tm);
521 if( LOCALTIME_R(&safe_time, &safe_date) == NULL )
af832814 522 return NULL;
a272e669 523
806a119a
MS
524 copy_tm_to_TM(&safe_date, local_tm);
525
a272e669 526 local_tm->tm_year = orig_year;
af832814
MS
527 if( local_tm->tm_year != orig_year ) {
528#ifdef EOVERFLOW
529 errno = EOVERFLOW;
530#endif
531 return NULL;
532 }
533
534
a272e669
MS
535 month_diff = local_tm->tm_mon - gm_tm.tm_mon;
536
537 /* When localtime is Dec 31st previous year and
538 gmtime is Jan 1st next year.
539 */
540 if( month_diff == 11 ) {
541 local_tm->tm_year--;
542 }
543
544 /* When localtime is Jan 1st, next year and
545 gmtime is Dec 31st, previous year.
546 */
547 if( month_diff == -11 ) {
548 local_tm->tm_year++;
549 }
550
551 /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st
552 in a non-leap xx00. There is one point in the cycle
553 we can't account for which the safe xx00 year is a leap
554 year. So we need to correct for Dec 31st comming out as
555 the 366th day of the year.
556 */
557 if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 )
558 local_tm->tm_yday--;
559
806a119a 560 assert(check_tm(local_tm));
a272e669
MS
561
562 return local_tm;
563}