Line data Source code
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 73910 : static bool isleap(int y)
47 : {
48 73910 : return ((y % 4) == 0 && (y % 100) != 0) || (y % 400) == 0;
49 : }
50 :
51 104322 : static int LEAPS_THROUGH_END_OF(int y)
52 : {
53 104322 : return y / 4 - y / 100 + y / 400;
54 : }
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 29391 : struct tm *CPLUnixTimeToYMDHMS(GIntBig unixTime, struct tm *pRet)
79 : {
80 29391 : GIntBig days = unixTime / SECSPERDAY;
81 29391 : GIntBig rem = unixTime % SECSPERDAY;
82 :
83 29391 : constexpr GIntBig TEN_THOUSAND_YEARS =
84 : static_cast<GIntBig>(10000) * SECSPERDAY * DAYSPERLYEAR;
85 29391 : if (unixTime < -TEN_THOUSAND_YEARS || unixTime > TEN_THOUSAND_YEARS)
86 : {
87 0 : CPLError(CE_Failure, CPLE_NotSupported,
88 : "Invalid unixTime = " CPL_FRMT_GIB, unixTime);
89 0 : memset(pRet, 0, sizeof(*pRet));
90 0 : return pRet;
91 : }
92 :
93 29411 : while (rem < 0)
94 : {
95 20 : rem += SECSPERDAY;
96 20 : --days;
97 : }
98 :
99 29391 : pRet->tm_hour = static_cast<int>(rem / SECSPERHOUR);
100 29391 : rem = rem % SECSPERHOUR;
101 29391 : 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 29391 : pRet->tm_sec = static_cast<int>(rem % SECSPERMIN);
107 29391 : pRet->tm_wday = static_cast<int>((EPOCH_WDAY + days) % DAYSPERWEEK);
108 29391 : if (pRet->tm_wday < 0)
109 25 : pRet->tm_wday += DAYSPERWEEK;
110 :
111 29391 : int y = EPOCH_YEAR;
112 29391 : int yleap = 0;
113 29391 : int iters = 0;
114 117448 : while (iters < 1000 &&
115 54903 : (days < 0 ||
116 54903 : days >= static_cast<GIntBig>(year_lengths[yleap = isleap(y)])))
117 : {
118 33154 : int newy = y + static_cast<int>(days / DAYSPERNYEAR);
119 33154 : if (days < 0)
120 7642 : --newy;
121 99462 : days -= static_cast<GIntBig>(newy - y) * DAYSPERNYEAR +
122 33154 : LEAPS_THROUGH_END_OF(newy - 1) - LEAPS_THROUGH_END_OF(y - 1);
123 33154 : y = newy;
124 33154 : iters++;
125 : }
126 29391 : if (iters == 1000)
127 : {
128 0 : CPLError(CE_Failure, CPLE_NotSupported,
129 : "Invalid unixTime = " CPL_FRMT_GIB, unixTime);
130 0 : memset(pRet, 0, sizeof(*pRet));
131 0 : return pRet;
132 : }
133 :
134 29391 : pRet->tm_year = static_cast<int>(y - TM_YEAR_BASE);
135 29391 : pRet->tm_yday = static_cast<int>(days);
136 29391 : const int *ip = mon_lengths[yleap];
137 :
138 175052 : for (pRet->tm_mon = 0; days >= static_cast<GIntBig>(ip[pRet->tm_mon]);
139 145661 : ++(pRet->tm_mon))
140 145661 : days = days - static_cast<GIntBig>(ip[pRet->tm_mon]);
141 :
142 29391 : pRet->tm_mday = static_cast<int>((days + 1));
143 29391 : pRet->tm_isdst = 0;
144 :
145 29391 : return pRet;
146 : }
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 19010 : GIntBig CPLYMDHMSToUnixTime(const struct tm *brokendowntime)
166 : {
167 :
168 19010 : if (brokendowntime->tm_mon < 0 || brokendowntime->tm_mon >= 12)
169 3 : return -1;
170 :
171 : // Number of days of the current month.
172 19007 : GIntBig days = brokendowntime->tm_mday - 1;
173 :
174 : // Add the number of days of the current year.
175 : const int *ip = mon_lengths[static_cast<int>(
176 19007 : isleap(TM_YEAR_BASE + brokendowntime->tm_year))];
177 109979 : for (int mon = 0; mon < brokendowntime->tm_mon; mon++)
178 90972 : days += ip[mon];
179 :
180 : // Add the number of days of the other years.
181 38014 : days += (TM_YEAR_BASE + static_cast<GIntBig>(brokendowntime->tm_year) -
182 19007 : EPOCH_YEAR) *
183 19007 : DAYSPERNYEAR +
184 38014 : LEAPS_THROUGH_END_OF(static_cast<int>(TM_YEAR_BASE) +
185 19007 : static_cast<int>(brokendowntime->tm_year) -
186 19007 : static_cast<int>(1)) -
187 19007 : 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 19007 : return brokendowntime->tm_sec + brokendowntime->tm_min * SECSPERMIN +
192 19007 : brokendowntime->tm_hour * SECSPERHOUR + days * SECSPERDAY;
193 : }
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 75 : int CPLParseRFC822DateTime(const char *pszRFC822DateTime, int *pnYear,
230 : int *pnMonth, int *pnDay, int *pnHour, int *pnMinute,
231 : int *pnSecond, int *pnTZFlag, int *pnWeekDay)
232 : {
233 : // Following
234 : // https://www.w3.org/Protocols/rfc822/#z28 :
235 : // [Fri,] 28 Dec 2007 05:24[:17] GMT
236 : char **papszTokens =
237 75 : CSLTokenizeStringComplex(pszRFC822DateTime, " ,:", TRUE, FALSE);
238 75 : char **papszVal = papszTokens;
239 75 : int nTokens = CSLCount(papszTokens);
240 75 : if (nTokens < 5)
241 : {
242 16 : CSLDestroy(papszTokens);
243 16 : return false;
244 : }
245 :
246 59 : if (pnWeekDay)
247 17 : *pnWeekDay = 0;
248 :
249 59 : if (!((*papszVal)[0] >= '0' && (*papszVal)[0] <= '9'))
250 : {
251 36 : if (pnWeekDay)
252 : {
253 23 : for (size_t i = 0; i < CPL_ARRAYSIZE(aszWeekDayStr); ++i)
254 : {
255 22 : if (EQUAL(*papszVal, aszWeekDayStr[i]))
256 : {
257 3 : *pnWeekDay = static_cast<int>(i + 1);
258 3 : break;
259 : }
260 : }
261 : }
262 :
263 36 : ++papszVal;
264 : }
265 :
266 59 : int day = atoi(*papszVal);
267 59 : if (day <= 0 || day >= 32)
268 : {
269 2 : CSLDestroy(papszTokens);
270 2 : return false;
271 : }
272 57 : if (pnDay)
273 56 : *pnDay = day;
274 57 : ++papszVal;
275 :
276 57 : int month = 0;
277 319 : for (int i = 0; i < 12; ++i)
278 : {
279 318 : if (EQUAL(*papszVal, aszMonthStr[i]))
280 : {
281 56 : month = i + 1;
282 56 : break;
283 : }
284 : }
285 57 : if (month == 0)
286 : {
287 1 : CSLDestroy(papszTokens);
288 1 : return false;
289 : }
290 56 : if (pnMonth)
291 55 : *pnMonth = month;
292 56 : ++papszVal;
293 :
294 56 : int year = atoi(*papszVal);
295 56 : if (year < 100 && year >= 30)
296 0 : year += 1900;
297 56 : else if (year < 30 && year >= 0)
298 0 : year += 2000;
299 56 : if (pnYear)
300 55 : *pnYear = year;
301 56 : ++papszVal;
302 :
303 56 : int hour = atoi(*papszVal);
304 56 : if (hour < 0 || hour >= 24)
305 : {
306 2 : CSLDestroy(papszTokens);
307 2 : return false;
308 : }
309 54 : if (pnHour)
310 53 : *pnHour = hour;
311 54 : ++papszVal;
312 :
313 54 : if (*papszVal == nullptr)
314 : {
315 1 : CSLDestroy(papszTokens);
316 1 : return false;
317 : }
318 53 : int minute = atoi(*papszVal);
319 53 : if (minute < 0 || minute >= 60)
320 : {
321 2 : CSLDestroy(papszTokens);
322 2 : return false;
323 : }
324 51 : if (pnMinute)
325 50 : *pnMinute = minute;
326 51 : ++papszVal;
327 :
328 51 : if (*papszVal != nullptr && (*papszVal)[0] >= '0' && (*papszVal)[0] <= '9')
329 : {
330 49 : int second = atoi(*papszVal);
331 49 : if (second < 0 || second >= 61)
332 : {
333 1 : CSLDestroy(papszTokens);
334 1 : return false;
335 : }
336 48 : if (pnSecond)
337 47 : *pnSecond = second;
338 48 : ++papszVal;
339 : }
340 2 : else if (pnSecond)
341 2 : *pnSecond = -1;
342 :
343 50 : int TZ = 0;
344 50 : if (*papszVal == nullptr)
345 : {
346 : }
347 29 : else if (strlen(*papszVal) == 5 &&
348 25 : ((*papszVal)[0] == '+' || (*papszVal)[0] == '-'))
349 : {
350 25 : char szBuf[3] = {(*papszVal)[1], (*papszVal)[2], 0};
351 25 : const int TZHour = atoi(szBuf);
352 25 : if (TZHour < 0 || TZHour >= 15)
353 : {
354 2 : CSLDestroy(papszTokens);
355 2 : return false;
356 : }
357 23 : szBuf[0] = (*papszVal)[3];
358 23 : szBuf[1] = (*papszVal)[4];
359 23 : szBuf[2] = 0;
360 23 : const int TZMinute = atoi(szBuf);
361 23 : TZ = 100 + (((*papszVal)[0] == '+') ? 1 : -1) *
362 23 : ((TZHour * 60 + TZMinute) / 15);
363 : }
364 : else
365 : {
366 4 : const char *aszTZStr[] = {"GMT", "UT", "Z", "EST", "EDT", "CST",
367 : "CDT", "MST", "MDT", "PST", "PDT"};
368 4 : const int anTZVal[] = {0, 0, 0, -5, -4, -6, -5, -7, -6, -8, -7};
369 4 : TZ = -1;
370 26 : for (int i = 0; i < 11; ++i)
371 : {
372 24 : if (EQUAL(*papszVal, aszTZStr[i]))
373 : {
374 2 : TZ = 100 + anTZVal[i] * 4;
375 2 : break;
376 : }
377 : }
378 4 : if (TZ < 0)
379 : {
380 2 : CSLDestroy(papszTokens);
381 2 : return false;
382 : }
383 : }
384 :
385 46 : if (pnTZFlag)
386 25 : *pnTZFlag = TZ;
387 :
388 46 : CSLDestroy(papszTokens);
389 46 : return true;
390 : }
|