This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Update from y2038
[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
af9b2bf5
MS
42#include "localtime64.h"
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] = {
a272e669
MS
73 5, 0, 1, 2, /* 2016 - 2019 */
74 3, 5, 6, 0,
75 1, 3, 4, 5,
76 6, 1, 2, 3,
77 4, 6, 0, 1,
78 2, 4, 5, 6, /* 2036, 2037, 2010, 2011 */
79 0, 2, 3, 4 /* 2012, 2013, 2014, 2015 */
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*/
9af24521 192int _cycle_offset(Int64 year)
a272e669 193{
9af24521
MS
194 const Int64 start_year = 2000;
195 Int64 year_diff = year - start_year - 1;
196 Int64 exceptions = year_diff / 100;
a272e669
MS
197 exceptions -= year_diff / 400;
198
a272e669
MS
199 /* printf("year: %d, exceptions: %d\n", year, exceptions); */
200
201 return exceptions * 16;
202}
203
204/* For a given year after 2038, pick the latest possible matching
205 year in the 28 year calendar cycle.
ea722b76
MS
206
207 A matching year...
208 1) Starts on the same day of the week.
209 2) Has the same leap year status.
210
211 This is so the calendars match up.
212
213 Also the previous year must match. When doing Jan 1st you might
214 wind up on Dec 31st the previous year when doing a -UTC time zone.
a272e669 215*/
9af24521 216int _safe_year(Int64 year)
a272e669
MS
217{
218 int safe_year;
9af24521 219 Int64 year_cycle = year + _cycle_offset(year);
a272e669
MS
220
221 /* Change non-leap xx00 years to an equivalent */
222 if( _is_exception_century(year) )
223 year_cycle += 11;
224
225 year_cycle %= SOLAR_CYCLE_LENGTH;
ea722b76
MS
226 if( year_cycle < 0 )
227 year_cycle = SOLAR_CYCLE_LENGTH + year_cycle;
a272e669
MS
228
229 safe_year = safe_years[year_cycle];
230
231 assert(safe_year <= 2037 && safe_year >= 2010);
232
233 /*
234 printf("year: %d, year_cycle: %d, safe_year: %d\n",
235 year, year_cycle, safe_year);
236 */
237
238 return safe_year;
239}
240
241struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p)
242{
243 int v_tm_sec, v_tm_min, v_tm_hour, v_tm_mon, v_tm_wday;
9af24521 244 Int64 v_tm_tday;
a272e669 245 int leap;
9af24521 246 Int64 m;
a272e669 247 Time64_T time = *in_time;
9af24521 248 Int64 year = 70;
a272e669 249
a64acb40
MS
250 /* Use the system gmtime() if time_t is small enough */
251 if( SHOULD_USE_SYSTEM_GMTIME(*in_time) ) {
252 time_t safe_time = *in_time;
253 localtime_r(&safe_time, p);
af9b2bf5 254 assert(_check_tm(p));
a64acb40
MS
255 return p;
256 }
257
9af24521 258#ifdef HAS_TM_TM_GMTOFF
a272e669
MS
259 p->tm_gmtoff = 0;
260#endif
261 p->tm_isdst = 0;
262
9af24521 263#ifdef HAS_TM_TM_ZONE
a272e669
MS
264 p->tm_zone = "UTC";
265#endif
266
267 v_tm_sec = time % 60;
268 time /= 60;
269 v_tm_min = time % 60;
270 time /= 60;
271 v_tm_hour = time % 24;
272 time /= 24;
273 v_tm_tday = time;
274 WRAP (v_tm_sec, v_tm_min, 60);
275 WRAP (v_tm_min, v_tm_hour, 60);
276 WRAP (v_tm_hour, v_tm_tday, 24);
277 if ((v_tm_wday = (v_tm_tday + 4) % 7) < 0)
278 v_tm_wday += 7;
279 m = v_tm_tday;
a272e669 280
9af24521
MS
281 if (m >= CHEAT_DAYS) {
282 year = CHEAT_YEARS;
283 m -= CHEAT_DAYS;
284 }
285
286 if (m >= 0) {
a272e669
MS
287 /* Gregorian cycles, this is huge optimization for distant times */
288 while (m >= (Time64_T) days_in_gregorian_cycle) {
289 m -= (Time64_T) days_in_gregorian_cycle;
290 year += years_in_gregorian_cycle;
291 }
292
293 /* Years */
294 leap = IS_LEAP (year);
295 while (m >= (Time64_T) length_of_year[leap]) {
296 m -= (Time64_T) length_of_year[leap];
297 year++;
298 leap = IS_LEAP (year);
299 }
300
301 /* Months */
302 v_tm_mon = 0;
303 while (m >= (Time64_T) days_in_month[leap][v_tm_mon]) {
304 m -= (Time64_T) days_in_month[leap][v_tm_mon];
305 v_tm_mon++;
306 }
307 } else {
9af24521 308 year--;
a272e669
MS
309
310 /* Gregorian cycles */
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 = 11;
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 m += (Time64_T) days_in_month[leap][v_tm_mon];
331 }
332
333 p->tm_year = year;
334 if( p->tm_year != year ) {
9af24521 335#ifdef EOVERFLOW
a272e669 336 errno = EOVERFLOW;
9af24521 337#endif
a272e669
MS
338 return NULL;
339 }
340
341 p->tm_mday = (int) m + 1;
342 p->tm_yday = julian_days_by_month[leap][v_tm_mon] + m;
343 p->tm_sec = v_tm_sec, p->tm_min = v_tm_min, p->tm_hour = v_tm_hour,
344 p->tm_mon = v_tm_mon, p->tm_wday = v_tm_wday;
345
af9b2bf5 346 assert(_check_tm(p));
a272e669
MS
347
348 return p;
349}
350
351
352struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm)
353{
354 time_t safe_time;
355 struct tm gm_tm;
9af24521 356 Int64 orig_year;
a272e669
MS
357 int month_diff;
358
a64acb40
MS
359 /* Use the system localtime() if time_t is small enough */
360 if( SHOULD_USE_SYSTEM_LOCALTIME(*time) ) {
361 safe_time = *time;
362 localtime_r(&safe_time, local_tm);
af9b2bf5 363 assert(_check_tm(local_tm));
a64acb40
MS
364 return local_tm;
365 }
366
af832814
MS
367 if( gmtime64_r(time, &gm_tm) == NULL )
368 return NULL;
369
a272e669
MS
370 orig_year = gm_tm.tm_year;
371
c07fe26c
MS
372 if (gm_tm.tm_year > (2037 - 1900) ||
373 gm_tm.tm_year < (1902 - 1900)
374 )
375 {
a272e669 376 gm_tm.tm_year = _safe_year(gm_tm.tm_year + 1900) - 1900;
c07fe26c 377 }
a272e669 378
9af24521 379 safe_time = TIMEGM(&gm_tm);
af832814
MS
380 if( localtime_r(&safe_time, local_tm) == NULL )
381 return NULL;
a272e669
MS
382
383 local_tm->tm_year = orig_year;
af832814
MS
384 if( local_tm->tm_year != orig_year ) {
385#ifdef EOVERFLOW
386 errno = EOVERFLOW;
387#endif
388 return NULL;
389 }
390
391
a272e669
MS
392 month_diff = local_tm->tm_mon - gm_tm.tm_mon;
393
394 /* When localtime is Dec 31st previous year and
395 gmtime is Jan 1st next year.
396 */
397 if( month_diff == 11 ) {
398 local_tm->tm_year--;
399 }
400
401 /* When localtime is Jan 1st, next year and
402 gmtime is Dec 31st, previous year.
403 */
404 if( month_diff == -11 ) {
405 local_tm->tm_year++;
406 }
407
408 /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st
409 in a non-leap xx00. There is one point in the cycle
410 we can't account for which the safe xx00 year is a leap
411 year. So we need to correct for Dec 31st comming out as
412 the 366th day of the year.
413 */
414 if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 )
415 local_tm->tm_yday--;
416
af9b2bf5 417 assert(_check_tm(local_tm));
a272e669
MS
418
419 return local_tm;
420}