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