Write down the logic behind the localtime vs gmtime date ranges.
[p5sagit/p5-mst-13.2.git] / localtime64.c
CommitLineData
a272e669 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 42#include "localtime64.h"
43
a272e669 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 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 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
a64acb40 92#define SHOULD_USE_SYSTEM_LOCALTIME(a) ( USE_SYSTEM_LOCALTIME && (a) <= SYSTEM_LOCALTIME_MAX )
93#define SHOULD_USE_SYSTEM_GMTIME(a) ( USE_SYSTEM_GMTIME && (a) <= SYSTEM_GMTIME_MAX )
94
95
9af24521 96int _is_exception_century(Int64 year)
a272e669 97{
98 int is_exception = ((year % 100 == 0) && !(year % 400 == 0));
99 /* printf("is_exception_century: %s\n", is_exception ? "yes" : "no"); */
100
101 return(is_exception);
102}
103
9af24521 104
105/* timegm() is a GNU extension, so emulate it here if we need it */
106#ifdef HAS_TIMEGM
107# define TIMEGM(n) timegm(n);
108#else
109# define TIMEGM(n) _my_timegm(n);
a272e669 110#endif
111
9af24521 112time_t _my_timegm(struct tm *date) {
113 int days = 0;
114 int seconds = 0;
115 time_t time;
116 int year;
a272e669 117
9af24521 118 if( date->tm_year > 70 ) {
119 year = 70;
120 while( year < date->tm_year ) {
121 days += length_of_year[IS_LEAP(year)];
122 year++;
a272e669 123 }
124 }
9af24521 125 else if ( date->tm_year < 70 ) {
126 year = 69;
127 do {
128 days -= length_of_year[IS_LEAP(year)];
129 year--;
130 } while( year >= date->tm_year );
131 }
132
133 days += julian_days_by_month[IS_LEAP(date->tm_year)][date->tm_mon];
134 days += date->tm_mday - 1;
135
136 seconds += date->tm_hour * 60 * 60;
137 seconds += date->tm_min * 60;
138 seconds += date->tm_sec;
139
140 time = (time_t)(days * 60 * 60 * 24) + seconds;
141
142 return(time);
143}
144
145
af9b2bf5 146int _check_tm(struct tm *tm)
9af24521 147{
9af24521 148 /* Don't forget leap seconds */
af9b2bf5 149 assert(tm->tm_sec >= 0);
9af24521 150 assert(tm->tm_sec <= 61);
151
af9b2bf5 152 assert(tm->tm_min >= 0);
9af24521 153 assert(tm->tm_min <= 59);
154
155 assert(tm->tm_hour >= 0);
156 assert(tm->tm_hour <= 23);
157
158 assert(tm->tm_mday >= 1);
af9b2bf5 159 assert(tm->tm_mday <= days_in_month[IS_LEAP(tm->tm_year)][tm->tm_mon]);
9af24521 160
161 assert(tm->tm_mon >= 0);
162 assert(tm->tm_mon <= 11);
163
164 assert(tm->tm_wday >= 0);
165 assert(tm->tm_wday <= 6);
166
167 assert(tm->tm_yday >= 0);
af9b2bf5 168 assert(tm->tm_yday <= length_of_year[IS_LEAP(tm->tm_year)]);
9af24521 169
170#ifdef HAS_TM_TM_GMTOFF
171 assert(tm->tm_gmtoff >= -24 * 60 * 60);
172 assert(tm->tm_gmtoff <= 24 * 60 * 60);
173#endif
af9b2bf5 174
175 return 1;
a272e669 176}
a64acb40 177
a272e669 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 184 const Int64 start_year = 2000;
185 Int64 year_diff = year - start_year - 1;
186 Int64 exceptions = year_diff / 100;
a272e669 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 201{
202 int safe_year;
9af24521 203 Int64 year_cycle = year + _cycle_offset(year);
a272e669 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
a64acb40 232 /* Use the system gmtime() if time_t is small enough */
233 if( SHOULD_USE_SYSTEM_GMTIME(*in_time) ) {
234 time_t safe_time = *in_time;
235 localtime_r(&safe_time, p);
af9b2bf5 236 assert(_check_tm(p));
a64acb40 237 return p;
238 }
239
9af24521 240#ifdef HAS_TM_TM_GMTOFF
a272e669 241 p->tm_gmtoff = 0;
242#endif
243 p->tm_isdst = 0;
244
9af24521 245#ifdef HAS_TM_TM_ZONE
a272e669 246 p->tm_zone = "UTC";
247#endif
248
249 v_tm_sec = time % 60;
250 time /= 60;
251 v_tm_min = time % 60;
252 time /= 60;
253 v_tm_hour = time % 24;
254 time /= 24;
255 v_tm_tday = time;
256 WRAP (v_tm_sec, v_tm_min, 60);
257 WRAP (v_tm_min, v_tm_hour, 60);
258 WRAP (v_tm_hour, v_tm_tday, 24);
259 if ((v_tm_wday = (v_tm_tday + 4) % 7) < 0)
260 v_tm_wday += 7;
261 m = v_tm_tday;
a272e669 262
9af24521 263 if (m >= CHEAT_DAYS) {
264 year = CHEAT_YEARS;
265 m -= CHEAT_DAYS;
266 }
267
268 if (m >= 0) {
a272e669 269 /* Gregorian cycles, this is huge optimization for distant times */
270 while (m >= (Time64_T) days_in_gregorian_cycle) {
271 m -= (Time64_T) days_in_gregorian_cycle;
272 year += years_in_gregorian_cycle;
273 }
274
275 /* Years */
276 leap = IS_LEAP (year);
277 while (m >= (Time64_T) length_of_year[leap]) {
278 m -= (Time64_T) length_of_year[leap];
279 year++;
280 leap = IS_LEAP (year);
281 }
282
283 /* Months */
284 v_tm_mon = 0;
285 while (m >= (Time64_T) days_in_month[leap][v_tm_mon]) {
286 m -= (Time64_T) days_in_month[leap][v_tm_mon];
287 v_tm_mon++;
288 }
289 } else {
9af24521 290 year--;
a272e669 291
292 /* Gregorian cycles */
293 while (m < (Time64_T) -days_in_gregorian_cycle) {
294 m += (Time64_T) days_in_gregorian_cycle;
295 year -= years_in_gregorian_cycle;
296 }
297
298 /* Years */
299 leap = IS_LEAP (year);
300 while (m < (Time64_T) -length_of_year[leap]) {
301 m += (Time64_T) length_of_year[leap];
302 year--;
303 leap = IS_LEAP (year);
304 }
305
306 /* Months */
307 v_tm_mon = 11;
308 while (m < (Time64_T) -days_in_month[leap][v_tm_mon]) {
309 m += (Time64_T) days_in_month[leap][v_tm_mon];
310 v_tm_mon--;
311 }
312 m += (Time64_T) days_in_month[leap][v_tm_mon];
313 }
314
315 p->tm_year = year;
316 if( p->tm_year != year ) {
9af24521 317#ifdef EOVERFLOW
a272e669 318 errno = EOVERFLOW;
9af24521 319#endif
a272e669 320 return NULL;
321 }
322
323 p->tm_mday = (int) m + 1;
324 p->tm_yday = julian_days_by_month[leap][v_tm_mon] + m;
325 p->tm_sec = v_tm_sec, p->tm_min = v_tm_min, p->tm_hour = v_tm_hour,
326 p->tm_mon = v_tm_mon, p->tm_wday = v_tm_wday;
327
af9b2bf5 328 assert(_check_tm(p));
a272e669 329
330 return p;
331}
332
333
334struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm)
335{
336 time_t safe_time;
337 struct tm gm_tm;
9af24521 338 Int64 orig_year;
a272e669 339 int month_diff;
340
a64acb40 341 /* Use the system localtime() if time_t is small enough */
342 if( SHOULD_USE_SYSTEM_LOCALTIME(*time) ) {
343 safe_time = *time;
344 localtime_r(&safe_time, local_tm);
af9b2bf5 345 assert(_check_tm(local_tm));
a64acb40 346 return local_tm;
347 }
348
a272e669 349 gmtime64_r(time, &gm_tm);
350 orig_year = gm_tm.tm_year;
351
352 if (gm_tm.tm_year > (2037 - 1900))
353 gm_tm.tm_year = _safe_year(gm_tm.tm_year + 1900) - 1900;
354
9af24521 355 safe_time = TIMEGM(&gm_tm);
a272e669 356 localtime_r(&safe_time, local_tm);
357
358 local_tm->tm_year = orig_year;
359 month_diff = local_tm->tm_mon - gm_tm.tm_mon;
360
361 /* When localtime is Dec 31st previous year and
362 gmtime is Jan 1st next year.
363 */
364 if( month_diff == 11 ) {
365 local_tm->tm_year--;
366 }
367
368 /* When localtime is Jan 1st, next year and
369 gmtime is Dec 31st, previous year.
370 */
371 if( month_diff == -11 ) {
372 local_tm->tm_year++;
373 }
374
375 /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st
376 in a non-leap xx00. There is one point in the cycle
377 we can't account for which the safe xx00 year is a leap
378 year. So we need to correct for Dec 31st comming out as
379 the 366th day of the year.
380 */
381 if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 )
382 local_tm->tm_yday--;
383
af9b2bf5 384 assert(_check_tm(local_tm));
a272e669 385
386 return local_tm;
387}