1 | /* |
2 | * Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com> |
3 | * All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions are met: |
7 | * |
8 | * * Redistributions of source code must retain the above copyright notice, |
9 | * this list of conditions and the following disclaimer. |
10 | * * Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * * Neither the name of Redis nor the names of its contributors may be used |
14 | * to endorse or promote products derived from this software without |
15 | * specific prior written permission. |
16 | * |
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
27 | * POSSIBILITY OF SUCH DAMAGE. |
28 | */ |
29 | |
30 | #include <time.h> |
31 | |
32 | /* This is a safe version of localtime() which contains no locks and is |
33 | * fork() friendly. Even the _r version of localtime() cannot be used safely |
34 | * in Redis. Another thread may be calling localtime() while the main thread |
35 | * forks(). Later when the child process calls localtime() again, for instance |
36 | * in order to log something to the Redis log, it may deadlock: in the copy |
37 | * of the address space of the forked process the lock will never be released. |
38 | * |
39 | * This function takes the timezone 'tz' as argument, and the 'dst' flag is |
40 | * used to check if daylight saving time is currently in effect. The caller |
41 | * of this function should obtain such information calling tzset() ASAP in the |
42 | * main() function to obtain the timezone offset from the 'timezone' global |
43 | * variable. To obtain the daylight information, if it is currently active or not, |
44 | * one trick is to call localtime() in main() ASAP as well, and get the |
45 | * information from the tm_isdst field of the tm structure. However the daylight |
46 | * time may switch in the future for long running processes, so this information |
47 | * should be refreshed at safe times. |
48 | * |
49 | * Note that this function does not work for dates < 1/1/1970, it is solely |
50 | * designed to work with what time(NULL) may return, and to support Redis |
51 | * logging of the dates, it's not really a complete implementation. */ |
52 | static int is_leap_year(time_t year) { |
53 | if (year % 4) return 0; /* A year not divisible by 4 is not leap. */ |
54 | else if (year % 100) return 1; /* If div by 4 and not 100 is surely leap. */ |
55 | else if (year % 400) return 0; /* If div by 100 *and* not by 400 is not leap. */ |
56 | else return 1; /* If div by 100 and 400 is leap. */ |
57 | } |
58 | |
59 | void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst) { |
60 | const time_t secs_min = 60; |
61 | const time_t secs_hour = 3600; |
62 | const time_t secs_day = 3600*24; |
63 | |
64 | t -= tz; /* Adjust for timezone. */ |
65 | t += 3600*dst; /* Adjust for daylight time. */ |
66 | time_t days = t / secs_day; /* Days passed since epoch. */ |
67 | time_t seconds = t % secs_day; /* Remaining seconds. */ |
68 | |
69 | tmp->tm_isdst = dst; |
70 | tmp->tm_hour = seconds / secs_hour; |
71 | tmp->tm_min = (seconds % secs_hour) / secs_min; |
72 | tmp->tm_sec = (seconds % secs_hour) % secs_min; |
73 | |
74 | /* 1/1/1970 was a Thursday, that is, day 4 from the POV of the tm structure |
75 | * where sunday = 0, so to calculate the day of the week we have to add 4 |
76 | * and take the modulo by 7. */ |
77 | tmp->tm_wday = (days+4)%7; |
78 | |
79 | /* Calculate the current year. */ |
80 | tmp->tm_year = 1970; |
81 | while(1) { |
82 | /* Leap years have one day more. */ |
83 | time_t days_this_year = 365 + is_leap_year(tmp->tm_year); |
84 | if (days_this_year > days) break; |
85 | days -= days_this_year; |
86 | tmp->tm_year++; |
87 | } |
88 | tmp->tm_yday = days; /* Number of day of the current year. */ |
89 | |
90 | /* We need to calculate in which month and day of the month we are. To do |
91 | * so we need to skip days according to how many days there are in each |
92 | * month, and adjust for the leap year that has one more day in February. */ |
93 | int mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; |
94 | mdays[1] += is_leap_year(tmp->tm_year); |
95 | |
96 | tmp->tm_mon = 0; |
97 | while(days >= mdays[tmp->tm_mon]) { |
98 | days -= mdays[tmp->tm_mon]; |
99 | tmp->tm_mon++; |
100 | } |
101 | |
102 | tmp->tm_mday = days+1; /* Add 1 since our 'days' is zero-based. */ |
103 | tmp->tm_year -= 1900; /* Surprisingly tm_year is year-1900. */ |
104 | } |
105 | |
106 | #ifdef LOCALTIME_TEST_MAIN |
107 | #include <stdio.h> |
108 | |
109 | int main(void) { |
110 | /* Obtain timezone and daylight info. */ |
111 | tzset(); /* Now 'timezone' global is populated. */ |
112 | time_t t = time(NULL); |
113 | struct tm *aux = localtime(&t); |
114 | int daylight_active = aux->tm_isdst; |
115 | |
116 | struct tm tm; |
117 | char buf[1024]; |
118 | |
119 | nolocks_localtime(&tm,t,timezone,daylight_active); |
120 | strftime(buf,sizeof(buf),"%d %b %H:%M:%S" ,&tm); |
121 | printf("[timezone: %d, dl: %d] %s\n" , (int)timezone, (int)daylight_active, buf); |
122 | } |
123 | #endif |
124 | |