C ++: ошибка mktime под MinGW в Windows Vista? - PullRequest
0 голосов
/ 04 августа 2011

Поэтому я пытаюсь преобразовать даты в формате «2000-01-01» в целые числа, представляющие количество дней с какого-либо произвольного происхождения (например, 1900/01/01), чтобы я мог рассматривать их как целочисленные индексы.Для этого я написал функцию преобразования, которая прекрасно работает на MinGW под Windows XP, но не под Vista.Я добавил некоторый регистрационный код:

int dateStrToInt(string date) {
        int ymd[3];
        tm tm1, tm0;
        istringstream iss(date);
        string s;
        for (int i = 3; i; --i) {
            getline(iss, s, '-');
            ymd[3-i] = str2<int>(s);
        }
        cout << ymd[0] << ' ' << ymd[1] << ' ' << ymd[2] << ' ' << endl;
        tm1.tm_year = ymd[0] - 1900;
        tm1.tm_mon = ymd[1] - 1;
        tm1.tm_mday = ymd[2];
        time_t t1 = mktime(&tm1);
        tm0.tm_year = 0;
        tm0.tm_mon = 0;
        tm0.tm_mday = 0;
        time_t t0 = mktime(&tm0);

        //cout << "times: " << mktime(&origin) << ' ' << mktime(&time) << endl;
        cout << "times: " << t0 << ' ' << t1 << endl;

        cout << "difftime: " << difftime(t1, t0) << endl;

        return difftime(mktime(&tm1), mktime(&tm0)) / (60*60*24);
    }

int i = dateStrToInt("2000-01-01");

, и вывод, который я получил от этого, -

2000 1 1
times: -1 -1
difftime: 0

, что кажется явно неправильным.Что я могу с этим поделать?

РЕДАКТИРОВАТЬ: как показано в ответе ниже, кажется, что есть проблемы с годами до 1970 года. Чтобы избежать этого, я управлял своей собственной функцией подсчета дней:

