Почему `mktime()` неожиданно игнорирует .tm_isdst для некоторых часовых поясов?

При использовании mktime() член .tm_isdst, когда 0 или 1, приводит к struct tm --> time_t разнице в один час или переход на летнее время для многих зон, таких как Америка/Лос-Анджелес.

Это ожидаемо и соответствует большинству часовых поясов. Для 2 struct tm, отличающихся только .tm_isdst = 0 or 1, результат mktime() отличается на дневной сдвиг.

В нескольких зонах, в том числе в тех, в которых используется летнее время (например, Америка/Доусон_Крик), результат mktime(), по-видимому, игнорирует .tm_isdst в большинстве struct tm раз, если только это не тот час из 25-часового дня при переходе на летнее время.

1) Почему некоторые часовые пояса игнорируют .tm_isdst? (Несмотря на то, что они используют летнее время) Возможно, я что-то упустил.

Мое мнение таково, что mktime() не работает для некоторых второстепенных зон, которые получают данные из основных зон.

2) Если действительно ошибка gcc mktime(), каковы следующие шаги?

Примечание. Никаких проблем не наблюдается, когда .tm_idst < 0.


Системный вызов mktime игнорирует флаг tm_isdst здесь не применяется, поскольку struct tm обновляется перед каждым вызовом mktime().

Вывод (прокрутите вниз и обратите внимание на строки * * *)

time_t (and delta from previous) via various .isdst settings.
    Unchanged after localtime(),   -1         ,    0         ,   1  

America/Los_Angeles
  No trouble here, each  delta goes up by 3600 or the one-time 0 for .isdst=0, .isdst=1
  gmt:Sun Oct 25 05:30:00 1970 +0000 GMT   0
local:Sat Oct 24 22:30:00 1970 -0700 PDT   1
time_t:25680600,25680600      , 25680600      , 25684200      , 25680600      , 
  gmt:Sun Oct 25 06:30:00 1970 +0000 GMT   0
local:Sat Oct 24 23:30:00 1970 -0700 PDT   1
time_t:25684200,25684200  3600, 25684200  3600, 25687800  3600, 25684200  3600, 
  gmt:Sun Oct 25 07:30:00 1970 +0000 GMT   0
local:Sun Oct 25 00:30:00 1970 -0700 PDT   1
time_t:25687800,25687800  3600, 25687800  3600, 25691400  3600, 25687800  3600, 
  gmt:Sun Oct 25 08:30:00 1970 +0000 GMT   0
local:Sun Oct 25 01:30:00 1970 -0700 PDT   1
time_t:25691400,25691400  3600, 25695000  7200, 25695000  3600, 25691400  3600, 
  gmt:Sun Oct 25 09:30:00 1970 +0000 GMT   0
local:Sun Oct 25 01:30:00 1970 -0800 PST   0
time_t:25695000,25695000  3600, 25695000     0, 25695000     0, 25691400     0, 
  gmt:Sun Oct 25 10:30:00 1970 +0000 GMT   0
local:Sun Oct 25 02:30:00 1970 -0800 PST   0
time_t:25698600,25698600  3600, 25698600  3600, 25698600  3600, 25695000  3600, 
  gmt:Sun Oct 25 11:30:00 1970 +0000 GMT   0
local:Sun Oct 25 03:30:00 1970 -0800 PST   0
time_t:25702200,25702200  3600, 25702200  3600, 25702200  3600, 25698600  3600, 

