This is a simple function that accepts a null-terminated string that represents a non-negative number of seconds. It can optionally end in the suffix "s" (seconds, default), "m" (minutes), "h" (hours), or "d" (days). The number is read by strtod
and so it can accept floating point notation. For example all of these return 216000: "2.5d", "60h", "3600m", "216000s", "216000". I understand the limitations of floating point types and it's not a problem if not every time_t
value can be precisely represented, nor is it a problem that the returne number of seconds will be rounded down to the nearest integer.
The function returns -1 on invalid input, TIME_MAX on input that would have exceeded the capacity of time_t
to represent, and an integer number of seconds otherwise. Performance is not particularly important, but avoiding edge cases is.
The function will only run on POSIX systems so I can make assumptions, such as assuming time_t
is a signed integer (POSIX guarantees that even though the C standard only states that it is an "arithmetic type").
The contents of parse_time.c
:
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <math.h>
#include <errno.h>
#include <assert.h>
#include "parse_time.h"
time_t parse_time(const char *str)
{
char *endp;
double time;
assert(str);
/* limit to decimal floating point without leading whitespace, a sign, or an exponent */
if (isspace(*str) || strpbrk(str, "eExX+-") != NULL)
return -1;
errno = 0;
time = strtod(str, &endp);
/* verify that the time is non-negative and does not overflow */
if (errno == ERANGE || endp == str || time < 0)
return -1;
/* verify that the float has no suffix or has only a one character suffix */
if (*endp != '\0' && *(endp + 1) != '\0')
return -1;
/* handle any suffix */
switch (*endp) {
case '\0':
case 's':
break;
case 'm':
time *= SECS_PER_MIN;
break;
case 'h':
time *= SECS_PER_HOUR;
break;
case 'd':
time *= SECS_PER_DAY;
break;
default:
return -1;
}
if (isnan(time))
return -1;
/* cap at the maximum value time_t can represent */
return (time >= TIME_MAX) ? TIME_MAX : (time_t)time;
}
The contents of parse_time.h
:
#include <assert.h>
#include <time.h>
/* get maximum value that time_t can represent */
#if __TIMESIZE == 64
#define TIME_MAX INT64_MAX
#else
#define TIME_MAX INT32_MAX
#endif
static_assert(TIME_MAX == (time_t)TIME_MAX);
#define SECS_PER_MIN 60
#define SECS_PER_HOUR 3600
#define SECS_PER_DAY 86400
time_t parse_time(const char *str);
123456789s
, will a result of123456788
be acceptable? That's one of the drawbacks of doing your calculations using floating-point arithmetic. \$\endgroup\$static_assert(TIME_MAX == (time_t)TIME_MAX);
may be valid in POSIX, yet that is not valid in current C. It is expected to be valid in C2X. Alternative:static_assert(TIME_MAX == (time_t)TIME_MAX, "TIME_MAX issue");
. \$\endgroup\$