1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
|
/* libjodycode: datetime string to UNIX epoch conversion
*
* Copyright (C) 2020-2026 by Jody Bruchon <jody@jodybruchon.com>
* Released under The MIT License
*/
#include <errno.h>
#include <string.h>
#include <time.h>
#include "likely_unlikely.h"
#include "libjodycode.h"
#define NTTIME_CONSTANT 116444736000000000
#define NTTIME_NSEC 10000000
#define ATONUM(a,b) (a = b - '0')
static int twodigit_atoi(const char * restrict p)
{
int i, val;
if (unlikely(*p < '0' || *p > '9')) return -1;
ATONUM(i, *p); val = i * 10; p++;
if (unlikely(*p < '0' || *p > '9')) return -1;
ATONUM(i, *p); val += i; p++;
return val;
}
/* Accepts date[time] strings "YYYY-MM-DD" or "YYYY-MM-DD HH:MM:SS"
* and returns the number of seconds since the Unix Epoch a la mktime()
* or returns -1 on any error */
time_t jc_strtoepoch(const char * const datetime)
{
time_t secs = 0; /* 1970-01-01 00:00:00 */
const char * restrict p = datetime;
int i;
struct tm tm;
if (unlikely(datetime == NULL || *datetime == '\0')) goto error_null;
memset(&tm, 0, sizeof(struct tm));
/* Process year */
i = twodigit_atoi(p); if (unlikely(i < 19)) goto error_datetime;
tm.tm_year += i * 100; p += 2;
i = twodigit_atoi(p); if (unlikely(i < 0)) goto error_datetime;
tm.tm_year += i;
tm.tm_year -= 1900; /* struct tm year is since 1900 */
p += 2;
if (unlikely(*p != '-')) goto error_datetime;
p++;
/* Process month (0-11, not 1-12) */
i = twodigit_atoi(p); if (unlikely(i < 1 || i > 12)) goto error_datetime;
tm.tm_mon = i - 1;
p += 2;
if (unlikely(*p != '-')) goto error_datetime;
p++;
/* Process day */
i = twodigit_atoi(p); if (unlikely(i < 1 || i > 31)) goto error_datetime;
tm.tm_mday = i;
p += 2;
/* If YYYY-MM-DD is specified only, skip the time part */
if (*p == '\0') goto skip_time;
if (unlikely(*p != ' ')) goto error_datetime; else p++;
/* Process hours */
i = twodigit_atoi(p); if (unlikely(i < 0 || i > 23)) goto error_datetime;
tm.tm_hour = i;
p += 2;
if (unlikely(*p != ':')) goto error_datetime;
p++;
/* Process minutes */
i = twodigit_atoi(p); if (unlikely(i < 0 || i > 59)) goto error_datetime;
tm.tm_min = i;
p += 2;
if (unlikely(*p != ':')) goto error_datetime;
p++;
/* Process seconds */
i = twodigit_atoi(p); if (unlikely(i < 0 || i > 59)) goto error_datetime;
tm.tm_sec = i;
p += 2;
/* Junk after datetime string should cause an error */
if (unlikely(*p != '\0')) goto error_datetime;
skip_time:
tm.tm_isdst = -1; /* Let the host library decide if DST is in effect */
errno = 0;
secs = mktime(&tm);
if (secs == -1) jc_errno = errno;
return secs;
error_null:
jc_errno = JC_ENULL;
return -1;
error_datetime:
jc_errno = JC_EDATETIME;
return -1;
}
#ifdef ON_WINDOWS
int jc_nttime_to_unixtime(const FILETIME * const restrict filetime, struct JC_TIMESPEC * const restrict unixtime)
{
uint64_t nttime;
if (unlikely(filetime == NULL || unixtime == NULL)) return -1;
nttime = ((uint64_t)(filetime->dwHighDateTime) << 32) + filetime->dwLowDateTime;
if (unlikely(nttime <= NTTIME_CONSTANT)) return -1;
unixtime->tv_sec = (time_t)((nttime - NTTIME_CONSTANT) / NTTIME_NSEC);
unixtime->tv_nsec = (long)(((nttime - NTTIME_CONSTANT) % NTTIME_NSEC) * 100);
return 0;
}
int jc_unixtime_to_nttime(const struct JC_TIMESPEC * const restrict unixtime, FILETIME * const restrict filetime)
{
uint64_t nttime;
if (unlikely(filetime == NULL || unixtime == NULL)) return -1;
nttime = (uint64_t)((unixtime->tv_sec * NTTIME_NSEC) + (unixtime->tv_nsec / 100) + NTTIME_CONSTANT);
if (unlikely(nttime <= NTTIME_CONSTANT)) return -1;
filetime->dwHighDateTime = (DWORD)(nttime >> 32);
filetime->dwLowDateTime = (DWORD)nttime - filetime->dwHighDateTime;
return 0;
}
#endif /* ON_WINDOWS */
|