This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Patch in a 64 bit clean gmtime_r() and localtime_r() from the y2038 project. http...
[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
86
87#define IS_LEAP(n) ((!(((n) + 1900) % 400) || (!(((n) + 1900) % 4) && (((n) + 1900) % 100))) != 0)
88#define WRAP(a,b,m) ((a) = ((a) < 0 ) ? ((b)--, (a) + (m)) : (a))
89
90int _is_exception_century(long year)
91{
92 int is_exception = ((year % 100 == 0) && !(year % 400 == 0));
93 /* printf("is_exception_century: %s\n", is_exception ? "yes" : "no"); */
94
95 return(is_exception);
96}
97
98void _check_tm(struct tm *tm)
99{
100 /* Don't forget leap seconds */
101 assert(tm->tm_sec >= 0 && tm->tm_sec <= 61);
102 assert(tm->tm_min >= 0 && tm->tm_min <= 59);
103 assert(tm->tm_hour >= 0 && tm->tm_hour <= 23);
104 assert(tm->tm_mday >= 1 && tm->tm_mday <= 31);
105 assert(tm->tm_mon >= 0 && tm->tm_mon <= 11);
106 assert(tm->tm_wday >= 0 && tm->tm_wday <= 6);
107 assert(tm->tm_yday >= 0 && tm->tm_yday <= 365);
108
109#ifdef TM_HAS_GMTOFF
110 assert( tm->tm_gmtoff >= -24 * 60 * 60
111 && tm->tm_gmtoff <= 24 * 60 * 60);
112#endif
113
114 if( !IS_LEAP(tm->tm_year) ) {
115 /* no more than 365 days in a non_leap year */
116 assert( tm->tm_yday <= 364 );
117
118 /* and no more than 28 days in Feb */
119 if( tm->tm_mon == 1 ) {
120 assert( tm->tm_mday <= 28 );
121 }
122 }
123}
124
125/* The exceptional centuries without leap years cause the cycle to
126 shift by 16
127*/
128int _cycle_offset(long year)
129{
130 const long start_year = 2000;
131 long year_diff = year - start_year - 1;
132 long exceptions = year_diff / 100;
133 exceptions -= year_diff / 400;
134
135 assert( year >= 2001 );
136
137 /* printf("year: %d, exceptions: %d\n", year, exceptions); */
138
139 return exceptions * 16;
140}
141
142/* For a given year after 2038, pick the latest possible matching
143 year in the 28 year calendar cycle.
144*/
145#define SOLAR_CYCLE_LENGTH 28
146int _safe_year(long year)
147{
148 int safe_year;
149 long year_cycle = year + _cycle_offset(year);
150
151 /* Change non-leap xx00 years to an equivalent */
152 if( _is_exception_century(year) )
153 year_cycle += 11;
154
155 year_cycle %= SOLAR_CYCLE_LENGTH;
156
157 safe_year = safe_years[year_cycle];
158
159 assert(safe_year <= 2037 && safe_year >= 2010);
160
161 /*
162 printf("year: %d, year_cycle: %d, safe_year: %d\n",
163 year, year_cycle, safe_year);
164 */
165
166 return safe_year;
167}
168
169struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p)
170{
171 int v_tm_sec, v_tm_min, v_tm_hour, v_tm_mon, v_tm_wday;
172 Time64_T v_tm_tday;
173 int leap;
174 Time64_T m;
175 Time64_T time = *in_time;
176 Time64_T year;
177
178#ifdef TM_HAS_GMTOFF
179 p->tm_gmtoff = 0;
180#endif
181 p->tm_isdst = 0;
182
183#ifdef TM_HAS_ZONE
184 p->tm_zone = "UTC";
185#endif
186
187 v_tm_sec = time % 60;
188 time /= 60;
189 v_tm_min = time % 60;
190 time /= 60;
191 v_tm_hour = time % 24;
192 time /= 24;
193 v_tm_tday = time;
194 WRAP (v_tm_sec, v_tm_min, 60);
195 WRAP (v_tm_min, v_tm_hour, 60);
196 WRAP (v_tm_hour, v_tm_tday, 24);
197 if ((v_tm_wday = (v_tm_tday + 4) % 7) < 0)
198 v_tm_wday += 7;
199 m = v_tm_tday;
200 if (m >= 0) {
201 year = 70;
202
203 /* Gregorian cycles, this is huge optimization for distant times */
204 while (m >= (Time64_T) days_in_gregorian_cycle) {
205 m -= (Time64_T) days_in_gregorian_cycle;
206 year += years_in_gregorian_cycle;
207 }
208
209 /* Years */
210 leap = IS_LEAP (year);
211 while (m >= (Time64_T) length_of_year[leap]) {
212 m -= (Time64_T) length_of_year[leap];
213 year++;
214 leap = IS_LEAP (year);
215 }
216
217 /* Months */
218 v_tm_mon = 0;
219 while (m >= (Time64_T) days_in_month[leap][v_tm_mon]) {
220 m -= (Time64_T) days_in_month[leap][v_tm_mon];
221 v_tm_mon++;
222 }
223 } else {
224 year = 69;
225
226 /* Gregorian cycles */
227 while (m < (Time64_T) -days_in_gregorian_cycle) {
228 m += (Time64_T) days_in_gregorian_cycle;
229 year -= years_in_gregorian_cycle;
230 }
231
232 /* Years */
233 leap = IS_LEAP (year);
234 while (m < (Time64_T) -length_of_year[leap]) {
235 m += (Time64_T) length_of_year[leap];
236 year--;
237 leap = IS_LEAP (year);
238 }
239
240 /* Months */
241 v_tm_mon = 11;
242 while (m < (Time64_T) -days_in_month[leap][v_tm_mon]) {
243 m += (Time64_T) days_in_month[leap][v_tm_mon];
244 v_tm_mon--;
245 }
246 m += (Time64_T) days_in_month[leap][v_tm_mon];
247 }
248
249 p->tm_year = year;
250 if( p->tm_year != year ) {
251 errno = EOVERFLOW;
252 return NULL;
253 }
254
255 p->tm_mday = (int) m + 1;
256 p->tm_yday = julian_days_by_month[leap][v_tm_mon] + m;
257 p->tm_sec = v_tm_sec, p->tm_min = v_tm_min, p->tm_hour = v_tm_hour,
258 p->tm_mon = v_tm_mon, p->tm_wday = v_tm_wday;
259
260 _check_tm(p);
261
262 return p;
263}
264
265
266struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm)
267{
268 time_t safe_time;
269 struct tm gm_tm;
270 long orig_year;
271 int month_diff;
272
273 gmtime64_r(time, &gm_tm);
274 orig_year = gm_tm.tm_year;
275
276 if (gm_tm.tm_year > (2037 - 1900))
277 gm_tm.tm_year = _safe_year(gm_tm.tm_year + 1900) - 1900;
278
279 safe_time = timegm(&gm_tm);
280 localtime_r(&safe_time, local_tm);
281
282 local_tm->tm_year = orig_year;
283 month_diff = local_tm->tm_mon - gm_tm.tm_mon;
284
285 /* When localtime is Dec 31st previous year and
286 gmtime is Jan 1st next year.
287 */
288 if( month_diff == 11 ) {
289 local_tm->tm_year--;
290 }
291
292 /* When localtime is Jan 1st, next year and
293 gmtime is Dec 31st, previous year.
294 */
295 if( month_diff == -11 ) {
296 local_tm->tm_year++;
297 }
298
299 /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st
300 in a non-leap xx00. There is one point in the cycle
301 we can't account for which the safe xx00 year is a leap
302 year. So we need to correct for Dec 31st comming out as
303 the 366th day of the year.
304 */
305 if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 )
306 local_tm->tm_yday--;
307
308 _check_tm(local_tm);
309
310 return local_tm;
311}