int dateStrToInt(string date) {
    int ymd[3];
    istringstream iss(date);
    string s;
    for (int i = 0; i < 3; ++i) {
        getline(iss, s, '-');
        ymd[i] = str2<int>(s);
    }
    const static int cum_m_days[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
    int year = ymd[0]+10000, month = ymd[1], day = ymd[2];
    int days = year*365 + cum_m_days[month-1] + day;
    // handle leap years
    if (month <= 2)
        --year;
    days = days + (year/4) - (year/100) + (year/400);
    return days;
}

1 Ответ

3 голосов
/ 04 августа 2011

Не обязательно оставлять все эти другие поля struct tm с их значениями по умолчанию (в данном случае случайными).

Стандарт не слишком явно указывает, какие поля необходимо установить перед вызовомmktime но в нем говорится, что он устанавливает tm_wday и tm_yday на основе других полей, и что эти другие поля не ограничиваются допустимостью.

Одна вещь, которую стандарт делает show - это пример кода, который устанавливает все поля , за исключением тех, которые упомянуты выше, так что именно к этому я и стремлюсь.

Попробуйте изменить сегмент, который вычисляет времена из:

tm1.tm_year = ymd[0] - 1900;
tm1.tm_mon = ymd[1] - 1;
tm1.tm_mday = ymd[2];
time_t t1 = mktime(&tm1);

tm0.tm_year = 0;
tm0.tm_mon = 0;
tm0.tm_mday = 0;
time_t t0 = mktime(&tm0);

примерно так:

// Quick and dirty way to get decent values for all fields.

time_t filled_in;
time (&filled_in);
memcpy (&tm1, localtime ( &filled_in ), sizeof (tm1));
memcpy (&tm0, &tm1, sizeof (tm0));

// Now do the modifications to relevant fields, and calculations.

tm1.tm_year = ymd[0] - 1900;
tm1.tm_mon = ymd[1] - 1;
tm1.tm_mday = ymd[2];
time_t t1 = mktime(&tm1);

tm0.tm_year = 0;
tm0.tm_mon = 0;
tm0.tm_mday = 0;
time_t t0 = mktime(&tm0);

Кроме того, некоторые эксперименты с CygWin под XP приводят к тому, что mktime всегда будет возвращать -1 для struct tm структур, гдеtm_year меньше двух.Является ли это фактической ошибкой или нет, сомнительно, поскольку я часто обнаруживал, что реализации не всегда поддерживают даты до эпохи (1 января 1970 г.).

Некоторые UNIX-ы позволяли вам указывать tm_year значенияменьше 70, и они часто могут использовать эти «отрицательные» значения time_t для доступа к годам до 1970 года.

Но, поскольку стандарт на самом деле не идет в это, он оставлен для реализации.Соответствующий бит стандарта C99 (и, возможно, более ранних итераций), который переносит в C ++, находится в 7.23.1 / 4:

Диапазон и точность времен, представляемых в clock_t и time_t, равныопределяется реализацией.

Самой безопасной ставкой было бы использование даты после начала эпохи в качестве базовой даты.Это показано в следующем коде:

#include <iostream>
#include <sstream>
#include <string>
#include <ctime>
#include <cstring>
#include <cstdlib>

int dateStrToInt(std::string date) {
    int ymd[3];
    tm tm1, tm0;
    std::istringstream iss(date);
    std::string s;

    // Test code.
    ymd[0] = 2000; ymd[1] = 1; ymd[2] = 1;

    std::cout << ymd[0] << ' ' << ymd[1] << ' ' << ymd[2] << ' ' << std::endl;

    time_t filled_in;
    time (&filled_in);
    std::memcpy (&tm0, localtime ( &filled_in ), sizeof (tm0));
    std::memcpy (&tm1, &tm0, sizeof (tm1));

    tm1.tm_year = ymd[0] - 1900;
    tm1.tm_mon = ymd[1] - 1;
    tm1.tm_mday = ymd[2];
    time_t t1 = mktime(&tm1);

    tm0.tm_year = 1970 - 1900;  // Use epoch as base date.
    tm0.tm_mon = 0;
    tm0.tm_mday = 1;

    time_t t0 = mktime(&tm0);

    std::cout << "times: " << t0 << ' ' << t1 << std::endl;
    std::cout << "difftime: " << difftime(t1, t0) << std::endl;
    return difftime(mktime(&tm1), mktime(&tm0)) / (60*60*24);
}

int main (void) {
    int i = dateStrToInt("2000-01-01");
    double d = i; d /= 365.25;
    std::cout << i << " days, about " << d << " years." << std::endl;
    return 0;
}

Выводит ожидаемые результаты:

2000 1 1
times: 31331 946716131
difftime: 9.46685e+08
10957 days, about 29.9986 years.

В качестве дополнения, POSIX имеет это , чтобы сказать:


4,14 секунды с начала эпохи

Значение, которое приблизительно равно количеству секундкоторые прошли с эпохи.Имя всемирного координированного времени (указанное в секундах (tm_sec), минутах (tm_min), часах (tm_hour), днях с 1 января года (tm_yday) и календарном году минус 1900 (tm_year)) относится квремя, представленное в секундах с начала эпохи, согласно приведенному ниже выражению.

Если год <1970 или значение отрицательное, связь не определена.Если год> = 1970 и значение неотрицательное, это значение связано с именем всемирного координированного времени в соответствии с выражением на языке C, где tm_sec, tm_min, tm_hour, tm_yday и tm_year являются целочисленными типами:

tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 +
    (tm_year-70)*31536000 + ((tm_year-69)/4)*86400 -
    ((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400

Взаимосвязь между фактическим временем дня и текущим значением в секундах, так как эпоха не указана.

Как любые изменения в значениях секунд, начиная с эпохи, выполняются для выравнивания сжелаемая связь с текущим фактическим временем определяется реализацией.Как показано в секундах с начала эпохи, каждый день должен составлять ровно 86400 секунд.

Примечание: последние три члена выражения добавляются в день для каждого года, следующего за високосным годом, начинающимся спервый високосный год со времен.Первый член добавляет день каждые 4 года, начиная с 1973 года, второй вычитает день назад каждые 100 лет, начиная с 2001 года, а третий добавляет день назад каждые 400 лет, начиная с 2001 года. Деления в формуле являются целочисленными делениями;то есть остаток отбрасывается, оставляя только целое число.


Другими словами (см. «Если год <1970 или значение отрицательное, отношение не определено» * </em>), используйте даты до 1970 г. на свой страх и риск.

...