Skip to content

%time{%s} in --write-out returns wrong epoch on non-UTC hosts #22038

Description

@wulin-nudt

I did this

Summary

curl --write-out '%time{%s}' (and %time{%s.%f}) outputs an epoch value that is
offset by the local timezone offset from UTC. On a host in Asia/Shanghai (UTC+8),
the value is consistently 28800 seconds (8 hours) too small.

Other %time{} specifiers such as %Y-%m-%d %H:%M:%S appear correct because they
read the broken-down UTC struct tm fields directly. Only %s is wrong.

The documentation states that %time{} uses UTC and that %s is
"The number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC)."

Steps to reproduce

  1. Set the system timezone to any non-UTC zone, e.g. Asia/Shanghai (UTC+8).

  2. Run:

curl -s -o /dev/null -w 'epoch:%time{%s} date:%time{%Y-%m-%d %H:%M:%S}\n' https://example.com
date +%s
date -u '+%Y-%m-%d %H:%M:%S UTC'
  1. Compare epoch: from curl with date +%s.

I expected the following

  • %time{%s} equals the real Unix epoch from gettimeofday() / date +%s.
  • %time{%s} is consistent with %time{%Y-%m-%d %H:%M:%S} for the same instant.

curl did this instead

Example output on Asia/Shanghai:

epoch:1781510611 date:2026-06-15 16:03:31
1781539411
2026-06-15 16:03:31 UTC
  • date +%s1781539411 (correct)
  • %time{%s}1781510611 (wrong, exactly 28800 seconds less)
  • %time{%Y-%m-%d %H:%M:%S}2026-06-15 16:03:31 (matches real UTC wall clock)

Decoding the wrong epoch:

$ date -u -d @1781510611
2026-06-15 08:03:31 UTC          # 8 hours behind reality

$ date -u -d @1781539411
2026-06-15 16:03:31 UTC          # correct

The date line shows the correct UTC time, but %s does not match that instant.

Root cause

In src/tool_writeout.c, function outtime():

  1. Reads wall time with gettimeofday()secs (correct epoch).
  2. Converts to UTC with curlx_gmtime(secs, &utc).
  3. Passes user format (including %s) to strftime(..., &utc).

%f, %z, and %Z are already expanded manually before strftime().
%s is not, so glibc's strftime() handles it.

On glibc, strftime() with %s calls mktime() on the supplied struct tm,
treating it as local time, not UTC. Because utc holds UTC components,
the result is the epoch for "those wall-clock numbers interpreted as local time",
which is offset by the timezone.

This is the same pattern already avoided for %f / %z / %Z.

Minimal C reproducer (glibc strftime behaviour)

Save as strftime_s_gmtime.c and compile with gcc -o strftime_s_gmtime strftime_s_gmtime.c.

strftime_s_gmtime(reproducer).c

I expected the following

I expected the following

curl -s -o /dev/null -w 'epoch:%time{%s} date:%time{%Y-%m-%d %H:%M:%S}\n' https://example.com
date +%s
date -u '+%Y-%m-%d %H:%M:%S UTC'
  • %time{%s} equals the real Unix epoch from gettimeofday() / date +%s.
  • %time{%s} is consistent with %time{%Y-%m-%d %H:%M:%S} for the same instant.

Suggested fix

Handle %s the same way %f is handled: expand it to the numeric value of
secs from gettimeofday() before calling strftime(). Use lowercase
s only; do not touch %S (seconds field).

patch:

fix-time-s.patch

After this patch, on Asia/Shanghai:

$ curl -s -o /dev/null -w '%time{%s}\n' https://example.com
1781576611
$ date +%s
1781576611

Additional notes

  • Affects any host where localtime offset from UTC is non-zero.
  • Not specific to snap vs source builds; reproducible with vanilla curl 8.20.0.
  • %time{%s.%f} is also fixed because both %s and %f are expanded manually.
  • Using two separate %time{} variables may still differ in the microsecond
    fraction because the clock is read once per %time{} invocation.

References

curl/libcurl version

curl 8.20.0 (x86_64-pc-linux-gnu) libcurl/8.20.0 OpenSSL/3.0.2 ...

operating system

  • OS: Ubuntu 22.04 / Ubuntu Kylin 22.04 (jammy), x86_64
  • libc: glibc 2.35
  • Timezone: Asia/Shanghai (CST, +0800)
  • TZ unset (system default)

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions