Menu

#1326 soap_timegm() Windows fallback returns (time_t)-1 for 1970-01-01T00:00:00Z when local TZ is east of UTC

v1.0 (example)
open
nobody
None
5
2026-08-05
2026-08-05
Alim Abaev
No

Summary

On Windows, soap_timegm() (stdsoap2.cpp (sourceforge.net)) (used when HAVE_TIMEGM is not defined) fails to
convert UTC timestamps near the Unix epoch if the process local timezone has
a positive UTC offset (e.g. UTC+3). Parsing the valid xsd:dateTime value
"1970-01-01T00:00:00Z" / "1970-01-01T00:00:00.000000Z" yields (time_t)-1
instead of 0.

This affects both soap_s2dateTime() (time_t) and the custom timeval
serializer (struct_timeval / soap_s2xsd__dateTime), which call soap_timegm().

Environment

  • OS: Windows 10/11
  • Compiler: MSVC (Visual Studio 2022)
  • Timezone: any TZ east of UTC (reproduced with UTC+3)
  • gSOAP: vendored copy based on 2.8.x (soap_timegm without HAVE_TIMEGM)

Repro

  1. Set Windows timezone to UTC+3 (or any UTC+N, N > 0).
  2. Parse xsd:dateTime string: 1970-01-01T00:00:00.000000Z
    (via soap_s2dateTime or soap_s2xsd__dateTime into struct timeval).
  3. Observe result: time_t / tv_sec == (time_t)-1
  4. Expected: 0 (Unix epoch).

Same string on Linux with HAVE_TIMEGM / timegm() correctly yields 0.

Root cause

Windows path of soap_timegm() emulates timegm() as:

  t = mktime(T);          // treats T as LOCAL time
  if (t == (time_t)-1)
    return (time_t)-1;    // fails before TZ correction
  ...
  return t - (g - t);     // intended UTC correction

For T = 1970-01-01 00:00:00, mktime() interprets the fields as local time.
In UTC+3 that corresponds to 1969-12-31 21:00:00 UTC, which is before the
Unix epoch; the CRT returns (time_t)-1. soap_timegm() propagates that error
sentinel even though the input was explicitly UTC ('Z').

Note: the 'Z' / timezone branch in the dateTime parser is correct — it does
call soap_timegm(). The bug is inside the Windows fallback of soap_timegm().

Impact

Callers that convert the resulting timeval/time_t to milliseconds
(seconds * 1000) and store it in an unsigned 64-bit timestamp get
18446744073709550616 (bit pattern of -1000), which then breaks date
libraries (e.g. boost::gregorian::bad_year) when formatting absolute times.

Suggested fix

On Win32/MSVC use the CRT UTC inverse of gmtime:

  #elif defined(_WIN32)
    return _mkgmtime(T);

_mkgmtime / _mkgmtime64 is documented by Microsoft as converting UTC
struct tm to time_t (POSIX timegm equivalent):
https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/mkgmtime-mkgmtime32-mkgmtime64

Minimal change to soap_timegm():

  #if defined(HAVE_TIMEGM)
    return timegm(T);
  #elif defined(_WIN32)
    return _mkgmtime(T);
  #else
    /* existing mktime/gmtime fallback */
  #endif

After this change, 1970-01-01T00:00:00Z must deserialize to 0 on Windows
regardless of the local timezone.

Discussion


Log in to post a comment.