xref: /reactos/sdk/lib/ucrt/time/mktime.cpp (revision b09b5584)
1 //
2 // mktime.cpp
3 //
4 //      Copyright (c) Microsoft Corporation. All rights reserved.
5 //
6 // The mktime and mkgmtime families of functions, which convert a time value in
7 // a (possibly incomplete) tm structure into a time_t value, then update all of
8 // the tm structure fields with the "normalized" values.
9 //
10 #include <corecrt_internal_time.h>
11 
12 
13 // ChkAdd evaluates to TRUE if dest = src1 + src2 has overflowed
14 #define ChkAdd(dest, src1, src2)  \
15     (((src1 >= 0L) && (src2 >= 0L) && (dest <  0L)) || \
16      ((src1 <  0L) && (src2 <  0L) && (dest >= 0L)))
17 
18 // ChkMul evaluates to TRUE if dest = src1 * src2 has overflowed
19 #define ChkMul(dest, src1, src2) ( src1 ? (dest / src1 != src2) : 0 )
20 
21 
22 
23 // The implementation of the _mktime and _mkgmtime functions.  If 'use_local_time'
24 // is true, the time is assumed to be in local time; otherwise, the time is
25 // assumed to be in UTC.
26 template <typename TimeType>
27 static TimeType __cdecl common_mktime(
28     tm*  const tb,
29     bool const use_local_time
30     ) throw()
31 {
32     typedef __crt_time_time_t_traits<TimeType> time_traits;
33 
34     TimeType const invalid_time = static_cast<TimeType>(-1);
35 
36     _VALIDATE_RETURN(tb != nullptr, EINVAL, invalid_time)
37 
38     TimeType tmptm1, tmptm2, tmptm3;
39 
40     // First, make sure tm_year is reasonably close to being in range.
41     if ((tmptm1 = tb->tm_year) < _BASE_YEAR - 1 || tmptm1 > time_traits::max_year + 1)
42         return (errno = EINVAL), invalid_time;
43 
44     // Adjust month value so it is in the range 0 - 11.  This is because
45     // we don't know how many days are in months 12, 13, 14, etc.
46     if (tb->tm_mon < 0 || tb->tm_mon > 11)
47     {
48         // No danger of overflow because the range check above.
49         tmptm1 += (tb->tm_mon / 12);
50 
51         if ((tb->tm_mon %= 12) < 0)
52         {
53             tb->tm_mon += 12;
54             --tmptm1;
55         }
56 
57         // Make sure year count is still in range.
58         if (tmptm1 < _BASE_YEAR - 1 || tmptm1 > time_traits::max_year + 1)
59             return (errno = EINVAL), invalid_time;
60     }
61 
62     // HERE: tmptm1 holds number of elapsed years
63 
64     // Calculate days elapsed minus one, in the given year, to the given
65     // month. Check for leap year and adjust if necessary.
66     tmptm2 = _days[tb->tm_mon];
67     if (__crt_time_is_leap_year(tmptm1) && tb->tm_mon > 1)
68         ++tmptm2;
69 
70     // Calculate elapsed days since base date (midnight, 1/1/70, UTC)
71     //
72     // 365 days for each elapsed year since 1970, plus one more day for
73     // each elapsed leap year. no danger of overflow because of the range
74     // check (above) on tmptm1.
75     tmptm3 = (tmptm1 - _BASE_YEAR) * 365 + __crt_time_elapsed_leap_years(tmptm1);
76 
77     // Elapsed days to current month (still no possible overflow)
78     tmptm3 += tmptm2;
79 
80     // Elapsed days to current date. overflow is now possible.
81     tmptm1 = tmptm3 + (tmptm2 = static_cast<TimeType>(tb->tm_mday));
82     _VALIDATE_RETURN_NOEXC(!ChkAdd(tmptm1, tmptm3, tmptm2), EINVAL, invalid_time)
83 
84     // HERE: tmptm1 holds number of elapsed days
85 
86     // Calculate elapsed hours since base date
87     tmptm2 = tmptm1 * 24;
88     _VALIDATE_RETURN_NOEXC(!ChkMul(tmptm2, tmptm1, 24), EINVAL, invalid_time)
89 
90 
91     tmptm1 = tmptm2 + (tmptm3 = static_cast<TimeType>(tb->tm_hour));
92     _VALIDATE_RETURN_NOEXC(!ChkAdd(tmptm1, tmptm2, tmptm3), EINVAL, invalid_time)
93 
94 
95     // HERE: tmptm1 holds number of elapsed hours
96 
97     // Calculate elapsed minutes since base date
98     tmptm2 = tmptm1 * 60;
99     _VALIDATE_RETURN_NOEXC(!ChkMul(tmptm2, tmptm1, 60), EINVAL, invalid_time)
100 
101 
102     tmptm1 = tmptm2 + (tmptm3 = static_cast<TimeType>(tb->tm_min));
103     _VALIDATE_RETURN_NOEXC(!ChkAdd(tmptm1, tmptm2, tmptm3), EINVAL, invalid_time)
104 
105 
106     // HERE: tmptm1 holds number of elapsed minutes
107 
108     // Calculate elapsed seconds since base date
109     tmptm2 = tmptm1 * 60L;
110     _VALIDATE_RETURN_NOEXC(!ChkMul(tmptm2, tmptm1, 60L), EINVAL, invalid_time)
111 
112 
113     tmptm1 = tmptm2 + (tmptm3 = static_cast<TimeType>(tb->tm_sec));
114     _VALIDATE_RETURN_NOEXC(!ChkAdd(tmptm1, tmptm2, tmptm3), EINVAL, invalid_time)
115 
116 
117     // HERE: tmptm1 holds number of elapsed seconds
118 
119     tm tbtemp;
120     if (use_local_time)
121     {
122         // Adjust for timezone. No need to check for overflow since
123         // localtime() will check its arg value
124         __tzset();
125 
126         long dstbias = 0;
127         long timezone = 0;
128         _ERRCHECK(_get_dstbias(&dstbias));
129         _ERRCHECK(_get_timezone(&timezone));
130 
131         tmptm1 += timezone;
132 
133         // Convert this second count back into a time block structure.
134         // If localtime returns nullptr, return an error.
135         if (time_traits::localtime_s(&tbtemp, &tmptm1) != 0)
136             return (errno = EINVAL), invalid_time;
137 
138         // Now must compensate for DST. The ANSI rules are to use the passed-in
139         // tm_isdst flag if it is non-negative. Otherwise, compute if DST
140         // applies. Recall that tbtemp has the time without DST compensation,
141         // but has set tm_isdst correctly.
142         if (tb->tm_isdst > 0 || (tb->tm_isdst < 0 && tbtemp.tm_isdst > 0))
143         {
144             tmptm1 += dstbias;
145             if (time_traits::localtime_s(&tbtemp, &tmptm1) != 0)
146                 return (errno = EINVAL), invalid_time;
147         }
148 
149     }
150     else
151     {
152         if (time_traits::gmtime_s(&tbtemp, &tmptm1) != 0)
153             return (errno = EINVAL), invalid_time;
154     }
155 
156     // HERE: tmptm1 holds number of elapsed seconds, adjusted for local time if
157     // requested
158 
159     *tb = tbtemp;
160     return tmptm1;
161 }
162 
163 
164 
165 // Converts a tm structure value into a time_t value.  These functions also
166 // update the tm structure to normalize it and populate any missing fields.
167 // There are three practical uses for these functions:
168 //
169 // (1) To convert a broken-down time to the internal time format (time_t)
170 // (2) To complete the tm value with the correct tm_wday, tm_yday, or tm_isdst
171 //     values given the rest of the contents of the tm.
172 // (3) To pass in a time structure with "out of range" values for some fields
173 //     and get back a "normalized" tm structure (e.g., to pass in 1/35/1987
174 //     and get back 2/4/1987).
175 //
176 // Returns the resulting time_t value on success; returns -1 on failure.
177 extern "C" __time32_t __cdecl _mktime32(tm* const tb)
178 {
179     return common_mktime<__time32_t>(tb, true);
180 }
181 
182 extern "C" __time64_t __cdecl _mktime64(tm* const tb)
183 {
184     return common_mktime<__time64_t>(tb, true);
185 }
186 
187 
188 
189 // Converts a UTC time stored in a tm structure into a time_t value.  These
190 // functions also update the tm structure to normalize it and populate any
191 // missing fields.  Returns the resulting time_t value on success; returns
192 // -1 on failure.
193 extern "C"  __time32_t __cdecl _mkgmtime32(tm* const tb)
194 {
195     return common_mktime<__time32_t>(tb, false);
196 }
197 
198 extern "C"  __time64_t __cdecl _mkgmtime64(tm* const tb)
199 {
200     return common_mktime<__time64_t>(tb, false);
201 }
202