Системный вызов mktime игнорирует флаг tm_isdst

еще один вопрос относительно mktime и DST

Linux, Ubuntu, часовой пояс установлен на Европу/Берлин, т.е. текущее время CEST:

>date
Mon Aug 22 16:08:10 CEST 2016
>date --utc
Mon Aug 22 14:08:14 UTC 2016

пока все в порядке.

Теперь я пытаюсь запустить следующий код:

#include <stdio.h>
#include <time.h>
int main()
{

    struct tm   tm = {0};
    int secs;

    tm.tm_sec = 0;
    tm.tm_min = 0;
    tm.tm_hour = 12;
    tm.tm_mon = 9 - 1;
    tm.tm_mday = 30;
    tm.tm_year = 2016 - 1900;

    tm.tm_isdst = 0;
    secs = mktime(&tm);
    printf("%i\n", secs);

    tm.tm_isdst = 1;
    secs = mktime(&tm);
    printf("%i\n", secs);

    tm.tm_isdst = -1;
    secs = mktime(&tm);
    printf("%i\n", secs);

    return 0;
}

и получить

1475233200
1475233200
1475233200

что во всех трех случаях неверно (смещение на 1 час):

>date -d @1475233200
Fri Sep 30 13:00:00 CEST 2016

Итак, я немного озадачен, мой часовой пояс как-то нарушен? Почему флаг tm_isdst полностью игнорируется?

Редактировать: @Nominal Animal дал ответ: mktime изменяет tm_hour! Интересно, где это задокументировано?!

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

void reset(struct tm* tm){
    (*tm) = (const struct tm){0};

    tm->tm_sec = 0;
    tm->tm_min = 0;
    tm->tm_hour = 12;
    tm->tm_mon = 9 - 1;
    tm->tm_mday = 30;
    tm->tm_year = 2016 - 1900;
}

int main()
{

    struct tm   tm;
    int secs;

    reset(&tm);
    tm.tm_isdst = 0;
    secs = mktime(&tm);
    printf("%i\n", secs);

    reset(&tm);
    tm.tm_isdst = 1;
    secs = mktime(&tm);
    printf("%i\n", secs);

    reset(&tm);    
    tm.tm_isdst = -1;
    secs = mktime(&tm);
    printf("%i\n", secs);

    return 0;
}

дает

1475233200
1475229600
1475229600

person Stasik    schedule 22.08.2016    source источник
comment
1) Поведение кажется странным. Чтобы лучше устранять неполадки, снова сбросьте все поля (или, по крайней мере, распечатайте их) после вызова mktime(&tm);, так как mktime() может настроить tm поля. 2) Используйте соответствующий спецификатор формата, например printf("%i\n", (int) secs); или printf("%lld\n", (long long) secs);, чтобы избежать неопределенного поведения.   -  person chux - Reinstate Monica    schedule 22.08.2016
comment
Это может казаться странным, но на самом деле это не так. Действительно, mktime() изменяет свой аргумент, в частности, tm.tm_hour и tm.tm_isdst в данном случае.   -  person Nominal Animal    schedule 22.08.2016
comment
@Nominal Animal Что такое odd, так это то, что mktime() не изменит tm, поскольку tm находится в основном диапазоне в первых двух случаях. ааа может быть, не в первом случае. Хммм - летнее время 0 может измениться на 1.   -  person chux - Reinstate Monica    schedule 22.08.2016
comment
@chux, не могли бы вы опубликовать ответ, чтобы я мог принять, кажется, это работает .. действительно странно это   -  person Stasik    schedule 22.08.2016
comment
@NominalAnimal, не могли бы вы показать, где задокументировано, что tm_hour изменен! похоже в этом суть   -  person Stasik    schedule 22.08.2016
comment
@Stasik: mktime() всегда меняет tm_isdst на 0 или 1. Поведение описано (как я объяснил в своем ответе) в man 3 mktime, а также в гораздо более сокращенной форме в POSIX .1.   -  person Nominal Animal    schedule 22.08.2016


Ответы (2)


Думаю, теперь я понимаю, как это может сбить с толку. Думайте о mktime() как о подписи

time_t mktime_actual(struct tm *dst, const struct tm *src);

где результат time_t рассчитывается на основе (нормализованного) *src, а нормализованные поля и то, применяется ли в это время летнее время, сохраняются в *dst.

Просто разработчики языка C исторически предпочитали использовать только один указатель, комбинируя как src, так и dst. Однако приведенная выше логика остается в силе.

