xref: /openbsd-src/gnu/usr.bin/perl/time64.c (revision e068048151d29f2562a32185e21a8ba885482260)
1b39c5158Smillert /*
2b39c5158Smillert 
3b39c5158Smillert Copyright (c) 2007-2008  Michael G Schwern
4b39c5158Smillert 
5b39c5158Smillert This software originally derived from Paul Sheer's pivotal_gmtime_r.c.
6b39c5158Smillert 
7b39c5158Smillert The MIT License:
8b39c5158Smillert 
9b39c5158Smillert Permission is hereby granted, free of charge, to any person obtaining a copy
10b39c5158Smillert of this software and associated documentation files (the "Software"), to deal
11b39c5158Smillert in the Software without restriction, including without limitation the rights
12b39c5158Smillert to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13b39c5158Smillert copies of the Software, and to permit persons to whom the Software is
14b39c5158Smillert furnished to do so, subject to the following conditions:
15b39c5158Smillert 
16b39c5158Smillert The above copyright notice and this permission notice shall be included in
17b39c5158Smillert all copies or substantial portions of the Software.
18b39c5158Smillert 
19b39c5158Smillert THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20b39c5158Smillert IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21b39c5158Smillert FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22b39c5158Smillert AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23b39c5158Smillert LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24b39c5158Smillert OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25b39c5158Smillert THE SOFTWARE.
26b39c5158Smillert 
27b39c5158Smillert */
28b39c5158Smillert 
29*e0680481Safresh1 
30*e0680481Safresh1 /*
31*e0680481Safresh1  *   This thing all things devours:
32*e0680481Safresh1  *   Birds, beasts, trees, flowers;
33*e0680481Safresh1  *   Gnaws iron, bites steel;
34*e0680481Safresh1  *   Grinds hard stones to meal;
35*e0680481Safresh1  *   Slays king, ruins town,
36*e0680481Safresh1  *   And beats high mountain down."
37*e0680481Safresh1  *
38*e0680481Safresh1  * Poor Bilbo sat in the dark thinking of all the horrible names of all the
39*e0680481Safresh1  * giants and ogres he had ever heard told of in tales, but not one of them had
40*e0680481Safresh1  * done all these things. He had a feeling that the answer was quite different
41*e0680481Safresh1  * and that he ought to know it, but he could not think of it. He began to get
42*e0680481Safresh1  * frightened, and that is bad for thinking. Gollum began to get out of his
43*e0680481Safresh1  * boat. He flapped into the water and paddled to the bank; Bilbo could see his
44*e0680481Safresh1  * eyes coming towards him. His tongue seemed to stick in his mouth; he wanted
45*e0680481Safresh1  * to shout out: "Give me more time! Give me time!" But all that came out with
46*e0680481Safresh1  * a sudden squeal was:
47*e0680481Safresh1  *
48*e0680481Safresh1  * "Time! Time!"
49*e0680481Safresh1  *
50*e0680481Safresh1  * Bilbo was saved by pure luck. For that of course was the answer.
51*e0680481Safresh1  *
52*e0680481Safresh1  *     [p.84 of _The Hobbit_: "Riddles in the Dark"]
53*e0680481Safresh1  *
54*e0680481Safresh1 */
55*e0680481Safresh1 
56b39c5158Smillert /*
57b39c5158Smillert 
58b39c5158Smillert Programmers who have available to them 64-bit time values as a 'long
59b39c5158Smillert long' type can use localtime64_r() and gmtime64_r() which correctly
60b39c5158Smillert converts the time even on 32-bit systems. Whether you have 64-bit time
61b39c5158Smillert values will depend on the operating system.
62b39c5158Smillert 
63b8851fccSafresh1 Perl_localtime64_r() is a 64-bit equivalent of localtime_r().
64b39c5158Smillert 
65b8851fccSafresh1 Perl_gmtime64_r() is a 64-bit equivalent of gmtime_r().
66b39c5158Smillert 
67b39c5158Smillert */
68b39c5158Smillert 
69b8851fccSafresh1 #include "EXTERN.h"
70b8851fccSafresh1 #define PERL_IN_TIME64_C
71b8851fccSafresh1 #include "perl.h"
72b39c5158Smillert #include "time64.h"
73b39c5158Smillert 
746fb12b70Safresh1 static const char days_in_month[2][12] = {
75b39c5158Smillert     {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
76b39c5158Smillert     {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
77b39c5158Smillert };
78b39c5158Smillert 
796fb12b70Safresh1 static const short julian_days_by_month[2][12] = {
80b39c5158Smillert     {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
81b39c5158Smillert     {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335},
82b39c5158Smillert };
83b39c5158Smillert 
846fb12b70Safresh1 static const short length_of_year[2] = { 365, 366 };
85b39c5158Smillert 
86b39c5158Smillert /* Number of days in a 400 year Gregorian cycle */
87b39c5158Smillert static const Year years_in_gregorian_cycle = 400;
88b39c5158Smillert static const int days_in_gregorian_cycle  = (365 * 400) + 100 - 4 + 1;
89b39c5158Smillert 
90b39c5158Smillert /* 28 year calendar cycle between 2010 and 2037 */
91b39c5158Smillert #define SOLAR_CYCLE_LENGTH 28
926fb12b70Safresh1 static const short safe_years[SOLAR_CYCLE_LENGTH] = {
93b39c5158Smillert     2016, 2017, 2018, 2019,
94b39c5158Smillert     2020, 2021, 2022, 2023,
95b39c5158Smillert     2024, 2025, 2026, 2027,
96b39c5158Smillert     2028, 2029, 2030, 2031,
97b39c5158Smillert     2032, 2033, 2034, 2035,
98b39c5158Smillert     2036, 2037, 2010, 2011,
99b39c5158Smillert     2012, 2013, 2014, 2015
100b39c5158Smillert };
101b39c5158Smillert 
102b39c5158Smillert /* Let's assume people are going to be looking for dates in the future.
103b39c5158Smillert    Let's provide some cheats so you can skip ahead.
104b39c5158Smillert    This has a 4x speed boost when near 2008.
105b39c5158Smillert */
106b39c5158Smillert /* Number of days since epoch on Jan 1st, 2008 GMT */
107b39c5158Smillert #define CHEAT_DAYS  (1199145600 / 24 / 60 / 60)
108b39c5158Smillert #define CHEAT_YEARS 108
109b39c5158Smillert 
110b39c5158Smillert #define IS_LEAP(n)	((!(((n) + 1900) % 400) || (!(((n) + 1900) % 4) && (((n) + 1900) % 100))) != 0)
111b8851fccSafresh1 #undef WRAP /* some <termios.h> define this */
112b39c5158Smillert #define WRAP(a,b,m)	((a) = ((a) <  0  ) ? ((b)--, (a) + (m)) : (a))
113b39c5158Smillert 
114b39c5158Smillert #ifdef USE_SYSTEM_LOCALTIME
115b39c5158Smillert #    define SHOULD_USE_SYSTEM_LOCALTIME(a)  (       \
116b39c5158Smillert     (a) <= SYSTEM_LOCALTIME_MAX &&              \
117b39c5158Smillert     (a) >= SYSTEM_LOCALTIME_MIN                 \
118b39c5158Smillert )
119b39c5158Smillert #else
120b39c5158Smillert #    define SHOULD_USE_SYSTEM_LOCALTIME(a)      (0)
121b39c5158Smillert #endif
122b39c5158Smillert 
123b39c5158Smillert #ifdef USE_SYSTEM_GMTIME
124b39c5158Smillert #    define SHOULD_USE_SYSTEM_GMTIME(a)     (       \
125b39c5158Smillert     (a) <= SYSTEM_GMTIME_MAX    &&              \
126b39c5158Smillert     (a) >= SYSTEM_GMTIME_MIN                    \
127b39c5158Smillert )
128b39c5158Smillert #else
129b39c5158Smillert #    define SHOULD_USE_SYSTEM_GMTIME(a)         (0)
130b39c5158Smillert #endif
131b39c5158Smillert 
132b39c5158Smillert /* Multi varadic macros are a C99 thing, alas */
133b39c5158Smillert #ifdef TIME_64_DEBUG
134b39c5158Smillert #    define TIME64_TRACE(format) (fprintf(stderr, format))
135b39c5158Smillert #    define TIME64_TRACE1(format, var1)    (fprintf(stderr, format, var1))
136b39c5158Smillert #    define TIME64_TRACE2(format, var1, var2)    (fprintf(stderr, format, var1, var2))
137b39c5158Smillert #    define TIME64_TRACE3(format, var1, var2, var3)    (fprintf(stderr, format, var1, var2, var3))
138b39c5158Smillert #else
139b39c5158Smillert #    define TIME64_TRACE(format) ((void)0)
140b39c5158Smillert #    define TIME64_TRACE1(format, var1) ((void)0)
141b39c5158Smillert #    define TIME64_TRACE2(format, var1, var2) ((void)0)
142b39c5158Smillert #    define TIME64_TRACE3(format, var1, var2, var3) ((void)0)
143b39c5158Smillert #endif
144b39c5158Smillert 
S_is_exception_century(Year year)145b39c5158Smillert static int S_is_exception_century(Year year)
146b39c5158Smillert {
1479f11ffb7Safresh1     const int is_exception = ((year % 100 == 0) && !(year % 400 == 0));
148b39c5158Smillert     TIME64_TRACE1("# is_exception_century: %s\n", is_exception ? "yes" : "no");
149b39c5158Smillert 
150b39c5158Smillert     return(is_exception);
151b39c5158Smillert }
152b39c5158Smillert 
153b39c5158Smillert 
S_timegm64(const struct TM * date)1549f11ffb7Safresh1 static Time64_T S_timegm64(const struct TM *date) {
155b39c5158Smillert     int      days    = 0;
156b39c5158Smillert     Time64_T seconds = 0;
157b39c5158Smillert 
158b39c5158Smillert     if( date->tm_year > 70 ) {
1599f11ffb7Safresh1         Year year = 70;
160b39c5158Smillert         while( year < date->tm_year ) {
161b39c5158Smillert             days += length_of_year[IS_LEAP(year)];
162b39c5158Smillert             year++;
163b39c5158Smillert         }
164b39c5158Smillert     }
165b39c5158Smillert     else if ( date->tm_year < 70 ) {
1669f11ffb7Safresh1         Year year = 69;
167b39c5158Smillert         do {
168b39c5158Smillert             days -= length_of_year[IS_LEAP(year)];
169b39c5158Smillert             year--;
170b39c5158Smillert         } while( year >= date->tm_year );
171b39c5158Smillert     }
172b39c5158Smillert 
173b39c5158Smillert     days += julian_days_by_month[IS_LEAP(date->tm_year)][date->tm_mon];
174b39c5158Smillert     days += date->tm_mday - 1;
175b39c5158Smillert 
176b39c5158Smillert     /* Avoid overflowing the days integer */
177b39c5158Smillert     seconds = days;
178b39c5158Smillert     seconds = seconds * 60 * 60 * 24;
179b39c5158Smillert 
180b39c5158Smillert     seconds += date->tm_hour * 60 * 60;
181b39c5158Smillert     seconds += date->tm_min * 60;
182b39c5158Smillert     seconds += date->tm_sec;
183b39c5158Smillert 
184b39c5158Smillert     return(seconds);
185b39c5158Smillert }
186b39c5158Smillert 
187b39c5158Smillert 
188b39c5158Smillert #ifdef DEBUGGING
S_check_tm(const struct TM * tm)1899f11ffb7Safresh1 static int S_check_tm(const struct TM *tm)
190b39c5158Smillert {
191b39c5158Smillert     /* Don't forget leap seconds */
192b39c5158Smillert     assert(tm->tm_sec >= 0);
193b39c5158Smillert     assert(tm->tm_sec <= 61);
194b39c5158Smillert 
195b39c5158Smillert     assert(tm->tm_min >= 0);
196b39c5158Smillert     assert(tm->tm_min <= 59);
197b39c5158Smillert 
198b39c5158Smillert     assert(tm->tm_hour >= 0);
199b39c5158Smillert     assert(tm->tm_hour <= 23);
200b39c5158Smillert 
201b39c5158Smillert     assert(tm->tm_mday >= 1);
202b39c5158Smillert     assert(tm->tm_mday <= days_in_month[IS_LEAP(tm->tm_year)][tm->tm_mon]);
203b39c5158Smillert 
204b39c5158Smillert     assert(tm->tm_mon  >= 0);
205b39c5158Smillert     assert(tm->tm_mon  <= 11);
206b39c5158Smillert 
207b39c5158Smillert     assert(tm->tm_wday >= 0);
208b39c5158Smillert     assert(tm->tm_wday <= 6);
209b39c5158Smillert 
210b39c5158Smillert     assert(tm->tm_yday >= 0);
211b39c5158Smillert     assert(tm->tm_yday <= length_of_year[IS_LEAP(tm->tm_year)]);
212b39c5158Smillert 
213b39c5158Smillert #ifdef HAS_TM_TM_GMTOFF
214b39c5158Smillert     assert(tm->tm_gmtoff >= -24 * 60 * 60);
215b39c5158Smillert     assert(tm->tm_gmtoff <=  24 * 60 * 60);
216b39c5158Smillert #endif
217b39c5158Smillert 
218b39c5158Smillert     return 1;
219b39c5158Smillert }
220b39c5158Smillert #endif
221b39c5158Smillert 
222b39c5158Smillert 
223b39c5158Smillert /* The exceptional centuries without leap years cause the cycle to
224b39c5158Smillert    shift by 16
225b39c5158Smillert */
S_cycle_offset(Year year)226b39c5158Smillert static Year S_cycle_offset(Year year)
227b39c5158Smillert {
228b39c5158Smillert     const Year start_year = 2000;
229b39c5158Smillert     Year year_diff  = year - start_year;
230b39c5158Smillert     Year exceptions;
231b39c5158Smillert 
232b39c5158Smillert     if( year > start_year )
233b39c5158Smillert         year_diff--;
234b39c5158Smillert 
235b39c5158Smillert     exceptions  = year_diff / 100;
236b39c5158Smillert     exceptions -= year_diff / 400;
237b39c5158Smillert 
238b39c5158Smillert     TIME64_TRACE3("# year: %lld, exceptions: %lld, year_diff: %lld\n",
239b39c5158Smillert           year, exceptions, year_diff);
240b39c5158Smillert 
241b39c5158Smillert     return exceptions * 16;
242b39c5158Smillert }
243b39c5158Smillert 
244b39c5158Smillert /* For a given year after 2038, pick the latest possible matching
245b39c5158Smillert    year in the 28 year calendar cycle.
246b39c5158Smillert 
247b39c5158Smillert    A matching year...
248b39c5158Smillert    1) Starts on the same day of the week.
249b39c5158Smillert    2) Has the same leap year status.
250b39c5158Smillert 
251b39c5158Smillert    This is so the calendars match up.
252b39c5158Smillert 
253b39c5158Smillert    Also the previous year must match.  When doing Jan 1st you might
254b39c5158Smillert    wind up on Dec 31st the previous year when doing a -UTC time zone.
255b39c5158Smillert 
256b39c5158Smillert    Finally, the next year must have the same start day of week.  This
257b39c5158Smillert    is for Dec 31st with a +UTC time zone.
258b39c5158Smillert    It doesn't need the same leap year status since we only care about
259b39c5158Smillert    January 1st.
260b39c5158Smillert */
S_safe_year(Year year)261b39c5158Smillert static int S_safe_year(Year year)
262b39c5158Smillert {
263b39c5158Smillert     int safe_year;
264b39c5158Smillert     Year year_cycle = year + S_cycle_offset(year);
265b39c5158Smillert 
266b39c5158Smillert     /* Change non-leap xx00 years to an equivalent */
267b39c5158Smillert     if( S_is_exception_century(year) )
268b39c5158Smillert         year_cycle += 11;
269b39c5158Smillert 
270b39c5158Smillert     /* Also xx01 years, since the previous year will be wrong */
271b39c5158Smillert     if( S_is_exception_century(year - 1) )
272b39c5158Smillert         year_cycle += 17;
273b39c5158Smillert 
274b39c5158Smillert     year_cycle %= SOLAR_CYCLE_LENGTH;
275b39c5158Smillert     if( year_cycle < 0 )
276b39c5158Smillert         year_cycle = SOLAR_CYCLE_LENGTH + year_cycle;
277b39c5158Smillert 
278b39c5158Smillert     assert( year_cycle >= 0 );
279b39c5158Smillert     assert( year_cycle < SOLAR_CYCLE_LENGTH );
280b39c5158Smillert     safe_year = safe_years[year_cycle];
281b39c5158Smillert 
282b39c5158Smillert     assert(safe_year <= 2037 && safe_year >= 2010);
283b39c5158Smillert 
284b39c5158Smillert     TIME64_TRACE3("# year: %lld, year_cycle: %lld, safe_year: %d\n",
285b39c5158Smillert           year, year_cycle, safe_year);
286b39c5158Smillert 
287b39c5158Smillert     return safe_year;
288b39c5158Smillert }
289b39c5158Smillert 
290b39c5158Smillert 
S_copy_little_tm_to_big_TM(const struct tm * src,struct TM * dest)291b39c5158Smillert static void S_copy_little_tm_to_big_TM(const struct tm *src, struct TM *dest) {
29291f110e0Safresh1     assert(src);
29391f110e0Safresh1     assert(dest);
294b39c5158Smillert #ifdef USE_TM64
295b39c5158Smillert     dest->tm_sec        = src->tm_sec;
296b39c5158Smillert     dest->tm_min        = src->tm_min;
297b39c5158Smillert     dest->tm_hour       = src->tm_hour;
298b39c5158Smillert     dest->tm_mday       = src->tm_mday;
299b39c5158Smillert     dest->tm_mon        = src->tm_mon;
300b39c5158Smillert     dest->tm_year       = (Year)src->tm_year;
301b39c5158Smillert     dest->tm_wday       = src->tm_wday;
302b39c5158Smillert     dest->tm_yday       = src->tm_yday;
303b39c5158Smillert     dest->tm_isdst      = src->tm_isdst;
304b39c5158Smillert 
305b39c5158Smillert #  ifdef HAS_TM_TM_GMTOFF
306b39c5158Smillert     dest->tm_gmtoff     = src->tm_gmtoff;
307b39c5158Smillert #  endif
308b39c5158Smillert 
309b39c5158Smillert #  ifdef HAS_TM_TM_ZONE
310b39c5158Smillert     dest->tm_zone       = src->tm_zone;
311b39c5158Smillert #  endif
312b39c5158Smillert 
313b39c5158Smillert #else
314b39c5158Smillert     /* They're the same type */
315b39c5158Smillert     memcpy(dest, src, sizeof(*dest));
316b39c5158Smillert #endif
317b39c5158Smillert }
318b39c5158Smillert 
Perl_gmtime64_r(const Time64_T * in_time,struct TM * p)319b8851fccSafresh1 struct TM *Perl_gmtime64_r (const Time64_T *in_time, struct TM *p)
320b39c5158Smillert {
321b39c5158Smillert     int v_tm_sec, v_tm_min, v_tm_hour, v_tm_mon, v_tm_wday;
322b39c5158Smillert     Time64_T v_tm_tday;
323b39c5158Smillert     int leap;
324b39c5158Smillert     Time64_T m;
325b39c5158Smillert     Time64_T time = *in_time;
326b39c5158Smillert     Year year = 70;
32756d68f1eSafresh1     dTHX;
328b39c5158Smillert 
329b39c5158Smillert     assert(p != NULL);
330b39c5158Smillert 
331b39c5158Smillert     /* Use the system gmtime() if time_t is small enough */
332b39c5158Smillert     if( SHOULD_USE_SYSTEM_GMTIME(*in_time) ) {
333b39c5158Smillert         time_t safe_time = (time_t)*in_time;
334b39c5158Smillert         struct tm safe_date;
33556d68f1eSafresh1         struct tm * result;
336b39c5158Smillert 
33756d68f1eSafresh1         GMTIME_LOCK;
33856d68f1eSafresh1 
33956d68f1eSafresh1         /* reentr.h will automatically replace this with a call to gmtime_r()
34056d68f1eSafresh1          * when appropriate */
34156d68f1eSafresh1         result = gmtime(&safe_time);
34256d68f1eSafresh1 
34356d68f1eSafresh1         assert(result != NULL);
34456d68f1eSafresh1 
34556d68f1eSafresh1 #if defined(HAS_GMTIME_R) && defined(USE_REENTRANT_API)
34656d68f1eSafresh1 
34756d68f1eSafresh1         PERL_UNUSED_VAR(safe_date);
34856d68f1eSafresh1 #else
34956d68f1eSafresh1         /* Here, no gmtime_r() and is a threaded perl where the result can be
35056d68f1eSafresh1          * overwritten by a call in another thread.  Copy to a safe place,
35156d68f1eSafresh1          * hopefully before another gmtime that isn't using the mutexes can
35256d68f1eSafresh1          * jump in and trash this result. */
35356d68f1eSafresh1         memcpy(&safe_date, result, sizeof(safe_date));
35456d68f1eSafresh1         result = &safe_date;
35556d68f1eSafresh1 #endif
35656d68f1eSafresh1         GMTIME_UNLOCK;
35756d68f1eSafresh1 
35856d68f1eSafresh1         S_copy_little_tm_to_big_TM(result, p);
359b39c5158Smillert         assert(S_check_tm(p));
360b39c5158Smillert 
361b39c5158Smillert         return p;
362b39c5158Smillert     }
363b39c5158Smillert 
364b39c5158Smillert #ifdef HAS_TM_TM_GMTOFF
365b39c5158Smillert     p->tm_gmtoff = 0;
366b39c5158Smillert #endif
367b39c5158Smillert     p->tm_isdst  = 0;
368b39c5158Smillert 
369b39c5158Smillert #ifdef HAS_TM_TM_ZONE
370eac174f2Safresh1     p->tm_zone   = "UTC";
371b39c5158Smillert #endif
372b39c5158Smillert 
373b8851fccSafresh1     v_tm_sec  = (int)Perl_fmod(time, 60.0);
374b8851fccSafresh1     time      = time >= 0 ? Perl_floor(time / 60.0) : Perl_ceil(time / 60.0);
375b8851fccSafresh1     v_tm_min  = (int)Perl_fmod(time, 60.0);
376b8851fccSafresh1     time      = time >= 0 ? Perl_floor(time / 60.0) : Perl_ceil(time / 60.0);
377b8851fccSafresh1     v_tm_hour = (int)Perl_fmod(time, 24.0);
378b8851fccSafresh1     time      = time >= 0 ? Perl_floor(time / 24.0) : Perl_ceil(time / 24.0);
379b39c5158Smillert     v_tm_tday = time;
380b39c5158Smillert 
381b39c5158Smillert     WRAP (v_tm_sec, v_tm_min, 60);
382b39c5158Smillert     WRAP (v_tm_min, v_tm_hour, 60);
383b39c5158Smillert     WRAP (v_tm_hour, v_tm_tday, 24);
384b39c5158Smillert 
385b8851fccSafresh1     v_tm_wday = (int)Perl_fmod((v_tm_tday + 4.0), 7.0);
386b39c5158Smillert     if (v_tm_wday < 0)
387b39c5158Smillert         v_tm_wday += 7;
388b39c5158Smillert     m = v_tm_tday;
389b39c5158Smillert 
390b39c5158Smillert     if (m >= CHEAT_DAYS) {
391b39c5158Smillert         year = CHEAT_YEARS;
392b39c5158Smillert         m -= CHEAT_DAYS;
393b39c5158Smillert     }
394b39c5158Smillert 
395b39c5158Smillert     if (m >= 0) {
396b39c5158Smillert         /* Gregorian cycles, this is huge optimization for distant times */
3979f11ffb7Safresh1         const int cycles = (int)Perl_floor(m / (Time64_T) days_in_gregorian_cycle);
398b39c5158Smillert         if( cycles ) {
399b39c5158Smillert             m -= (cycles * (Time64_T) days_in_gregorian_cycle);
400b39c5158Smillert             year += (cycles * years_in_gregorian_cycle);
401b39c5158Smillert         }
402b39c5158Smillert 
403b39c5158Smillert         /* Years */
404b39c5158Smillert         leap = IS_LEAP (year);
405b39c5158Smillert         while (m >= (Time64_T) length_of_year[leap]) {
406b39c5158Smillert             m -= (Time64_T) length_of_year[leap];
407b39c5158Smillert             year++;
408b39c5158Smillert             leap = IS_LEAP (year);
409b39c5158Smillert         }
410b39c5158Smillert 
411b39c5158Smillert         /* Months */
412b39c5158Smillert         v_tm_mon = 0;
413b39c5158Smillert         while (m >= (Time64_T) days_in_month[leap][v_tm_mon]) {
414b39c5158Smillert             m -= (Time64_T) days_in_month[leap][v_tm_mon];
415b39c5158Smillert             v_tm_mon++;
416b39c5158Smillert         }
417b39c5158Smillert     } else {
4189f11ffb7Safresh1         int cycles;
4199f11ffb7Safresh1 
420b39c5158Smillert         year--;
421b39c5158Smillert 
422b39c5158Smillert         /* Gregorian cycles */
423b8851fccSafresh1         cycles = (int)Perl_ceil((m / (Time64_T) days_in_gregorian_cycle) + 1);
424b39c5158Smillert         if( cycles ) {
425b39c5158Smillert             m -= (cycles * (Time64_T) days_in_gregorian_cycle);
426b39c5158Smillert             year += (cycles * years_in_gregorian_cycle);
427b39c5158Smillert         }
428b39c5158Smillert 
429b39c5158Smillert         /* Years */
430b39c5158Smillert         leap = IS_LEAP (year);
431b39c5158Smillert         while (m < (Time64_T) -length_of_year[leap]) {
432b39c5158Smillert             m += (Time64_T) length_of_year[leap];
433b39c5158Smillert             year--;
434b39c5158Smillert             leap = IS_LEAP (year);
435b39c5158Smillert         }
436b39c5158Smillert 
437b39c5158Smillert         /* Months */
438b39c5158Smillert         v_tm_mon = 11;
439b39c5158Smillert         while (m < (Time64_T) -days_in_month[leap][v_tm_mon]) {
440b39c5158Smillert             m += (Time64_T) days_in_month[leap][v_tm_mon];
441b39c5158Smillert             v_tm_mon--;
442b39c5158Smillert         }
443b39c5158Smillert         m += (Time64_T) days_in_month[leap][v_tm_mon];
444b39c5158Smillert     }
445b39c5158Smillert 
446b39c5158Smillert     p->tm_year = year;
447b39c5158Smillert     if( p->tm_year != year ) {
448b39c5158Smillert #ifdef EOVERFLOW
449b39c5158Smillert         errno = EOVERFLOW;
450b39c5158Smillert #endif
451b39c5158Smillert         return NULL;
452b39c5158Smillert     }
453b39c5158Smillert 
454b39c5158Smillert     /* At this point m is less than a year so casting to an int is safe */
455b39c5158Smillert     p->tm_mday = (int) m + 1;
456b39c5158Smillert     p->tm_yday = julian_days_by_month[leap][v_tm_mon] + (int)m;
457b39c5158Smillert     p->tm_sec  = v_tm_sec;
458b39c5158Smillert     p->tm_min  = v_tm_min;
459b39c5158Smillert     p->tm_hour = v_tm_hour;
460b39c5158Smillert     p->tm_mon  = v_tm_mon;
461b39c5158Smillert     p->tm_wday = v_tm_wday;
462b39c5158Smillert 
463b39c5158Smillert     assert(S_check_tm(p));
464b39c5158Smillert 
465b39c5158Smillert     return p;
466b39c5158Smillert }
467b39c5158Smillert 
468b39c5158Smillert 
Perl_localtime64_r(const Time64_T * time,struct TM * local_tm)469b8851fccSafresh1 struct TM *Perl_localtime64_r (const Time64_T *time, struct TM *local_tm)
470b39c5158Smillert {
471b39c5158Smillert     time_t safe_time;
472b39c5158Smillert     struct tm safe_date;
47356d68f1eSafresh1     const struct tm * result;
474b39c5158Smillert     struct TM gm_tm;
475eac174f2Safresh1     Year orig_year = 0; /* initialise to avoid spurious compiler warning */
476b39c5158Smillert     int month_diff;
47756d68f1eSafresh1     const bool use_system = SHOULD_USE_SYSTEM_LOCALTIME(*time);
47856d68f1eSafresh1     dTHX;
479b39c5158Smillert 
480b39c5158Smillert     assert(local_tm != NULL);
481b39c5158Smillert 
482b39c5158Smillert     /* Use the system localtime() if time_t is small enough */
48356d68f1eSafresh1     if (use_system) {
484b39c5158Smillert         safe_time = (time_t)*time;
485b39c5158Smillert 
486b39c5158Smillert         TIME64_TRACE1("Using system localtime for %lld\n", *time);
487b39c5158Smillert     }
48856d68f1eSafresh1     else {
489b8851fccSafresh1         if (Perl_gmtime64_r(time, &gm_tm) == NULL) {
490b39c5158Smillert             TIME64_TRACE1("gmtime64_r returned null for %lld\n", *time);
491b39c5158Smillert             return NULL;
492b39c5158Smillert         }
493b39c5158Smillert 
494b39c5158Smillert         orig_year = gm_tm.tm_year;
495b39c5158Smillert 
496b39c5158Smillert         if (gm_tm.tm_year > (2037 - 1900) ||
497b39c5158Smillert             gm_tm.tm_year < (1970 - 1900)
498b39c5158Smillert            )
499b39c5158Smillert         {
50056d68f1eSafresh1             TIME64_TRACE1("Mapping tm_year %lld to safe_year\n",
50156d68f1eSafresh1                                                         (Year)gm_tm.tm_year);
502b39c5158Smillert             gm_tm.tm_year = S_safe_year((Year)(gm_tm.tm_year + 1900)) - 1900;
503b39c5158Smillert         }
504b39c5158Smillert 
505b39c5158Smillert         safe_time = (time_t)S_timegm64(&gm_tm);
50656d68f1eSafresh1     }
50756d68f1eSafresh1 
50856d68f1eSafresh1     LOCALTIME_LOCK;
50956d68f1eSafresh1 
51056d68f1eSafresh1     /* reentr.h will automatically replace this with a call to localtime_r()
51156d68f1eSafresh1      * when appropriate */
51256d68f1eSafresh1     result = localtime(&safe_time);
51356d68f1eSafresh1 
51456d68f1eSafresh1     if(UNLIKELY(result == NULL)) {
51556d68f1eSafresh1         LOCALTIME_UNLOCK;
51656d68f1eSafresh1         TIME64_TRACE1("localtime(%d) returned NULL\n", (int)safe_time);
517b39c5158Smillert         return NULL;
518b39c5158Smillert     }
519b39c5158Smillert 
52056d68f1eSafresh1 #if ! defined(USE_REENTRANT_API) || defined(PERL_REENTR_USING_LOCALTIME_R)
52156d68f1eSafresh1 
52256d68f1eSafresh1     PERL_UNUSED_VAR(safe_date);
52356d68f1eSafresh1 
52456d68f1eSafresh1 #else
52556d68f1eSafresh1 
52656d68f1eSafresh1     /* Here, would be using localtime_r() if it could, meaning there isn't one,
52756d68f1eSafresh1      * and is a threaded perl where the result can be overwritten by a call in
52856d68f1eSafresh1      * another thread.  Copy to a safe place, hopefully before another
52956d68f1eSafresh1      * localtime that isn't using the mutexes can jump in and trash this
53056d68f1eSafresh1      * result. */
53156d68f1eSafresh1     memcpy(&safe_date, result, sizeof(safe_date));
53256d68f1eSafresh1     result = &safe_date;
53356d68f1eSafresh1 
53456d68f1eSafresh1 #endif
53556d68f1eSafresh1 
53656d68f1eSafresh1     LOCALTIME_UNLOCK;
53756d68f1eSafresh1 
53856d68f1eSafresh1     S_copy_little_tm_to_big_TM(result, local_tm);
53956d68f1eSafresh1 
54056d68f1eSafresh1     if (! use_system) {
541b39c5158Smillert 
542b39c5158Smillert         local_tm->tm_year = orig_year;
543b39c5158Smillert         if( local_tm->tm_year != orig_year ) {
544b39c5158Smillert             TIME64_TRACE2("tm_year overflow: tm_year %lld, orig_year %lld\n",
545b39c5158Smillert                   (Year)local_tm->tm_year, (Year)orig_year);
546b39c5158Smillert 
547b39c5158Smillert #ifdef EOVERFLOW
548b39c5158Smillert             errno = EOVERFLOW;
549b39c5158Smillert #endif
550b39c5158Smillert             return NULL;
551b39c5158Smillert         }
552b39c5158Smillert 
553b39c5158Smillert         month_diff = local_tm->tm_mon - gm_tm.tm_mon;
554b39c5158Smillert 
555b39c5158Smillert         /*  When localtime is Dec 31st previous year and
556b39c5158Smillert             gmtime is Jan 1st next year.
557b39c5158Smillert         */
558b39c5158Smillert         if( month_diff == 11 ) {
559b39c5158Smillert             local_tm->tm_year--;
560b39c5158Smillert         }
561b39c5158Smillert 
562b39c5158Smillert         /*  When localtime is Jan 1st, next year and
563b39c5158Smillert             gmtime is Dec 31st, previous year.
564b39c5158Smillert         */
565b39c5158Smillert         if( month_diff == -11 ) {
566b39c5158Smillert             local_tm->tm_year++;
567b39c5158Smillert         }
568b39c5158Smillert 
569b39c5158Smillert         /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st
570b39c5158Smillert            in a non-leap xx00.  There is one point in the cycle
571b39c5158Smillert            we can't account for which the safe xx00 year is a leap
572898184e3Ssthen            year.  So we need to correct for Dec 31st coming out as
573b39c5158Smillert            the 366th day of the year.
574b39c5158Smillert         */
575b39c5158Smillert         if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 )
576b39c5158Smillert             local_tm->tm_yday--;
577b39c5158Smillert 
57856d68f1eSafresh1     }
57956d68f1eSafresh1 
580b39c5158Smillert     assert(S_check_tm(local_tm));
581b39c5158Smillert 
582b39c5158Smillert     return local_tm;
583b39c5158Smillert }
584