Africa/Juba
  Trouble here, delta goes up by 7200, .isdst seems only relevant for the one hour.  else time_t same without regard to `.isdst.

  gmt:Wed Oct 14 17:30:00 1970 +0000 GMT   0
local:Wed Oct 14 20:30:00 1970 +0300 CAST  1
time_t:24773400,24773400      , 24773400      , 24773400      , 24773400      , 
  gmt:Wed Oct 14 18:30:00 1970 +0000 GMT   0
local:Wed Oct 14 21:30:00 1970 +0300 CAST  1
time_t:24777000,24777000  3600, 24777000  3600, 24777000  3600, 24777000  3600, 
  gmt:Wed Oct 14 19:30:00 1970 +0000 GMT   0
local:Wed Oct 14 22:30:00 1970 +0300 CAST  1
time_t:24780600,24780600  3600, 24780600  3600, 24780600  3600, 24780600  3600, 
  gmt:Wed Oct 14 20:30:00 1970 +0000 GMT   0
local:Wed Oct 14 23:30:00 1970 +0300 CAST  1
time_t:24784200,24784200  3600, 24784200  3600, 24787800  7200, 24784200  3600, ***
  gmt:Wed Oct 14 21:30:00 1970 +0000 GMT   0
local:Wed Oct 14 23:30:00 1970 +0200 CAT   0
time_t:24787800,24787800  3600, 24784200     0, 24787800     0, 24784200     0, 
  gmt:Wed Oct 14 22:30:00 1970 +0000 GMT   0
local:Thu Oct 15 00:30:00 1970 +0200 CAT   0
time_t:24791400,24791400  3600, 24791400  7200, 24791400  3600, 24791400  7200, ***
  gmt:Wed Oct 14 23:30:00 1970 +0000 GMT   0
local:Thu Oct 15 01:30:00 1970 +0200 CAT   0
time_t:24795000,24795000  3600, 24795000  3600, 24795000  3600, 24795000  3600, 

America/Dawson_Creek
  Trouble here, delta goes up by 7200, .isdst seems only relevant for the one hour.  else time_t same without regard to `.isdst.
  gmt:Sun Oct 25 05:30:00 1970 +0000 GMT   0
local:Sat Oct 24 22:30:00 1970 -0700 PDT   1
time_t:25680600,25680600      , 25680600      , 25680600      , 25680600      , 
  gmt:Sun Oct 25 06:30:00 1970 +0000 GMT   0
local:Sat Oct 24 23:30:00 1970 -0700 PDT   1
time_t:25684200,25684200  3600, 25684200  3600, 25684200  3600, 25684200  3600, 
  gmt:Sun Oct 25 07:30:00 1970 +0000 GMT   0
local:Sun Oct 25 00:30:00 1970 -0700 PDT   1
time_t:25687800,25687800  3600, 25687800  3600, 25687800  3600, 25687800  3600, 
  gmt:Sun Oct 25 08:30:00 1970 +0000 GMT   0
local:Sun Oct 25 01:30:00 1970 -0700 PDT   1
time_t:25691400,25691400  3600, 25695000  7200, 25695000  7200, 25691400  3600, ***
  gmt:Sun Oct 25 09:30:00 1970 +0000 GMT   0
local:Sun Oct 25 01:30:00 1970 -0800 PST   0
time_t:25695000,25695000  3600, 25695000     0, 25695000     0, 25691400     0, 
  gmt:Sun Oct 25 10:30:00 1970 +0000 GMT   0
local:Sun Oct 25 02:30:00 1970 -0800 PST   0
time_t:25698600,25698600  3600, 25698600  3600, 25698600  3600, 25698600  7200, ***
  gmt:Sun Oct 25 11:30:00 1970 +0000 GMT   0
local:Sun Oct 25 03:30:00 1970 -0800 PST   0
time_t:25702200,25702200  3600, 25702200  3600, 25702200  3600, 25702200  3600, 

America/Fort_Nelson
  Trouble here, delta goes up by 7200, .isdst seems only relevant for the one hour.  else time_t same without regard to `.isdst.
  gmt:Sun Oct 25 05:30:00 1970 +0000 GMT   0
local:Sat Oct 24 22:30:00 1970 -0700 PDT   1
time_t:25680600,25680600      , 25680600      , 25680600      , 25680600      , 
  gmt:Sun Oct 25 06:30:00 1970 +0000 GMT   0
local:Sat Oct 24 23:30:00 1970 -0700 PDT   1
time_t:25684200,25684200  3600, 25684200  3600, 25684200  3600, 25684200  3600, 
  gmt:Sun Oct 25 07:30:00 1970 +0000 GMT   0
local:Sun Oct 25 00:30:00 1970 -0700 PDT   1
time_t:25687800,25687800  3600, 25687800  3600, 25687800  3600, 25687800  3600, 
  gmt:Sun Oct 25 08:30:00 1970 +0000 GMT   0
local:Sun Oct 25 01:30:00 1970 -0700 PDT   1
time_t:25691400,25691400  3600, 25695000  7200, 25695000  7200, 25691400  3600, ***
  gmt:Sun Oct 25 09:30:00 1970 +0000 GMT   0
local:Sun Oct 25 01:30:00 1970 -0800 PST   0
time_t:25695000,25695000  3600, 25695000     0, 25695000     0, 25691400     0, 
  gmt:Sun Oct 25 10:30:00 1970 +0000 GMT   0
local:Sun Oct 25 02:30:00 1970 -0800 PST   0
time_t:25698600,25698600  3600, 25698600  3600, 25698600  3600, 25698600  7200, ***
  gmt:Sun Oct 25 11:30:00 1970 +0000 GMT   0
local:Sun Oct 25 03:30:00 1970 -0800 PST   0
time_t:25702200,25702200  3600, 25702200  3600, 25702200  3600, 25702200  3600, 

America/Punta_Arenas
  Trouble here, delta goes up by 7200, .isdst seems only relevant for the one hour.  else time_t same without regard to `.isdst.

  gmt:Sat Mar 28 23:30:00 1970 +0000 GMT   0
