From: Daniel Stenberg Date: Thu, 31 Jul 2025 14:41:36 +0000 (+0200) Subject: writeout: add %time{} X-Git-Tag: curl-8_16_0~260 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=fadc4875670a0b0d484b710baa91b353957f4fe4;p=thirdparty%2Fcurl.git writeout: add %time{} Output the current UTC time using strftime format. %f is an extra curl specific flag to output the microsecond fraction of the current second. Verified by test 1981 Closes #18119 --- diff --git a/docs/cmdline-opts/write-out.md b/docs/cmdline-opts/write-out.md index f5944cc526..4f2f99dc4d 100644 --- a/docs/cmdline-opts/write-out.md +++ b/docs/cmdline-opts/write-out.md @@ -220,6 +220,10 @@ From this point on, the --write-out output is written to standard output. This is the default, but can be used to switch back after switching to stderr. (Added in 7.63.0) +## `time{format}` +Output the current UTC time using `strftime()` format. See TIME OUTPUT FORMAT +below for details. (Added in 8.16.0) + ## `time_appconnect` The time, in seconds, it took from the start until the SSL/SSH/etc connect/handshake to the remote host was completed. (Added in 7.19.0) @@ -347,3 +351,185 @@ The numerical identifier of the last transfer done. -1 if no transfer has been started yet for the handle. The transfer id is unique among all transfers performed using the same connection cache. (Added in 8.2.0) + +## + +TIME OUTPUT FORMAT + +When showing time with `%time{}`, the following output qualifiers are +available: + +## `%a` + +The abbreviated name of the day of the week according to the current locale. + +## `%A` + +The full name of the day of the week according to the current locale. + +## `%b` + +The abbreviated month name according to the current locale. + +## `%B` + +The full month name according to the current locale. + +## `%c` + +The preferred date and time representation for the current locale. (In the +POSIX locale this is equivalent to %a %b %e %H:%M:%S %Y.) + +## `%C` + +The century number (year/100) as a 2-digit integer. + +## `%d` + +The day of the month as a decimal number (range 01 to 31). + +## `%D` + +Equivalent to %m/%d/%y. In international contexts, this format is ambiguous +and should be avoided.) + +## `%e` + +Like %d, the day of the month as a decimal number, but a leading zero is +replaced by a space. + +## `%f` + +The number of microseconds elapsed of the current second. (This a curl special +code and not a standard one.) + +## `%F` + +Equivalent to %Y-%m-%d (the ISO 8601 date format). + +## `%G` + +The ISO 8601 week-based year with century as a decimal number. The 4-digit +year corresponding to the ISO week number (see %V). This has the same format +and value as %Y, except that if the ISO week number belongs to the previous or +next year, that year is used instead. + +## `%g` + +Like `%G`, but without century, that is, with a 2-digit year (00-99). + +## `%h` + +Equivalent to `%b`. + +## `%H` + +The hour as a decimal number using a 24-hour clock (range 00 to 23). + +## `%I` + +The hour as a decimal number using a 12-hour clock (range 01 to 12). + +## `%j` + +The day of the year as a decimal number (range 001 to 366). + +## `%k` + +The hour (24-hour clock) as a decimal number (range 0 to 23); single digits +are preceded by a blank. + +## `%l` + +The hour (12-hour clock) as a decimal number (range 1 to 12); single digits +are preceded by a blank. + +## `%m` + +The month as a decimal number (range 01 to 12). + +## `%M` + +The minute as a decimal number (range 00 to 59). + +## `%p` + +Either "AM" or "PM" according to the given time value, or the corresponding +strings for the current locale. Noon is treated as "PM" and midnight as "AM". + +## `%P` + +Like %p but in lowercase: "am" or "pm" or a corresponding string for the +current locale. + +## `%r` + +The time in am or pm notation. + +## `%R` + +The time in 24-hour notation (%H:%M). For a version including the seconds, see +`%T` below. + +## `%s` + +The number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). + +## `%S` + +The second as a decimal number (range 00 to 60). (The range is up to 60 to +allow for occasional leap seconds.) + +## `%T` + +The time in 24-hour notation (%H:%M:%S). + +## `%u` + +The day of the week as a decimal, range 1 to 7, Monday being 1. + +## `%U` + +The week number of the current year as a decimal number, range 00 to 53, +starting with the first Sunday as the first day of week 01. See also `%V` and +`%W`. + +## `%V` + +The ISO 8601 week number (see NOTES) of the current year as a decimal number, +range 01 to 53, where week 1 is the first week that has at least 4 days in the +new year. See also `%U` and `%W`. + +## `%w` + +The day of the week as a decimal, range 0 to 6, Sunday being 0. See also `%u`. + +## `%W` + +The week number of the current year as a decimal number, range 00 to 53, +starting with the first Monday as the first day of week 01. + +## `%x` + +The preferred date representation for the current locale without the time. + +## `%X` + +The preferred time representation for the current locale without the date. + +## `%y` + +The year as a decimal number without a century (range 00 to 99). + +## `%Y` + +The year as a decimal number including the century. + +## `%z` + +The `+hhmm` or `-hhmm` numeric timezone (that is, the hour and minute offset +from UTC). As time is always UTC, this outputs `+0000`. + +## `%Z` + +The timezone name. For some reason `GMT`. diff --git a/src/tool_writeout.c b/src/tool_writeout.c index dd69e3bd02..7e38b1a206 100644 --- a/src/tool_writeout.c +++ b/src/tool_writeout.c @@ -539,6 +539,81 @@ matchvar(const void *m1, const void *m2) #define MAX_WRITEOUT_NAME_LENGTH 24 +/* return the position after %time{} */ +static const char *outtime(const char *ptr, /* %time{ ... */ + FILE *stream) +{ + const char *end; + ptr += 6; + end = strchr(ptr, '}'); + if(end) { + struct tm *utc; + struct dynbuf format; + char output[256]; /* max output time length */ +#ifdef HAVE_GETTIMEOFDAY + struct timeval cnow; +#else + struct curltime cnow; +#endif + time_t secs; + unsigned int usecs; + size_t i; + size_t vlen; + CURLcode result = CURLE_OK; + +#ifdef HAVE_GETTIMEOFDAY + gettimeofday(&cnow, NULL); +#else + cnow.tv_sec = time(NULL); + cnow.tv_usec = 0; +#endif + secs = cnow.tv_sec; + usecs = (unsigned int)cnow.tv_usec; +#ifdef DEBUGBUILD + { + const char *timestr = getenv("CURL_TIME"); + if(timestr) { + curl_off_t val; + curlx_str_number(×tr, &val, TIME_T_MAX); + secs = (time_t)val; + usecs = (unsigned int)(val % 1000000); + } + } +#endif + vlen = end - ptr; + curlx_dyn_init(&format, 1024); + + /* insert sub-seconds for %f */ + /* insert +0000 for %z because it is otherwise not portable */ + /* insert UTC for %Z because it is otherwise not portable */ + for(i = 0; !result && i < vlen; i++) { + if((i < vlen - 1) && ptr[i] == '%' && + ((ptr[i + 1] == 'f') || ((ptr[i + 1] | 0x20) == 'z'))) { + if(ptr[i + 1] == 'f') + result = curlx_dyn_addf(&format, "%06u", usecs); + else if(ptr[i + 1] == 'Z') + result = curlx_dyn_addn(&format, "UTC", 3); + else + result = curlx_dyn_addn(&format, "+0000", 5); + i++; + } + else + result = curlx_dyn_addn(&format, &ptr[i], 1); + } + if(!result) { + /* !checksrc! disable BANNEDFUNC 1 */ + utc = gmtime(&secs); + strftime(output, sizeof(output), curlx_dyn_ptr(&format), utc); + fputs(output, stream); + curlx_dyn_free(&format); + } + ptr = end + 1; + } + else + fputs("%time{", stream); + return ptr; +} + void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, CURLcode per_result) { @@ -640,6 +715,9 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, else fputs("%header{", stream); } + else if(!strncmp("time{", &ptr[1], 5)) { + ptr = outtime(ptr, stream); + } else if(!strncmp("output{", &ptr[1], 7)) { bool append = FALSE; ptr += 8; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 98e2829231..379ede24a8 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -238,7 +238,7 @@ test1933 test1934 test1935 test1936 test1937 test1938 test1939 test1940 \ test1941 test1942 test1943 test1944 test1945 test1946 test1947 test1948 \ test1955 test1956 test1957 test1958 test1959 test1960 test1964 \ test1970 test1971 test1972 test1973 test1974 test1975 test1976 test1977 \ -test1978 test1979 test1980 \ +test1978 test1979 test1980 test1981 \ \ test2000 test2001 test2002 test2003 test2004 test2005 \ \ diff --git a/tests/data/test1981 b/tests/data/test1981 new file mode 100644 index 0000000000..faf061c1d1 --- /dev/null +++ b/tests/data/test1981 @@ -0,0 +1,62 @@ + + + +HTTP +HTTP GET + + + +# +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +ETag: "21025-dc7-39462498" +Accept-Ranges: bytes +Content-Length: 6 +Connection: close +Content-Type: text/html +Funny-head: yesyes + +-foo- + + + +# +# Client-side + + +http + + +%time output with --write-out + + +Debug + + +CURL_TIME=1754037103 + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --write-out='Time: %time{%d/%b/%Y %H:%M:%S.%f %z %Z}\n' -s -o %LOGDIR/dump + + + +# +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +Time: 01/Aug/2025 08:31:43.037103 +0000 UTC + + + diff --git a/tests/test1486.pl b/tests/test1486.pl index 4ff55c3b12..f679261061 100755 --- a/tests/test1486.pl +++ b/tests/test1486.pl @@ -54,11 +54,22 @@ sub getsrcvars { close($f); } +my %special = ( + 'header{name}' => 1, + 'output{filename}' => 1, + 'time{format}' => 1, + ); + sub getdocsvars { open(my $f, "<", "$root/../docs/cmdline-opts/write-out.md"); while(<$f>) { - if($_ =~ /^\#\# \`([^\`]*)\`/) { - if($1 ne "header{name}" && $1 ne "output{filename}") { + chomp; + $_ =~ s/[\r\n]//g; + if($_ =~ /^\#\# *\z/) { + last; + } + elsif($_ =~ /^\#\# \`([^\`]*)\`/) { + if(!$special{$1}) { $indocs{$1} = 1; } }