/src/gdal/port/cpl_time.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /********************************************************************** |
2 | | * |
3 | | * Name: cpl_time.cpp |
4 | | * Project: CPL - Common Portability Library |
5 | | * Purpose: Time functions. |
6 | | * Author: Even Rouault, <even dot rouault at spatialys.com> |
7 | | * |
8 | | ********************************************************************** |
9 | | * |
10 | | * CPLUnixTimeToYMDHMS() is derived from timesub() in localtime.c from |
11 | | * openbsd/freebsd/netbsd. |
12 | | * |
13 | | * CPLYMDHMSToUnixTime() has been implemented by Even Rouault and is in the |
14 | | * public domain. |
15 | | * |
16 | | * c.f. |
17 | | *http://svn.freebsd.org/viewvc/base/stable/7/lib/libc/stdtime/localtime.c?revision=178142&view=markup |
18 | | * localtime.c comes with the following header : |
19 | | * |
20 | | * This file is in the public domain, so clarified as of |
21 | | * 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov). |
22 | | */ |
23 | | |
24 | | #include "cpl_time.h" |
25 | | #include "cpl_string.h" |
26 | | |
27 | | #include <cstring> |
28 | | #include <ctime> |
29 | | |
30 | | #include "cpl_error.h" |
31 | | |
32 | | constexpr int SECSPERMIN = 60; |
33 | | constexpr int MINSPERHOUR = 60; |
34 | | constexpr int HOURSPERDAY = 24; |
35 | | constexpr int SECSPERHOUR = SECSPERMIN * MINSPERHOUR; |
36 | | constexpr int SECSPERDAY = SECSPERHOUR * HOURSPERDAY; |
37 | | constexpr int DAYSPERWEEK = 7; |
38 | | constexpr int MONSPERYEAR = 12; |
39 | | |
40 | | constexpr int EPOCH_YEAR = 1970; |
41 | | constexpr int EPOCH_WDAY = 4; |
42 | | constexpr int TM_YEAR_BASE = 1900; |
43 | | constexpr int DAYSPERNYEAR = 365; |
44 | | constexpr int DAYSPERLYEAR = 366; |
45 | | |
46 | | static bool isleap(int y) |
47 | 0 | { |
48 | 0 | return ((y % 4) == 0 && (y % 100) != 0) || (y % 400) == 0; |
49 | 0 | } |
50 | | |
51 | | static int LEAPS_THROUGH_END_OF(int y) |
52 | 0 | { |
53 | 0 | return y / 4 - y / 100 + y / 400; |
54 | 0 | } |
55 | | |
56 | | constexpr int mon_lengths[2][MONSPERYEAR] = { |
57 | | {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, |
58 | | {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}; |
59 | | |
60 | | constexpr int year_lengths[2] = {DAYSPERNYEAR, DAYSPERLYEAR}; |
61 | | |
62 | | /************************************************************************/ |
63 | | /* CPLUnixTimeToYMDHMS() */ |
64 | | /************************************************************************/ |
65 | | |
66 | | /** Converts a time value since the Epoch (aka "unix" time) to a broken-down |
67 | | * UTC time. |
68 | | * |
69 | | * This function is similar to gmtime_r(). |
70 | | * This function will always set tm_isdst to 0. |
71 | | * |
72 | | * @param unixTime number of seconds since the Epoch. |
73 | | * @param pRet address of the return structure. |
74 | | * |
75 | | * @return the structure pointed by pRet filled with a broken-down UTC time. |
76 | | */ |
77 | | |
78 | | struct tm *CPLUnixTimeToYMDHMS(GIntBig unixTime, struct tm *pRet) |
79 | 0 | { |
80 | 0 | GIntBig days = unixTime / SECSPERDAY; |
81 | 0 | GIntBig rem = unixTime % SECSPERDAY; |
82 | |
|
83 | 0 | constexpr GIntBig TEN_THOUSAND_YEARS = |
84 | 0 | static_cast<GIntBig>(10000) * SECSPERDAY * DAYSPERLYEAR; |
85 | 0 | if (unixTime < -TEN_THOUSAND_YEARS || unixTime > TEN_THOUSAND_YEARS) |
86 | 0 | { |
87 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
88 | 0 | "Invalid unixTime = " CPL_FRMT_GIB, unixTime); |
89 | 0 | memset(pRet, 0, sizeof(*pRet)); |
90 | 0 | return pRet; |
91 | 0 | } |
92 | | |
93 | 0 | while (rem < 0) |
94 | 0 | { |
95 | 0 | rem += SECSPERDAY; |
96 | 0 | --days; |
97 | 0 | } |
98 | |
|
99 | 0 | pRet->tm_hour = static_cast<int>(rem / SECSPERHOUR); |
100 | 0 | rem = rem % SECSPERHOUR; |
101 | 0 | pRet->tm_min = static_cast<int>(rem / SECSPERMIN); |
102 | | /* |
103 | | ** A positive leap second requires a special |
104 | | ** representation. This uses "... ??:59:60" et seq. |
105 | | */ |
106 | 0 | pRet->tm_sec = static_cast<int>(rem % SECSPERMIN); |
107 | 0 | pRet->tm_wday = static_cast<int>((EPOCH_WDAY + days) % DAYSPERWEEK); |
108 | 0 | if (pRet->tm_wday < 0) |
109 | 0 | pRet->tm_wday += DAYSPERWEEK; |
110 | |
|
111 | 0 | int y = EPOCH_YEAR; |
112 | 0 | int yleap = 0; |
113 | 0 | int iters = 0; |
114 | 0 | while (iters < 1000 && |
115 | 0 | (days < 0 || |
116 | 0 | days >= static_cast<GIntBig>(year_lengths[yleap = isleap(y)]))) |
117 | 0 | { |
118 | 0 | int newy = y + static_cast<int>(days / DAYSPERNYEAR); |
119 | 0 | if (days < 0) |
120 | 0 | --newy; |
121 | 0 | days -= static_cast<GIntBig>(newy - y) * DAYSPERNYEAR + |
122 | 0 | LEAPS_THROUGH_END_OF(newy - 1) - LEAPS_THROUGH_END_OF(y - 1); |
123 | 0 | y = newy; |
124 | 0 | iters++; |
125 | 0 | } |
126 | 0 | if (iters == 1000) |
127 | 0 | { |
128 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
129 | 0 | "Invalid unixTime = " CPL_FRMT_GIB, unixTime); |
130 | 0 | memset(pRet, 0, sizeof(*pRet)); |
131 | 0 | return pRet; |
132 | 0 | } |
133 | | |
134 | 0 | pRet->tm_year = static_cast<int>(y - TM_YEAR_BASE); |
135 | 0 | pRet->tm_yday = static_cast<int>(days); |
136 | 0 | const int *ip = mon_lengths[yleap]; |
137 | |
|
138 | 0 | for (pRet->tm_mon = 0; days >= static_cast<GIntBig>(ip[pRet->tm_mon]); |
139 | 0 | ++(pRet->tm_mon)) |
140 | 0 | days = days - static_cast<GIntBig>(ip[pRet->tm_mon]); |
141 | |
|
142 | 0 | pRet->tm_mday = static_cast<int>((days + 1)); |
143 | 0 | pRet->tm_isdst = 0; |
144 | |
|
145 | 0 | return pRet; |
146 | 0 | } |
147 | | |
148 | | /************************************************************************/ |
149 | | /* CPLYMDHMSToUnixTime() */ |
150 | | /************************************************************************/ |
151 | | |
152 | | /** Converts a broken-down UTC time into time since the Epoch (aka "unix" time). |
153 | | * |
154 | | * This function is similar to mktime(), but the passed structure is not |
155 | | * modified. This function ignores the tm_wday, tm_yday and tm_isdst fields of |
156 | | * the passed value. No timezone shift will be applied. This function |
157 | | * returns 0 for the 1/1/1970 00:00:00 |
158 | | * |
159 | | * @param brokendowntime broken-downtime UTC time. |
160 | | * |
161 | | * @return a number of seconds since the Epoch encoded as a value of type |
162 | | * GIntBig, or -1 if the time cannot be represented. |
163 | | */ |
164 | | |
165 | | GIntBig CPLYMDHMSToUnixTime(const struct tm *brokendowntime) |
166 | 0 | { |
167 | |
|
168 | 0 | if (brokendowntime->tm_mon < 0 || brokendowntime->tm_mon >= 12) |
169 | 0 | return -1; |
170 | | |
171 | | // Number of days of the current month. |
172 | 0 | GIntBig days = brokendowntime->tm_mday - 1; |
173 | | |
174 | | // Add the number of days of the current year. |
175 | 0 | const int *ip = mon_lengths[static_cast<int>( |
176 | 0 | isleap(TM_YEAR_BASE + brokendowntime->tm_year))]; |
177 | 0 | for (int mon = 0; mon < brokendowntime->tm_mon; mon++) |
178 | 0 | days += ip[mon]; |
179 | | |
180 | | // Add the number of days of the other years. |
181 | 0 | days += (TM_YEAR_BASE + static_cast<GIntBig>(brokendowntime->tm_year) - |
182 | 0 | EPOCH_YEAR) * |
183 | 0 | DAYSPERNYEAR + |
184 | 0 | LEAPS_THROUGH_END_OF(static_cast<int>(TM_YEAR_BASE) + |
185 | 0 | static_cast<int>(brokendowntime->tm_year) - |
186 | 0 | static_cast<int>(1)) - |
187 | 0 | LEAPS_THROUGH_END_OF(EPOCH_YEAR - 1); |
188 | | |
189 | | // Now add the secondes, minutes and hours to the number of days |
190 | | // since EPOCH. |
191 | 0 | return brokendowntime->tm_sec + brokendowntime->tm_min * SECSPERMIN + |
192 | 0 | brokendowntime->tm_hour * SECSPERHOUR + days * SECSPERDAY; |
193 | 0 | } |
194 | | |
195 | | /************************************************************************/ |
196 | | /* OGRParseRFC822DateTime() */ |
197 | | /************************************************************************/ |
198 | | |
199 | | static const char *const aszWeekDayStr[] = {"Mon", "Tue", "Wed", "Thu", |
200 | | "Fri", "Sat", "Sun"}; |
201 | | |
202 | | static const char *const aszMonthStr[] = {"Jan", "Feb", "Mar", "Apr", |
203 | | "May", "Jun", "Jul", "Aug", |
204 | | "Sep", "Oct", "Nov", "Dec"}; |
205 | | |
206 | | /** Parse a RFC822 formatted date-time string. |
207 | | * |
208 | | * Such as [Fri,] 28 Dec 2007 05:24[:17] GMT |
209 | | * |
210 | | * @param pszRFC822DateTime formatted string. |
211 | | * @param pnYear pointer to int receiving year (like 1980, 2000, etc...), or |
212 | | * NULL |
213 | | * @param pnMonth pointer to int receiving month (between 1 and 12), or NULL |
214 | | * @param pnDay pointer to int receiving day of month (between 1 and 31), or |
215 | | * NULL |
216 | | * @param pnHour pointer to int receiving hour of day (between 0 and 23), or |
217 | | * NULL |
218 | | * @param pnMinute pointer to int receiving minute (between 0 and 59), or NULL |
219 | | * @param pnSecond pointer to int receiving second (between 0 and 60, or -1 if |
220 | | * unknown), or NULL |
221 | | * @param pnTZFlag pointer to int receiving time zone flag (0=unknown, 100=GMT, |
222 | | * 101=GMT+15minute, 99=GMT-15minute), or NULL |
223 | | * @param pnWeekDay pointer to int receiving day of week (between 1 and 7, or 0 |
224 | | * if invalid/unset), or NULL |
225 | | * @return TRUE if parsing is successful |
226 | | * |
227 | | * @since GDAL 2.3 |
228 | | */ |
229 | | int CPLParseRFC822DateTime(const char *pszRFC822DateTime, int *pnYear, |
230 | | int *pnMonth, int *pnDay, int *pnHour, int *pnMinute, |
231 | | int *pnSecond, int *pnTZFlag, int *pnWeekDay) |
232 | 0 | { |
233 | | // Following |
234 | | // https://www.w3.org/Protocols/rfc822/#z28 : |
235 | | // [Fri,] 28 Dec 2007 05:24[:17] GMT |
236 | 0 | char **papszTokens = |
237 | 0 | CSLTokenizeStringComplex(pszRFC822DateTime, " ,:", TRUE, FALSE); |
238 | 0 | char **papszVal = papszTokens; |
239 | 0 | int nTokens = CSLCount(papszTokens); |
240 | 0 | if (nTokens < 5) |
241 | 0 | { |
242 | 0 | CSLDestroy(papszTokens); |
243 | 0 | return false; |
244 | 0 | } |
245 | | |
246 | 0 | if (pnWeekDay) |
247 | 0 | *pnWeekDay = 0; |
248 | |
|
249 | 0 | if (!((*papszVal)[0] >= '0' && (*papszVal)[0] <= '9')) |
250 | 0 | { |
251 | 0 | if (pnWeekDay) |
252 | 0 | { |
253 | 0 | for (size_t i = 0; i < CPL_ARRAYSIZE(aszWeekDayStr); ++i) |
254 | 0 | { |
255 | 0 | if (EQUAL(*papszVal, aszWeekDayStr[i])) |
256 | 0 | { |
257 | 0 | *pnWeekDay = static_cast<int>(i + 1); |
258 | 0 | break; |
259 | 0 | } |
260 | 0 | } |
261 | 0 | } |
262 | |
|
263 | 0 | ++papszVal; |
264 | 0 | } |
265 | |
|
266 | 0 | int day = atoi(*papszVal); |
267 | 0 | if (day <= 0 || day >= 32) |
268 | 0 | { |
269 | 0 | CSLDestroy(papszTokens); |
270 | 0 | return false; |
271 | 0 | } |
272 | 0 | if (pnDay) |
273 | 0 | *pnDay = day; |
274 | 0 | ++papszVal; |
275 | |
|
276 | 0 | int month = 0; |
277 | 0 | for (int i = 0; i < 12; ++i) |
278 | 0 | { |
279 | 0 | if (EQUAL(*papszVal, aszMonthStr[i])) |
280 | 0 | { |
281 | 0 | month = i + 1; |
282 | 0 | break; |
283 | 0 | } |
284 | 0 | } |
285 | 0 | if (month == 0) |
286 | 0 | { |
287 | 0 | CSLDestroy(papszTokens); |
288 | 0 | return false; |
289 | 0 | } |
290 | 0 | if (pnMonth) |
291 | 0 | *pnMonth = month; |
292 | 0 | ++papszVal; |
293 | |
|
294 | 0 | int year = atoi(*papszVal); |
295 | 0 | if (year < 100 && year >= 30) |
296 | 0 | year += 1900; |
297 | 0 | else if (year < 30 && year >= 0) |
298 | 0 | year += 2000; |
299 | 0 | if (pnYear) |
300 | 0 | *pnYear = year; |
301 | 0 | ++papszVal; |
302 | |
|
303 | 0 | int hour = atoi(*papszVal); |
304 | 0 | if (hour < 0 || hour >= 24) |
305 | 0 | { |
306 | 0 | CSLDestroy(papszTokens); |
307 | 0 | return false; |
308 | 0 | } |
309 | 0 | if (pnHour) |
310 | 0 | *pnHour = hour; |
311 | 0 | ++papszVal; |
312 | |
|
313 | 0 | if (*papszVal == nullptr) |
314 | 0 | { |
315 | 0 | CSLDestroy(papszTokens); |
316 | 0 | return false; |
317 | 0 | } |
318 | 0 | int minute = atoi(*papszVal); |
319 | 0 | if (minute < 0 || minute >= 60) |
320 | 0 | { |
321 | 0 | CSLDestroy(papszTokens); |
322 | 0 | return false; |
323 | 0 | } |
324 | 0 | if (pnMinute) |
325 | 0 | *pnMinute = minute; |
326 | 0 | ++papszVal; |
327 | |
|
328 | 0 | if (*papszVal != nullptr && (*papszVal)[0] >= '0' && (*papszVal)[0] <= '9') |
329 | 0 | { |
330 | 0 | int second = atoi(*papszVal); |
331 | 0 | if (second < 0 || second >= 61) |
332 | 0 | { |
333 | 0 | CSLDestroy(papszTokens); |
334 | 0 | return false; |
335 | 0 | } |
336 | 0 | if (pnSecond) |
337 | 0 | *pnSecond = second; |
338 | 0 | ++papszVal; |
339 | 0 | } |
340 | 0 | else if (pnSecond) |
341 | 0 | *pnSecond = -1; |
342 | | |
343 | 0 | int TZ = 0; |
344 | 0 | if (*papszVal == nullptr) |
345 | 0 | { |
346 | 0 | } |
347 | 0 | else if (strlen(*papszVal) == 5 && |
348 | 0 | ((*papszVal)[0] == '+' || (*papszVal)[0] == '-')) |
349 | 0 | { |
350 | 0 | char szBuf[3] = {(*papszVal)[1], (*papszVal)[2], 0}; |
351 | 0 | const int TZHour = atoi(szBuf); |
352 | 0 | if (TZHour < 0 || TZHour >= 15) |
353 | 0 | { |
354 | 0 | CSLDestroy(papszTokens); |
355 | 0 | return false; |
356 | 0 | } |
357 | 0 | szBuf[0] = (*papszVal)[3]; |
358 | 0 | szBuf[1] = (*papszVal)[4]; |
359 | 0 | szBuf[2] = 0; |
360 | 0 | const int TZMinute = atoi(szBuf); |
361 | 0 | TZ = 100 + (((*papszVal)[0] == '+') ? 1 : -1) * |
362 | 0 | ((TZHour * 60 + TZMinute) / 15); |
363 | 0 | } |
364 | 0 | else |
365 | 0 | { |
366 | 0 | const char *aszTZStr[] = {"GMT", "UT", "Z", "EST", "EDT", "CST", |
367 | 0 | "CDT", "MST", "MDT", "PST", "PDT"}; |
368 | 0 | const int anTZVal[] = {0, 0, 0, -5, -4, -6, -5, -7, -6, -8, -7}; |
369 | 0 | TZ = -1; |
370 | 0 | for (int i = 0; i < 11; ++i) |
371 | 0 | { |
372 | 0 | if (EQUAL(*papszVal, aszTZStr[i])) |
373 | 0 | { |
374 | 0 | TZ = 100 + anTZVal[i] * 4; |
375 | 0 | break; |
376 | 0 | } |
377 | 0 | } |
378 | 0 | if (TZ < 0) |
379 | 0 | { |
380 | 0 | CSLDestroy(papszTokens); |
381 | 0 | return false; |
382 | 0 | } |
383 | 0 | } |
384 | | |
385 | 0 | if (pnTZFlag) |
386 | 0 | *pnTZFlag = TZ; |
387 | |
|
388 | 0 | CSLDestroy(papszTokens); |
389 | 0 | return true; |
390 | 0 | } |