local:Sat Mar 28 20:30:00 1970 -0300 -03   1
time_t: 7515000, 7515000      ,  7515000      ,  7515000      ,  7515000      , 
  gmt:Sun Mar 29 00:30:00 1970 +0000 GMT   0
local:Sat Mar 28 21:30:00 1970 -0300 -03   1
time_t: 7518600, 7518600  3600,  7518600  3600,  7518600  3600,  7518600  3600, 
  gmt:Sun Mar 29 01:30:00 1970 +0000 GMT   0
local:Sat Mar 28 22:30:00 1970 -0300 -03   1
time_t: 7522200, 7522200  3600,  7522200  3600,  7522200  3600,  7522200  3600, 
  gmt:Sun Mar 29 02:30:00 1970 +0000 GMT   0
local:Sat Mar 28 23:30:00 1970 -0300 -03   1
time_t: 7525800, 7525800  3600,  7529400  7200,  7529400  7200,  7525800  3600, ***
  gmt:Sun Mar 29 03:30:00 1970 +0000 GMT   0
local:Sat Mar 28 23:30:00 1970 -0400 -04   0
time_t: 7529400, 7529400  3600,  7529400     0,  7529400     0,  7525800     0, 
  gmt:Sun Mar 29 04:30:00 1970 +0000 GMT   0
local:Sun Mar 29 00:30:00 1970 -0400 -04   0
time_t: 7533000, 7533000  3600,  7533000  3600,  7533000  3600,  7533000  7200, ***
  gmt:Sun Mar 29 05:30:00 1970 +0000 GMT   0
local:Sun Mar 29 01:30:00 1970 -0400 -04   0
time_t: 7536600, 7536600  3600,  7536600  3600,  7536600  3600,  7536600  3600, 

Тестовый код

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

// Some code to set the time zone
extern void tzset(void);
int set_tz(const char *tz) {
  //tz = getenv("TZ");
  //if (tz) {
  printf("%s\n", tz);
  if (setenv("TZ", tz, 1))
    return 1;
  tzset();
  return 0;
}

// Print time info around the potential mistake    
void time_oops(const char *tz, time_t t0, time_t t1) {
  time_t before[4] = {0};
  if (set_tz(tz)) {
    return;
  }
  set_tz(tz);
  for (time_t t = t0; t <= t1; t += 3600) {

    char buf[200];
    struct tm *tm = gmtime(&t);
    strftime(buf, sizeof buf, "%c %z %Z", tm);
    printf("  gmt:%-36s %d\n", buf, tm->tm_isdst);

    tm = localtime(&t);
    strftime(buf, sizeof buf, "%c %z %Z", tm);
    printf("local:%-36s %d\n", buf, tm->tm_isdst);

    printf("time_t:%8jd,", (intmax_t) t);
    for (int dst = -2; dst <= 1; dst++) {
      tm = localtime(&t);
      if (dst > -2)
        tm->tm_isdst = dst;
      time_t t_make = mktime(tm);
      printf("%8jd", (intmax_t) t_make);
      if (t == t0) {
        printf(" %5s, ", "");
      } else {
        printf(" %5jd, ", (intmax_t) (t_make - before[dst + 2]));
      }
      before[dst + 2] = t_make;
    }
    puts("");
  }
}

