]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
writeout: add %time{}
authorDaniel Stenberg <daniel@haxx.se>
Thu, 31 Jul 2025 14:41:36 +0000 (16:41 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 4 Aug 2025 21:45:48 +0000 (23:45 +0200)
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

docs/cmdline-opts/write-out.md
src/tool_writeout.c
tests/data/Makefile.am
tests/data/test1981 [new file with mode: 0644]
tests/test1486.pl

index f5944cc526e536ca86de9c866f80192f6adce112..4f2f99dc4d9e1198548d428f2c847924ea770482 100644 (file)
@@ -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`.
index dd69e3bd0239f31ebb12c9b7a520f71b8d91e886..7e38b1a2067e4baba2e973af5b08a8864f1b3d6f 100644 (file)
@@ -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(&timestr, &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;
index 98e282923166db45da5d580488964a3c5b59dcce..379ede24a8a309aea1f6925d8ff3425a1663bdcb 100644 (file)
@@ -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 (file)
index 0000000..faf061c
--- /dev/null
@@ -0,0 +1,62 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data crlf="yes" nocheck="yes">
+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-
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+http
+</server>
+<name>
+%time output with --write-out
+</name>
+<features>
+Debug
+</features>
+<setenv>
+CURL_TIME=1754037103
+</setenv>
+<command>
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER --write-out='Time: %time{%d/%b/%Y %H:%M:%S.%f %z %Z}\n' -s -o %LOGDIR/dump
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol crlf="yes">
+GET /%TESTNUMBER HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+User-Agent: curl/%VERSION
+Accept: */*
+
+</protocol>
+<stdout mode="text">
+Time: 01/Aug/2025 08:31:43.037103 +0000 UTC
+</stdout>
+</verify>
+</testcase>
index 4ff55c3b12cb3cf09b3fdff0dfefa28f34b80840..f6792610616720d979da1e4a4c96b171e1d9ccc2 100755 (executable)
@@ -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;
             }
         }