This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Update from y2038 project.
[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
197 assert( year >= 2001 );
198
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.
206*/
207#define SOLAR_CYCLE_LENGTH 28
9af24521 208int _safe_year(Int64 year)
a272e669
MS
209{
210 int safe_year;
9af24521 211 Int64 year_cycle = year + _cycle_offset(year);
a272e669
MS
212
213 /* Change non-leap xx00 years to an equivalent */
214 if( _is_exception_century(year) )
215 year_cycle += 11;
216
217 year_cycle %= SOLAR_CYCLE_LENGTH;
218
219 safe_year = safe_years[year_cycle];
220
221 assert(safe_year <= 2037 && safe_year >= 2010);
222
223 /*
224 printf("year: %d, year_cycle: %d, safe_year: %d\n",
225 year, year_cycle, safe_year);
226 */
227
228 return safe_year;
229}
230
231struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p)
232{
233 int v_tm_sec, v_tm_min, v_tm_hour, v_tm_mon, v_tm_wday;
9af24521 234 Int64 v_tm_tday;
a272e669 235 int leap;
9af24521 236 Int64 m;
a272e669 237 Time64_T time = *in_time;
9af24521 238 Int64 year = 70;
a272e669 239
a64acb40
MS
240 /* Use the system gmtime() if time_t is small enough */
241 if( SHOULD_USE_SYSTEM_GMTIME(*in_time) ) {
242 time_t safe_time = *in_time;
243 localtime_r(&safe_time, p);
af9b2bf5 244 assert(_check_tm(p));
a64acb40
MS
245 return p;
246 }
247
9af24521 248#ifdef HAS_TM_TM_GMTOFF
a272e669
MS
249 p->tm_gmtoff = 0;
250#endif
251 p->tm_isdst = 0;
252
9af24521 253#ifdef HAS_TM_TM_ZONE
a272e669
MS
254 p->tm_zone = "UTC";
255#endif
256
257 v_tm_sec = time % 60;
258 time /= 60;
259 v_tm_min = time % 60;
260 time /= 60;
261 v_tm_hour = time % 24;
262 time /= 24;
263 v_tm_tday = time;
264 WRAP (v_tm_sec, v_tm_min, 60);
265 WRAP (v_tm_min, v_tm_hour, 60);
266 WRAP (v_tm_hour, v_tm_tday, 24);
267 if ((v_tm_wday = (v_tm_tday + 4) % 7) < 0)
268 v_tm_wday += 7;
269 m = v_tm_tday;
a272e669 270
9af24521
MS
271 if (m >= CHEAT_DAYS) {
272 year = CHEAT_YEARS;
273 m -= CHEAT_DAYS;
274 }
275
276 if (m >= 0) {
a272e669
MS
277 /* Gregorian cycles, this is huge optimization for distant times */
278 while (m >= (Time64_T) days_in_gregorian_cycle) {
279 m -= (Time64_T) days_in_gregorian_cycle;
280 year += years_in_gregorian_cycle;
281 }
282
283 /* Years */
284 leap = IS_LEAP (year);
285 while (m >= (Time64_T) length_of_year[leap]) {
286 m -= (Time64_T) length_of_year[leap];
287 year++;
288 leap = IS_LEAP (year);
289 }
290
291 /* Months */
292 v_tm_mon = 0;
293 while (m >= (Time64_T) days_in_month[leap][v_tm_mon]) {
294 m -= (Time64_T) days_in_month[leap][v_tm_mon];
295 v_tm_mon++;
296 }
297 } else {
9af24521 298 year--;
a272e669
MS
299
300 /* Gregorian cycles */
301 while (m < (Time64_T) -days_in_gregorian_cycle) {
302 m += (Time64_T) days_in_gregorian_cycle;
303 year -= years_in_gregorian_cycle;
304 }
305
306 /* Years */
307 leap = IS_LEAP (year);
308 while (m < (Time64_T) -length_of_year[leap]) {
309 m += (Time64_T) length_of_year[leap];
310 year--;
311 leap = IS_LEAP (year);
312 }
313
314 /* Months */
315 v_tm_mon = 11;
316 while (m < (Time64_T) -days_in_month[leap][v_tm_mon]) {
317 m += (Time64_T) days_in_month[leap][v_tm_mon];
318 v_tm_mon--;
319 }
320 m += (Time64_T) days_in_month[leap][v_tm_mon];
321 }
322
323 p->tm_year = year;
324 if( p->tm_year != year ) {
9af24521 325#ifdef EOVERFLOW
a272e669 326 errno = EOVERFLOW;
9af24521 327#endif
a272e669
MS
328 return NULL;
329 }
330
331 p->tm_mday = (int) m + 1;
332 p->tm_yday = julian_days_by_month[leap][v_tm_mon] + m;
333 p->tm_sec = v_tm_sec, p->tm_min = v_tm_min, p->tm_hour = v_tm_hour,
334 p->tm_mon = v_tm_mon, p->tm_wday = v_tm_wday;
335
af9b2bf5 336 assert(_check_tm(p));
a272e669
MS
337
338 return p;
339}
340
341
342struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm)
343{
344 time_t safe_time;
345 struct tm gm_tm;
9af24521 346 Int64 orig_year;
a272e669
MS
347 int month_diff;
348
a64acb40
MS
349 /* Use the system localtime() if time_t is small enough */
350 if( SHOULD_USE_SYSTEM_LOCALTIME(*time) ) {
351 safe_time = *time;
352 localtime_r(&safe_time, local_tm);
af9b2bf5 353 assert(_check_tm(local_tm));
a64acb40
MS
354 return local_tm;
355 }
356
af832814
MS
357 if( gmtime64_r(time, &gm_tm) == NULL )
358 return NULL;
359
a272e669
MS
360 orig_year = gm_tm.tm_year;
361
362 if (gm_tm.tm_year > (2037 - 1900))
363 gm_tm.tm_year = _safe_year(gm_tm.tm_year + 1900) - 1900;
364
9af24521 365 safe_time = TIMEGM(&gm_tm);
af832814
MS
366 if( localtime_r(&safe_time, local_tm) == NULL )
367 return NULL;
a272e669
MS
368
369 local_tm->tm_year = orig_year;
af832814
MS
370 if( local_tm->tm_year != orig_year ) {
371#ifdef EOVERFLOW
372 errno = EOVERFLOW;
373#endif
374 return NULL;
375 }
376
377
a272e669
MS
378 month_diff = local_tm->tm_mon - gm_tm.tm_mon;
379
380 /* When localtime is Dec 31st previous year and
381 gmtime is Jan 1st next year.
382 */
383 if( month_diff == 11 ) {
384 local_tm->tm_year--;
385 }
386
387 /* When localtime is Jan 1st, next year and
388 gmtime is Dec 31st, previous year.
389 */
390 if( month_diff == -11 ) {
391 local_tm->tm_year++;
392 }
393
394 /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st
395 in a non-leap xx00. There is one point in the cycle
396 we can't account for which the safe xx00 year is a leap
397 year. So we need to correct for Dec 31st comming out as
398 the 366th day of the year.
399 */
400 if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 )
401 local_tm->tm_yday--;
402
af9b2bf5 403 assert(_check_tm(local_tm));
a272e669
MS
404
405 return local_tm;
406}