This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Pull in changes 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
71static const int dow_year_start[28] = {
72 5, 0, 1, 2, /* 2016 - 2019 */
73 3, 5, 6, 0,
74 1, 3, 4, 5,
75 6, 1, 2, 3,
76 4, 6, 0, 1,
77 2, 4, 5, 6, /* 2036, 2037, 2010, 2011 */
78 0, 2, 3, 4 /* 2012, 2013, 2014, 2015 */
79};
80
9af24521
MS
81/* Let's assume people are going to be looking for dates in the future.
82 Let's provide some cheats so you can skip ahead.
83 This has a 4x speed boost when near 2008.
84*/
85/* Number of days since epoch on Jan 1st, 2008 GMT */
86#define CHEAT_DAYS (1199145600 / 24 / 60 / 60)
87#define CHEAT_YEARS 108
a272e669
MS
88
89#define IS_LEAP(n) ((!(((n) + 1900) % 400) || (!(((n) + 1900) % 4) && (((n) + 1900) % 100))) != 0)
90#define WRAP(a,b,m) ((a) = ((a) < 0 ) ? ((b)--, (a) + (m)) : (a))
91
7bda3dfc
MS
92#define SHOULD_USE_SYSTEM_LOCALTIME(a) ( \
93 USE_SYSTEM_LOCALTIME && \
94 (a) <= SYSTEM_LOCALTIME_MAX && \
95 (a) >= SYSTEM_LOCALTIME_MIN \
96)
97#define SHOULD_USE_SYSTEM_GMTIME(a) ( \
98 USE_SYSTEM_GMTIME && \
99 (a) <= SYSTEM_GMTIME_MAX && \
100 (a) >= SYSTEM_GMTIME_MIN \
101)
a64acb40
MS
102
103
9af24521 104int _is_exception_century(Int64 year)
a272e669
MS
105{
106 int is_exception = ((year % 100 == 0) && !(year % 400 == 0));
107 /* printf("is_exception_century: %s\n", is_exception ? "yes" : "no"); */
108
109 return(is_exception);
110}
111
9af24521
MS
112
113/* timegm() is a GNU extension, so emulate it here if we need it */
114#ifdef HAS_TIMEGM
115# define TIMEGM(n) timegm(n);
116#else
117# define TIMEGM(n) _my_timegm(n);
a272e669
MS
118#endif
119
9af24521
MS
120time_t _my_timegm(struct tm *date) {
121 int days = 0;
122 int seconds = 0;
123 time_t time;
124 int 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
144 seconds += date->tm_hour * 60 * 60;
145 seconds += date->tm_min * 60;
146 seconds += date->tm_sec;
147
148 time = (time_t)(days * 60 * 60 * 24) + seconds;
149
150 return(time);
151}
152
153
af9b2bf5 154int _check_tm(struct tm *tm)
9af24521 155{
9af24521 156 /* Don't forget leap seconds */
af9b2bf5 157 assert(tm->tm_sec >= 0);
9af24521
MS
158 assert(tm->tm_sec <= 61);
159
af9b2bf5 160 assert(tm->tm_min >= 0);
9af24521
MS
161 assert(tm->tm_min <= 59);
162
163 assert(tm->tm_hour >= 0);
164 assert(tm->tm_hour <= 23);
165
166 assert(tm->tm_mday >= 1);
af9b2bf5 167 assert(tm->tm_mday <= days_in_month[IS_LEAP(tm->tm_year)][tm->tm_mon]);
9af24521
MS
168
169 assert(tm->tm_mon >= 0);
170 assert(tm->tm_mon <= 11);
171
172 assert(tm->tm_wday >= 0);
173 assert(tm->tm_wday <= 6);
174
175 assert(tm->tm_yday >= 0);
af9b2bf5 176 assert(tm->tm_yday <= length_of_year[IS_LEAP(tm->tm_year)]);
9af24521
MS
177
178#ifdef HAS_TM_TM_GMTOFF
179 assert(tm->tm_gmtoff >= -24 * 60 * 60);
180 assert(tm->tm_gmtoff <= 24 * 60 * 60);
181#endif
af9b2bf5
MS
182
183 return 1;
a272e669 184}
a64acb40 185
a272e669
MS
186
187/* The exceptional centuries without leap years cause the cycle to
188 shift by 16
189*/
9af24521 190int _cycle_offset(Int64 year)
a272e669 191{
9af24521
MS
192 const Int64 start_year = 2000;
193 Int64 year_diff = year - start_year - 1;
194 Int64 exceptions = year_diff / 100;
a272e669
MS
195 exceptions -= year_diff / 400;
196
a272e669
MS
197 /* printf("year: %d, exceptions: %d\n", year, exceptions); */
198
199 return exceptions * 16;
200}
201
202/* For a given year after 2038, pick the latest possible matching
203 year in the 28 year calendar cycle.
204*/
205#define SOLAR_CYCLE_LENGTH 28
9af24521 206int _safe_year(Int64 year)
a272e669
MS
207{
208 int safe_year;
9af24521 209 Int64 year_cycle = year + _cycle_offset(year);
a272e669
MS
210
211 /* Change non-leap xx00 years to an equivalent */
212 if( _is_exception_century(year) )
213 year_cycle += 11;
214
215 year_cycle %= SOLAR_CYCLE_LENGTH;
216
217 safe_year = safe_years[year_cycle];
218
219 assert(safe_year <= 2037 && safe_year >= 2010);
220
221 /*
222 printf("year: %d, year_cycle: %d, safe_year: %d\n",
223 year, year_cycle, safe_year);
224 */
225
226 return safe_year;
227}
228
229struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p)
230{
231 int v_tm_sec, v_tm_min, v_tm_hour, v_tm_mon, v_tm_wday;
9af24521 232 Int64 v_tm_tday;
a272e669 233 int leap;
9af24521 234 Int64 m;
a272e669 235 Time64_T time = *in_time;
9af24521 236 Int64 year = 70;
a272e669 237
a64acb40
MS
238 /* Use the system gmtime() if time_t is small enough */
239 if( SHOULD_USE_SYSTEM_GMTIME(*in_time) ) {
240 time_t safe_time = *in_time;
241 localtime_r(&safe_time, p);
af9b2bf5 242 assert(_check_tm(p));
a64acb40
MS
243 return p;
244 }
245
9af24521 246#ifdef HAS_TM_TM_GMTOFF
a272e669
MS
247 p->tm_gmtoff = 0;
248#endif
249 p->tm_isdst = 0;
250
9af24521 251#ifdef HAS_TM_TM_ZONE
a272e669
MS
252 p->tm_zone = "UTC";
253#endif
254
255 v_tm_sec = time % 60;
256 time /= 60;
257 v_tm_min = time % 60;
258 time /= 60;
259 v_tm_hour = time % 24;
260 time /= 24;
261 v_tm_tday = time;
262 WRAP (v_tm_sec, v_tm_min, 60);
263 WRAP (v_tm_min, v_tm_hour, 60);
264 WRAP (v_tm_hour, v_tm_tday, 24);
265 if ((v_tm_wday = (v_tm_tday + 4) % 7) < 0)
266 v_tm_wday += 7;
267 m = v_tm_tday;
a272e669 268
9af24521
MS
269 if (m >= CHEAT_DAYS) {
270 year = CHEAT_YEARS;
271 m -= CHEAT_DAYS;
272 }
273
274 if (m >= 0) {
a272e669
MS
275 /* Gregorian cycles, this is huge optimization for distant times */
276 while (m >= (Time64_T) days_in_gregorian_cycle) {
277 m -= (Time64_T) days_in_gregorian_cycle;
278 year += years_in_gregorian_cycle;
279 }
280
281 /* Years */
282 leap = IS_LEAP (year);
283 while (m >= (Time64_T) length_of_year[leap]) {
284 m -= (Time64_T) length_of_year[leap];
285 year++;
286 leap = IS_LEAP (year);
287 }
288
289 /* Months */
290 v_tm_mon = 0;
291 while (m >= (Time64_T) days_in_month[leap][v_tm_mon]) {
292 m -= (Time64_T) days_in_month[leap][v_tm_mon];
293 v_tm_mon++;
294 }
295 } else {
9af24521 296 year--;
a272e669
MS
297
298 /* Gregorian cycles */
299 while (m < (Time64_T) -days_in_gregorian_cycle) {
300 m += (Time64_T) days_in_gregorian_cycle;
301 year -= years_in_gregorian_cycle;
302 }
303
304 /* Years */
305 leap = IS_LEAP (year);
306 while (m < (Time64_T) -length_of_year[leap]) {
307 m += (Time64_T) length_of_year[leap];
308 year--;
309 leap = IS_LEAP (year);
310 }
311
312 /* Months */
313 v_tm_mon = 11;
314 while (m < (Time64_T) -days_in_month[leap][v_tm_mon]) {
315 m += (Time64_T) days_in_month[leap][v_tm_mon];
316 v_tm_mon--;
317 }
318 m += (Time64_T) days_in_month[leap][v_tm_mon];
319 }
320
321 p->tm_year = year;
322 if( p->tm_year != year ) {
9af24521 323#ifdef EOVERFLOW
a272e669 324 errno = EOVERFLOW;
9af24521 325#endif
a272e669
MS
326 return NULL;
327 }
328
329 p->tm_mday = (int) m + 1;
330 p->tm_yday = julian_days_by_month[leap][v_tm_mon] + m;
331 p->tm_sec = v_tm_sec, p->tm_min = v_tm_min, p->tm_hour = v_tm_hour,
332 p->tm_mon = v_tm_mon, p->tm_wday = v_tm_wday;
333
af9b2bf5 334 assert(_check_tm(p));
a272e669
MS
335
336 return p;
337}
338
339
340struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm)
341{
342 time_t safe_time;
343 struct tm gm_tm;
9af24521 344 Int64 orig_year;
a272e669
MS
345 int month_diff;
346
a64acb40
MS
347 /* Use the system localtime() if time_t is small enough */
348 if( SHOULD_USE_SYSTEM_LOCALTIME(*time) ) {
349 safe_time = *time;
350 localtime_r(&safe_time, local_tm);
af9b2bf5 351 assert(_check_tm(local_tm));
a64acb40
MS
352 return local_tm;
353 }
354
af832814
MS
355 if( gmtime64_r(time, &gm_tm) == NULL )
356 return NULL;
357
a272e669
MS
358 orig_year = gm_tm.tm_year;
359
c07fe26c
MS
360 if (gm_tm.tm_year > (2037 - 1900) ||
361 gm_tm.tm_year < (1902 - 1900)
362 )
363 {
a272e669 364 gm_tm.tm_year = _safe_year(gm_tm.tm_year + 1900) - 1900;
c07fe26c 365 }
a272e669 366
9af24521 367 safe_time = TIMEGM(&gm_tm);
af832814
MS
368 if( localtime_r(&safe_time, local_tm) == NULL )
369 return NULL;
a272e669
MS
370
371 local_tm->tm_year = orig_year;
af832814
MS
372 if( local_tm->tm_year != orig_year ) {
373#ifdef EOVERFLOW
374 errno = EOVERFLOW;
375#endif
376 return NULL;
377 }
378
379
a272e669
MS
380 month_diff = local_tm->tm_mon - gm_tm.tm_mon;
381
382 /* When localtime is Dec 31st previous year and
383 gmtime is Jan 1st next year.
384 */
385 if( month_diff == 11 ) {
386 local_tm->tm_year--;
387 }
388
389 /* When localtime is Jan 1st, next year and
390 gmtime is Dec 31st, previous year.
391 */
392 if( month_diff == -11 ) {
393 local_tm->tm_year++;
394 }
395
396 /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st
397 in a non-leap xx00. There is one point in the cycle
398 we can't account for which the safe xx00 year is a leap
399 year. So we need to correct for Dec 31st comming out as
400 the 366th day of the year.
401 */
402 if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 )
403 local_tm->tm_yday--;
404
af9b2bf5 405 assert(_check_tm(local_tm));
a272e669
MS
406
407 return local_tm;
408}