Update from y2038
[p5sagit/p5-mst-13.2.git] / localtime64.c
CommitLineData
a272e669 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
af9b2bf5 42#include "localtime64.h"
43
a272e669 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 71#define SOLAR_CYCLE_LENGTH 28
72static const int dow_year_start[SOLAR_CYCLE_LENGTH] = {
003c3b95 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 80};
81
9af24521 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 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 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 103
104
9af24521 105int _is_exception_century(Int64 year)
a272e669 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 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 119#endif
120
ea722b76 121Time64_T timegm64(struct tm *date) {
122 int days = 0;
123 Int64 seconds = 0;
124 Int64 year;
a272e669 125
9af24521 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 131 }
132 }
9af24521 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 144 /* Avoid overflowing the days integer */
145 seconds = days;
146 seconds = seconds * 60 * 60 * 24;
147
9af24521 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 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 160 assert(tm->tm_sec <= 61);
161
af9b2bf5 162 assert(tm->tm_min >= 0);
9af24521 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 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 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 184
185 return 1;
a272e669 186}
a64acb40 187
a272e669 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 194 const Year start_year = 2000;
195 Year year_diff = year - start_year;
196 Year exceptions;
003c3b95 197
198 if( year > start_year )
199 year_diff--;
200
750c447b 201 exceptions = year_diff / 100;
202 exceptions -= year_diff / 400;
a272e669 203
003c3b95 204 /*
205 fprintf(stderr, "# year: %lld, exceptions: %lld, year_diff: %lld\n",
206 year, exceptions, year_diff);
207 */
a272e669 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 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 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 230{
231 int safe_year;
750c447b 232 Year year_cycle = year + _cycle_offset(year);
a272e669 233
234 /* Change non-leap xx00 years to an equivalent */
235 if( _is_exception_century(year) )
236 year_cycle += 11;
237
003c3b95 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 243 if( year_cycle < 0 )
244 year_cycle = SOLAR_CYCLE_LENGTH + year_cycle;
a272e669 245
003c3b95 246 assert( year_cycle >= 0 );
247 assert( year_cycle < SOLAR_CYCLE_LENGTH );
a272e669 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
a272e669 261struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p)
262{
263 int v_tm_sec, v_tm_min, v_tm_hour, v_tm_mon, v_tm_wday;
9af24521 264 Int64 v_tm_tday;
a272e669 265 int leap;
9af24521 266 Int64 m;
a272e669 267 Time64_T time = *in_time;
750c447b 268 Year year = 70;
a272e669 269
a64acb40 270 /* Use the system gmtime() if time_t is small enough */
271 if( SHOULD_USE_SYSTEM_GMTIME(*in_time) ) {
272 time_t safe_time = *in_time;
750c447b 273 gmtime_r(&safe_time, p);
af9b2bf5 274 assert(_check_tm(p));
a64acb40 275 return p;
276 }
277
9af24521 278#ifdef HAS_TM_TM_GMTOFF
a272e669 279 p->tm_gmtoff = 0;
280#endif
281 p->tm_isdst = 0;
282
9af24521 283#ifdef HAS_TM_TM_ZONE
a272e669 284 p->tm_zone = "UTC";
285#endif
286
750c447b 287 v_tm_sec = (int)(time % 60);
a272e669 288 time /= 60;
750c447b 289 v_tm_min = (int)(time % 60);
a272e669 290 time /= 60;
750c447b 291 v_tm_hour = (int)(time % 24);
a272e669 292 time /= 24;
293 v_tm_tday = time;
750c447b 294
a272e669 295 WRAP (v_tm_sec, v_tm_min, 60);
296 WRAP (v_tm_min, v_tm_hour, 60);
297 WRAP (v_tm_hour, v_tm_tday, 24);
750c447b 298
299 v_tm_wday = (int)((v_tm_tday + 4) % 7);
300 if (v_tm_wday < 0)
a272e669 301 v_tm_wday += 7;
302 m = v_tm_tday;
a272e669 303
9af24521 304 if (m >= CHEAT_DAYS) {
305 year = CHEAT_YEARS;
306 m -= CHEAT_DAYS;
307 }
308
309 if (m >= 0) {
a272e669 310 /* Gregorian cycles, this is huge optimization for distant times */
311 while (m >= (Time64_T) days_in_gregorian_cycle) {
312 m -= (Time64_T) days_in_gregorian_cycle;
313 year += years_in_gregorian_cycle;
314 }
315
316 /* Years */
317 leap = IS_LEAP (year);
318 while (m >= (Time64_T) length_of_year[leap]) {
319 m -= (Time64_T) length_of_year[leap];
320 year++;
321 leap = IS_LEAP (year);
322 }
323
324 /* Months */
325 v_tm_mon = 0;
326 while (m >= (Time64_T) days_in_month[leap][v_tm_mon]) {
327 m -= (Time64_T) days_in_month[leap][v_tm_mon];
328 v_tm_mon++;
329 }
330 } else {
9af24521 331 year--;
a272e669 332
333 /* Gregorian cycles */
334 while (m < (Time64_T) -days_in_gregorian_cycle) {
335 m += (Time64_T) days_in_gregorian_cycle;
336 year -= years_in_gregorian_cycle;
337 }
338
339 /* Years */
340 leap = IS_LEAP (year);
341 while (m < (Time64_T) -length_of_year[leap]) {
342 m += (Time64_T) length_of_year[leap];
343 year--;
344 leap = IS_LEAP (year);
345 }
346
347 /* Months */
348 v_tm_mon = 11;
349 while (m < (Time64_T) -days_in_month[leap][v_tm_mon]) {
350 m += (Time64_T) days_in_month[leap][v_tm_mon];
351 v_tm_mon--;
352 }
353 m += (Time64_T) days_in_month[leap][v_tm_mon];
354 }
355
356 p->tm_year = year;
357 if( p->tm_year != year ) {
9af24521 358#ifdef EOVERFLOW
a272e669 359 errno = EOVERFLOW;
9af24521 360#endif
a272e669 361 return NULL;
362 }
363
364 p->tm_mday = (int) m + 1;
750c447b 365 p->tm_yday = (int) julian_days_by_month[leap][v_tm_mon] + m;
a272e669 366 p->tm_sec = v_tm_sec, p->tm_min = v_tm_min, p->tm_hour = v_tm_hour,
367 p->tm_mon = v_tm_mon, p->tm_wday = v_tm_wday;
368
af9b2bf5 369 assert(_check_tm(p));
a272e669 370
371 return p;
372}
373
374
375struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm)
376{
377 time_t safe_time;
378 struct tm gm_tm;
750c447b 379 Year orig_year;
a272e669 380 int month_diff;
381
a64acb40 382 /* Use the system localtime() if time_t is small enough */
383 if( SHOULD_USE_SYSTEM_LOCALTIME(*time) ) {
384 safe_time = *time;
385 localtime_r(&safe_time, local_tm);
af9b2bf5 386 assert(_check_tm(local_tm));
a64acb40 387 return local_tm;
388 }
389
af832814 390 if( gmtime64_r(time, &gm_tm) == NULL )
391 return NULL;
392
a272e669 393 orig_year = gm_tm.tm_year;
394
c07fe26c 395 if (gm_tm.tm_year > (2037 - 1900) ||
396 gm_tm.tm_year < (1902 - 1900)
397 )
398 {
a272e669 399 gm_tm.tm_year = _safe_year(gm_tm.tm_year + 1900) - 1900;
c07fe26c 400 }
a272e669 401
9af24521 402 safe_time = TIMEGM(&gm_tm);
af832814 403 if( localtime_r(&safe_time, local_tm) == NULL )
404 return NULL;
a272e669 405
406 local_tm->tm_year = orig_year;
af832814 407 if( local_tm->tm_year != orig_year ) {
408#ifdef EOVERFLOW
409 errno = EOVERFLOW;
410#endif
411 return NULL;
412 }
413
414
a272e669 415 month_diff = local_tm->tm_mon - gm_tm.tm_mon;
416
417 /* When localtime is Dec 31st previous year and
418 gmtime is Jan 1st next year.
419 */
420 if( month_diff == 11 ) {
421 local_tm->tm_year--;
422 }
423
424 /* When localtime is Jan 1st, next year and
425 gmtime is Dec 31st, previous year.
426 */
427 if( month_diff == -11 ) {
428 local_tm->tm_year++;
429 }
430
431 /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st
432 in a non-leap xx00. There is one point in the cycle
433 we can't account for which the safe xx00 year is a leap
434 year. So we need to correct for Dec 31st comming out as
435 the 366th day of the year.
436 */
437 if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 )
438 local_tm->tm_yday--;
439
af9b2bf5 440 assert(_check_tm(local_tm));
a272e669 441
442 return local_tm;
443}