// Run some tests    
int main(void) {
  int h = 60 * 60;
  time_oops("America/Los_Angeles", 25662600 + 5 * h, 25662600 + 11 * h);
  time_oops("Africa/Juba", 24784200 - 3 * h, 24784200 + 3 * h);
  time_oops("America/Dawson_Creek", 25662600 + 5 * h, 25662600 + 11 * h);
  time_oops("America/Fort_Nelson", 25687800 - 2 * h, 25687800 + 4 * h);
  time_oops("America/Punta_Arenas", 7522200 - 2 * h, 7522200 + 4 * h);
  return 0;
}

Некоторый вывод компилятора

make all 
Building file: ../test.c
Invoking: Cygwin C Compiler
gcc -O0 -g3 -pedantic -Wall -Wextra -Wconversion -c -fmessage-length=0 -v -MMD -MP -MF"test.d" -MT"test.o" -o "test.o" "../test.c"
Using built-in specs.
COLLECT_GCC=gcc
Target: x86_64-pc-cygwin
Configured with: /cygdrive/i/szsz/tmpp/gcc/gcc-7.3.0-3.x86_64/src/gcc-7.3.0/configure --srcdir=/cygdrive/i/szsz/tmpp/gcc/gcc-7.3.0-3.x86_64/src/gcc-7.3.0 --prefix=/usr --exec-prefix=/usr --localstatedir=/var --sysconfdir=/etc --docdir=/usr/share/doc/gcc --htmldir=/usr/share/doc/gcc/html -C --build=x86_64-pc-cygwin --host=x86_64-pc-cygwin --target=x86_64-pc-cygwin --without-libiconv-prefix --without-libintl-prefix --libexecdir=/usr/lib --enable-shared --enable-shared-libgcc --enable-static --enable-version-specific-runtime-libs --enable-bootstrap --enable-__cxa_atexit --with-dwarf2 --with-tune=generic --enable-languages=ada,c,c++,fortran,lto,objc,obj-c++ --enable-graphite --enable-threads=posix --enable-libatomic --enable-libcilkrts --enable-libgomp --enable-libitm --enable-libquadmath --enable-libquadmath-support --disable-libssp --enable-libada --disable-symvers --with-gnu-ld --with-gnu-as --with-cloog-include=/usr/include/cloog-isl --without-libiconv-prefix --without-libintl-prefix --with-system-zlib --enable-linker-build-id --with-default-libstdcxx-abi=gcc4-compatible --enable-libstdcxx-filesystem-ts
Thread model: posix
gcc version 7.3.0 (GCC) 
COLLECT_GCC_OPTIONS='-O0' '-g3' '-Wpedantic' '-Wall' '-Wextra' '-Wconversion' '-c' '-fmessage-length=0' '-v' '-MMD' '-MP' '-MF' 'test.d' '-MT' 'test.o' '-o' 'test.o' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-pc-cygwin/7.3.0/cc1.exe -quiet -v -MMD test.d -MF test.d -MP -MT test.o -dD -idirafter /usr/lib/gcc/x86_64-pc-cygwin/7.3.0/../../../../lib/../include/w32api -idirafter /usr/lib/gcc/x86_64-pc-cygwin/7.3.0/../../../../x86_64-pc-cygwin/lib/../lib/../../include/w32api ../test.c -quiet -dumpbase test.c -mtune=generic -march=x86-64 -auxbase-strip test.o -g3 -O0 -Wpedantic -Wall -Wextra -Wconversion -version -fmessage-length=0 -o /cygdrive/c/Users/TPC/AppData/Local/Temp/cc3JcC90.s
GNU C11 (GCC) version 7.3.0 (x86_64-pc-cygwin)
    compiled by GNU C version 7.3.0, GMP version 6.1.2, MPFR version 4.0.1-p6, MPC version 1.1.0, isl version isl-0.16.1-GMP

Я использовал zdump для дампа данных о зонах, но не обнаружил разницы между зонами, которые работают должным образом, и теми, которые не работают.
Некоторый дамп данных часовых поясов.

zdump -v /usr/share/zoneinfo/America/Los_Angeles
zdump -v /usr/share/zoneinfo/America/Dawson_Creek

