commit: 02de891996ed3af39c5f91cddc5aef92bc8485a3
parent c0c9b9ce4654fb31b4ce6f2fe812147f2957150a
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Wed, 18 Jun 2025 13:45:07 +0200
Add support for concatenated durations (say 1d5m)
Diffstat:
4 files changed, 111 insertions(+), 80 deletions(-)
diff --git a/cmd/sleep.1 b/cmd/sleep.1
@@ -1,7 +1,7 @@
.\" utils-std: Collection of commonly available Unix tools
.\" Copyright 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
.\" SPDX-License-Identifier: MPL-2.0
-.Dd July 25, 2024
+.Dd June 18, 2025
.Dt SLEEP 1
.Os
.Sh NAME
@@ -18,7 +18,8 @@ utily shall suspends execution for the total of each
argument.
.Pp
.Ar duration
-is a non-negative decimal number including floats, optionally followed by a suffix: s for seconds (default), m for minutes, h for hours.
+is a string containing non-negative decimal numbers including floats, terminated by a suffix: s for seconds, m for minutes, h for hours, d for days.
+If the final number doesn't have a suffix it is assumed to be seconds.
Longer durations are taken as out of scope.
.Sh EXIT STATUS
.Ex -std
diff --git a/cmd/timeout.1 b/cmd/timeout.1
@@ -1,7 +1,7 @@
.\" utils-std: Collection of commonly available Unix tools
.\" Copyright 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
.\" SPDX-License-Identifier: MPL-2.0
-.Dd July 22, 2024
+.Dd June 18, 2025
.Dt TIMEOUT 1
.Os
.Sh NAME
@@ -24,8 +24,9 @@ and terminates it, if still running after
.Ar duration .
.Pp
.Ar duration
-is a non-negative decimal number including floats,
-optionally followed by a suffix: s for seconds (default), m for minutes, h for hours.
+is a string containing non-negative decimal numbers including floats, terminated by a suffix: s for seconds, m for minutes, h for hours, d for days.
+If the final number doesn't have a suffix it is assumed to be seconds.
+Longer durations are taken as out of scope.
.Sh OPTIONS
.Bl -tag -width __
.It Fl f
diff --git a/lib/strtodur.c b/lib/strtodur.c
@@ -6,6 +6,7 @@
#include "strtodur.h"
#include <assert.h>
+#include <ctype.h>
#include <errno.h> // errno
#include <stdio.h> // fprintf, perror, sscanf
#include <string.h> // strerror
@@ -15,95 +16,115 @@ const char *errstr_nan = "Not a number";
int
strtodur(char *s, struct timespec *dur)
{
- if(s == 0 || s[0] == '\0') return 0;
+ if(s == 0) return 0;
assert(dur);
- float in = 0.0;
+ float total = 0.0;
- if(s[0] != '.' && s[0] != ',')
+ while(*s != '\0')
{
- int parsed = 0;
+ float in = 0.0;
- errno = 0;
- if(sscanf(s, "%10f%n", &in, &parsed) < 1)
+ if(s[0] != '.' && s[0] != ',')
{
- const char *errstr = errstr_nan;
- if((errno = !0))
+ int parsed = 0;
+ float res = 0.0;
+
+ errno = 0;
+ if(sscanf(s, "%10f%n", &res, &parsed) < 1)
{
- errstr = strerror(errno);
- errno = 0;
+ const char *errstr = errstr_nan;
+ if(errno != 0)
+ {
+ errstr = strerror(errno);
+ errno = 0;
+ }
+
+ fprintf(
+ stderr, "%s: error: strtodur failed scanning '%s' as a number: %s\n", argv0, s, errstr);
+ return -1;
}
- fprintf(
- stderr, "%s: error: strtodur failed scanning '%s' as a number: %s\n", argv0, s, errstr);
- return -1;
+ in += res;
+ s += parsed;
}
- s += parsed;
- }
+ if((s[0] == '.' || s[0] == ','))
+ {
+ if(s[1] == '\0')
+ {
+ s++;
+ goto dot_skip;
+ }
- if((s[0] == '.' || s[0] == ',') && s[1] != '\0')
- {
- float fraction = 0.0;
- if(s[0] == ',') s[0] = '.';
+ float fraction = 0.0;
+ if(s[0] == ',') s[0] = '.';
- int parsed = 0;
+ int parsed = 0;
- errno = 0;
- if(sscanf(s, "%10f%n", &fraction, &parsed) < 1)
- {
- const char *errstr = errstr_nan;
- if((errno = !0))
+ errno = 0;
+ if(sscanf(s, "%10f%n", &fraction, &parsed) < 1)
{
- errstr = strerror(errno);
- errno = 0;
+ const char *errstr = errstr_nan;
+ if(errno != 0)
+ {
+ errstr = strerror(errno);
+ errno = 0;
+ }
+
+ fprintf(stderr,
+ "%s: error: strtodur failed scanning decimal part '%s' as a number: %s\n",
+ argv0,
+ s,
+ errstr);
+ return -1;
}
- fprintf(stderr,
- "%s: error: strtodur failed scanning decimal part '%s' as a number: %s\n",
- argv0,
- s,
- errstr);
- return -1;
- }
-
- in += fraction;
- s += parsed;
- }
+ if(parsed == 0) parsed++;
- if(s[0] != '\0' && s[0] != ',' && s[0] != '.')
- {
- if(s[1] != '\0')
- {
- fprintf(stderr,
- "%s: error: duration suffix '%s' is too long, should be only one character\n",
- argv0,
- s);
- return -1;
+ in += fraction;
+ s += parsed;
}
- switch(s[0])
+ if(s[0] != '\0' && s[0] != ',' && s[0] != '.')
{
- case 's': // seconds
- break;
- case 'm': // minutes
- in *= 60;
- break;
- case 'h': // hours
- in *= 60 * 60;
- break;
- case 'd': // days
- in *= 24 * 60 * 60;
- break;
- default:
- fprintf(stderr, "%s: error: Unknown duration suffix '%c'\n", argv0, s[0]);
- return -1;
+ if(s[1] != '\0' && !isdigit(s[1]))
+ {
+ fprintf(stderr,
+ "%s: error: duration suffix '%s' is too long, should be only one character\n",
+ argv0,
+ s);
+ return -1;
+ }
+
+ switch(s[0])
+ {
+ case 's': // seconds
+ break;
+ case 'm': // minutes
+ in *= 60;
+ break;
+ case 'h': // hours
+ in *= 60 * 60;
+ break;
+ case 'd': // days
+ in *= 24 * 60 * 60;
+ break;
+ default:
+ fprintf(stderr, "%s: error: Unknown duration suffix '%c'\n", argv0, s[0]);
+ return -1;
+ }
+
+ s++;
}
+
+dot_skip:
+ total += in;
}
- dur->tv_sec = in;
- dur->tv_nsec = (in - dur->tv_sec) * 1000000000;
+ dur->tv_sec = total;
+ dur->tv_nsec = (total - dur->tv_sec) * 1000000000;
return 0;
}
diff --git a/test-lib/t_strtodur.c b/test-lib/t_strtodur.c
@@ -44,11 +44,16 @@ t_strtodur(char *str, time_t ex_sec, long ex_nsec)
int
main(void)
{
- int plan = 16;
+ int plan = 18;
printf("1..%d\n", plan);
// TODO: Capture errors, say with open_memstream(3)
+#define T_NSEC 1000000000
+#define T_MIN 60
+#define T_HOUR 60 * T_MIN
+#define T_DAY 24 * T_HOUR
+
t_strtodur(NULL, 0, 0);
t_strtodur((char *)"", 0, 0);
t_strtodur((char *)",", 0, 0);
@@ -57,18 +62,21 @@ main(void)
t_strtodur((char *)"1.", 1, 0);
t_strtodur((char *)"1,", 1, 0);
- t_strtodur((char *)".1", 0, 1000000000 * 0.1);
- t_strtodur((char *)"0.1", 0, 1000000000 * 0.1);
+ t_strtodur((char *)".1", 0, T_NSEC * 0.1);
+ t_strtodur((char *)"0.1", 0, T_NSEC * 0.1);
t_strtodur((char *)"1s", 1, 0);
- t_strtodur((char *)"1m", 60, 0);
- t_strtodur((char *)"1h", 60 * 60, 0);
- t_strtodur((char *)"1d", 60 * 60 * 24, 0);
-
- t_strtodur((char *)"1.5s", 1, 1000000000 * 0.5);
- t_strtodur((char *)"1.5m", 1.5 * 60, 0);
- t_strtodur((char *)"1.5h", 1.5 * 60 * 60, 0);
- t_strtodur((char *)"1.5d", 1.5 * 60 * 60 * 24, 0);
+ t_strtodur((char *)"1m", T_MIN, 0);
+ t_strtodur((char *)"1h", 1 * T_HOUR, 0);
+ t_strtodur((char *)"1d", 1 * T_DAY, 0);
+ t_strtodur((char *)"1d1h1m1s", (1 * T_DAY) + (1 * T_HOUR) + (1 * T_MIN) + 1, 0);
+
+ t_strtodur((char *)"1.5s", 1, T_NSEC * 0.5);
+ t_strtodur((char *)"1.5m", 1.5 * T_MIN, 0);
+ t_strtodur((char *)"1.5h", 1.5 * T_HOUR, 0);
+ t_strtodur((char *)"1.5d", 1.5 * T_DAY, 0);
+ t_strtodur(
+ (char *)"1.5d1.5h1.5m1.5s", (1.5 * T_DAY) + (1.5 * T_HOUR) + (1.5 * T_MIN) + 1, T_NSEC * 0.5);
assert(counter == plan);
return err;