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