/usr/share/zoneinfo/America/Los_Angeles Sun Apr 26 09:59:59 1970 UT = Sun Apr 26 01:59:59 1970 PST isdst=0 gmtoff=-28800
/usr/share/zoneinfo/America/Los_Angeles Sun Apr 26 10:00:00 1970 UT = Sun Apr 26 03:00:00 1970 PDT isdst=1 gmtoff=-25200
/usr/share/zoneinfo/America/Los_Angeles Sun Oct 25 08:59:59 1970 UT = Sun Oct 25 01:59:59 1970 PDT isdst=1 gmtoff=-25200
/usr/share/zoneinfo/America/Los_Angeles Sun Oct 25 09:00:00 1970 UT = Sun Oct 25 01:00:00 1970 PST isdst=0 gmtoff=-28800
/usr/share/zoneinfo/America/Dawson_Creek Sun Apr 26 09:59:59 1970 UT = Sun Apr 26 01:59:59 1970 PST isdst=0 gmtoff=-28800
/usr/share/zoneinfo/America/Dawson_Creek Sun Apr 26 10:00:00 1970 UT = Sun Apr 26 03:00:00 1970 PDT isdst=1 gmtoff=-25200
/usr/share/zoneinfo/America/Dawson_Creek Sun Oct 25 08:59:59 1970 UT = Sun Oct 25 01:59:59 1970 PDT isdst=1 gmtoff=-25200
/usr/share/zoneinfo/America/Dawson_Creek Sun Oct 25 09:00:00 1970 UT = Sun Oct 25 01:00:00 1970 PST isdst=0 gmtoff=-28800

person chux - Reinstate Monica    schedule 23.08.2018    source источник
comment
Иногда мне интересно, задают ли люди здесь вопросы, чтобы оценить общественную реакцию на ошибку, которую кто-то отказывается признать... пожалуйста, скажите мне, что это не один из таких случаев? Чтобы было ясно, я читал некоторые руководства и так далее, и мне это тоже кажется ошибкой... следующий шаг, о котором вы спрашивали, ну, ответ - сообщить об этом как об ошибке. У них может быть рациональное объяснение, но я думаю, что программирование, связанное со временем, — это просто сложная задача, которую нужно решить правильно, поскольку некоторые юрисдикции просто случайным образом изобретают дополнительные секунды, которые вам нужно добавить, например... это неправильно с самого начала. идти.   -  person autistic    schedule 24.08.2018
comment
@autistic это не один из тех случаи, это проблема, обнаруженная в моем кодировании в этом месяце. сообщить об этом как об ошибке. --› Хорошо, но я не знаю, как, где и кто они. Ваши идеи будут оценены. В сторону: я не вижу високосных секунд как часть проблемы.   -  person chux - Reinstate Monica    schedule 24.08.2018
comment
Я также не знаком с тем, как, где и кто они, но я предполагаю, что, поскольку вы используете gcc, это будет в каком-то списке рассылки, связанном с glibc, или в репозитории git, или что-то в этом роде... вы можете узнать о тем не менее, улучшая свой тестовый пример. Может быть хорошей идеей использовать assert() для кодирования того, что ожидается, а не printf для распечатки данных и полагаться на человеческий мозг, чтобы сделать вывод о том, что ожидается. Не уверен, что это тот ответ, который вы ищете, но я заметил, что вы вызываете tzset, а в руководстве POSIX говорится, что информация о местном часовом поясе должна быть установлена ​​так, как будто mktime() вызывается tzset().   -  person autistic    schedule 24.08.2018
comment
Да, чем больше я смотрю на это, тем больше я думаю, что вы наткнулись на ошибку... но вы также создали ее сами... Я не думаю, что вам нужно устанавливать здесь элемент tm_isdst, и Я не думаю, что -2 для этого поля означает то, что вы думаете... Этот тест немного больше смысл; он демонстрирует, как localtime должен устанавливать tm_isdst (либо 1, чтобы указать, что ОС точно перешла на летнее время, либо -1, чтобы указать, что информация недоступна/неопределенна). Думаю, та же ошибка; точно так же, как localtime должен установить, mktime не должен игнорировать.   -  person autistic    schedule 24.08.2018
comment
Если вы хотите сообщить об ошибках в glibc, вы можете перейти на домашнюю страницу GNU glibc и перейдите по ссылке Принять участие, где есть дополнительная ссылка о том, как сортировать и сообщать об ошибках. Обратите внимание, что Glibc несколько независим от компилятора. Вы также, кажется, используете Cygwin; это не обязательно ошибка в Glibc, но может быть проблемой в Cygwin. У меня нет доказательств ни в том, ни в другом случае — я просто привожу возможности.   -  person Jonathan Leffler    schedule 24.08.2018