См. справочную страницу `man mktime, особенно эту часть:

Функция mktime() преобразует структуру времени в разбивке, выраженную как местное время, в представление календарного времени. Функция игнорирует значения, предоставленные вызывающей стороной в полях tm_wday и tm_yday. Значение, указанное в поле tm_isdst, сообщает mktime(), действует ли летнее время (DST) для времени, указанного в структуре tm: положительное значение означает, что действует летнее время; ноль означает, что летнее время не действует; а отрицательное значение означает, что mktime() должна (используя информацию о часовом поясе и системных базах данных) попытаться определить, действует ли летнее время в указанное время.

Функция mktime() изменяет поля структуры tm следующим образом: tm_wday и tm_yday устанавливаются на значения, определяемые из содержимого других полей; если элементы структуры находятся за пределами допустимого интервала, они будут нормализованы (так, например, 40 октября заменяется на 9 ноября); tm_isdst устанавливается (независимо от его начального значения) в положительное значение или в 0, соответственно, чтобы указать, действует ли летнее время или нет в указанное время. Вызов mktime() также устанавливает внешнюю переменную tzname с информацией о текущем часовом поясе.

Если указанное время в разбивке не может быть представлено как календарное время (секунды с начала Эпохи), mktime() возвращает (time_t) -1 и не изменяет элементы структуры времени в разбивке.

Другими словами, если вы немного измените свою тестовую программу, скажем, на

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

static const char *dst(const int flag)
{
    if (flag > 0)
        return "(>0: is DST)";
    else
    if (flag < 0)
        return "(<0: Unknown if DST)";
    else
        return "(=0: not DST)";
}

static struct tm newtm(const int year, const int month, const int day,
                       const int hour, const int min, const int sec,
                       const int isdst)
{
    struct tm t = { .tm_year  = year - 1900,
                    .tm_mon   = month - 1,
                    .tm_mday  = day,
                    .tm_hour  = hour,
                    .tm_min   = min,
                    .tm_sec   = sec,
                    .tm_isdst = isdst };
    return t;
}

int main(void)
{
    struct tm   tm = {0};
    time_t secs;

    tm = newtm(2016,9,30, 12,0,0, -1);
    secs = mktime(&tm);
    printf("-1: %04d-%02d-%02d %02d:%02d:%02d %s %lld\n",
           tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
           tm.tm_hour, tm.tm_min, tm.tm_sec, dst(tm.tm_isdst), (long long)secs);

    tm = newtm(2016,9,30, 12,0,0, 0);
    secs = mktime(&tm);
    printf(" 0: %04d-%02d-%02d %02d:%02d:%02d %s %lld\n",
           tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
           tm.tm_hour, tm.tm_min, tm.tm_sec, dst(tm.tm_isdst), (long long)secs);

    tm = newtm(2016,9,30, 12,0,0, 1);
    secs = mktime(&tm);
    printf("+1: %04d-%02d-%02d %02d:%02d:%02d %s %lld\n",
           tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
           tm.tm_hour, tm.tm_min, tm.tm_sec, dst(tm.tm_isdst), (long long)secs);

    return EXIT_SUCCESS;
}

затем его запуск производит вывод

-1: 2016-09-30 12:00:00 (>0: is DST) 1475226000
 0: 2016-09-30 13:00:00 (>0: is DST) 1475229600
+1: 2016-09-30 12:00:00 (>0: is DST) 1475226000

Другими словами, он ведет себя именно так, как описано (в цитате выше). Такое поведение задокументировано в C89, C99 и POSIX.1 (я думаю, что и в C11, но не проверял).

person Nominal Animal    schedule 22.08.2016

При успешном завершении значения компонентов tm_wday и tm_yday структуры устанавливаются соответствующим образом, а другие компоненты задаются для представления указанного календарного времени, ... C11dr §7.27.2.3 2

При вызове mktime(&tm) исходные значения tm не ограничиваются диапазоном.

Из-за первого вызова mktime(&tm), конечно, tm.tm_isdst и tm.tm_hour были изменены на 1 и 11. Таким образом, следующий код OP tm.tm_isdst = 1; и tm.tm_isdst = -1; не повлиял на отметку времени.

Лучше установить все поля для исследования.

struct tm   tm0 = {0};
struct tm   tm;
int secs;

tm0.tm_sec = 0;
tm0.tm_min = 0;
tm0.tm_hour = 12;
tm0.tm_mon = 9 - 1;
tm0.tm_mday = 30;
tm0.tm_year = 2016 - 1900;

tm = tm0;
tm.tm_isdst = 0;
secs = mktime(&tm);
printf("%i\n", (int) secs);

tm = tm0;
tm.tm_isdst = 1;
secs = mktime(&tm);
printf("%i\n", (int) secs);

tm = tm0;
tm.tm_isdst = -1;
secs = mktime(&tm);
printf("%i\n", (int) secs);
person chux - Reinstate Monica    schedule 22.08.2016