Coverage Report

Created: 2025-06-13 06:29

/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
}