Update from y2038.
[p5sagit/p5-mst-13.2.git] / localtime64.c
1 /*
2
3 Copyright (c) 2007-2008  Michael G Schwern
4
5 This software originally derived from Paul Sheer's pivotal_gmtime_r.c.
6
7 The MIT License:
8
9 Permission is hereby granted, free of charge, to any person obtaining a copy
10 of this software and associated documentation files (the "Software"), to deal
11 in the Software without restriction, including without limitation the rights
12 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 copies of the Software, and to permit persons to whom the Software is
14 furnished to do so, subject to the following conditions:
15
16 The above copyright notice and this permission notice shall be included in
17 all copies or substantial portions of the Software.
18
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 THE SOFTWARE.
26
27 */
28
29 /*
30
31 Programmers who have available to them 64-bit time values as a 'long
32 long' type can use localtime64_r() and gmtime64_r() which correctly
33 converts the time even on 32-bit systems. Whether you have 64-bit time
34 values will depend on the operating system.
35
36 localtime64_r() is a 64-bit equivalent of localtime_r().
37
38 gmtime64_r() is a 64-bit equivalent of gmtime_r().
39
40 */
41
42 #include "localtime64.h"
43
44 static 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
49 static 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
54 static const int length_of_year[2] = { 365, 366 };
55
56 /* Number of days in a 400 year Gregorian cycle */
57 static const int years_in_gregorian_cycle = 400;
58 static const int days_in_gregorian_cycle  = (365 * 400) + 100 - 4 + 1;
59
60 /* 28 year calendar cycle between 2010 and 2037 */
61 static 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
71 static 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
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
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
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 )
102
103
104 int _is_exception_century(Int64 year)
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
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);
118 #endif
119
120 time_t _my_timegm(struct tm *date) {
121     int days    = 0;
122     int seconds = 0;
123     time_t time;
124     int year;
125
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++;
131         }
132     }
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
154 int _check_tm(struct tm *tm)
155 {
156     /* Don't forget leap seconds */
157     assert(tm->tm_sec >= 0);
158     assert(tm->tm_sec <= 61);
159
160     assert(tm->tm_min >= 0);
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);
167     assert(tm->tm_mday <= days_in_month[IS_LEAP(tm->tm_year)][tm->tm_mon]);
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);
176     assert(tm->tm_yday <= length_of_year[IS_LEAP(tm->tm_year)]);
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
182
183     return 1;
184 }
185
186
187 /* The exceptional centuries without leap years cause the cycle to
188    shift by 16
189 */
190 int _cycle_offset(Int64 year)
191 {
192     const Int64 start_year = 2000;
193     Int64 year_diff  = year - start_year - 1;
194     Int64 exceptions  = year_diff / 100;
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
208 int _safe_year(Int64 year)
209 {
210     int safe_year;
211     Int64 year_cycle = year + _cycle_offset(year);
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
231 struct 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;
234     Int64 v_tm_tday;
235     int leap;
236     Int64 m;
237     Time64_T time = *in_time;
238     Int64 year = 70;
239
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);
244         assert(_check_tm(p));
245         return p;
246     }
247
248 #ifdef HAS_TM_TM_GMTOFF
249     p->tm_gmtoff = 0;
250 #endif
251     p->tm_isdst  = 0;
252
253 #ifdef HAS_TM_TM_ZONE
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;
270
271     if (m >= CHEAT_DAYS) {
272         year = CHEAT_YEARS;
273         m -= CHEAT_DAYS;
274     }
275
276     if (m >= 0) {
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 {
298         year--;
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 ) {
325 #ifdef EOVERFLOW
326         errno = EOVERFLOW;
327 #endif
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
336     assert(_check_tm(p));
337
338     return p;
339 }
340
341
342 struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm)
343 {
344     time_t safe_time;
345     struct tm gm_tm;
346     Int64 orig_year;
347     int month_diff;
348
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);
353         assert(_check_tm(local_tm));
354         return local_tm;
355     }
356
357     gmtime64_r(time, &gm_tm);
358     orig_year = gm_tm.tm_year;
359
360     if (gm_tm.tm_year > (2037 - 1900))
361         gm_tm.tm_year = _safe_year(gm_tm.tm_year + 1900) - 1900;
362
363     safe_time = TIMEGM(&gm_tm);
364     localtime_r(&safe_time, local_tm);
365
366     local_tm->tm_year = orig_year;
367     month_diff = local_tm->tm_mon - gm_tm.tm_mon;
368
369     /*  When localtime is Dec 31st previous year and
370         gmtime is Jan 1st next year.
371     */
372     if( month_diff == 11 ) {
373         local_tm->tm_year--;
374     }
375
376     /*  When localtime is Jan 1st, next year and
377         gmtime is Dec 31st, previous year.
378     */
379     if( month_diff == -11 ) {
380         local_tm->tm_year++;
381     }
382
383     /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st
384        in a non-leap xx00.  There is one point in the cycle
385        we can't account for which the safe xx00 year is a leap
386        year.  So we need to correct for Dec 31st comming out as
387        the 366th day of the year.
388     */
389     if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 )
390         local_tm->tm_yday--;
391
392     assert(_check_tm(local_tm));
393
394     return local_tm;
395 }