0

そこで、「2000-01-01」形式の日付を、任意の起点 (1900/01/01 など) からの日数を表す整数に変換して、整数インデックスとして扱うことができるようにしようとしています。これを行うために、Windows XP の MinGW では問題なく動作するが、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;
}
4

1 に答える 1

3

struct tm他のすべてのフィールドをデフォルト (この場合はランダム) 値のままにしておくことは、必ずしも良い考えではありません。

標準は、呼び出す前にどのフィールドを設定する必要があるかについて過度に明示的ではありませんが、他のフィールドに基づいmktimeて設定tm_wdayし、それらの他のフィールドが有効であることに制限されていないと述べています。tm_yday

標準が示していることの 1 つは、上記の 2 つを除くすべてのフィールドを設定するコード例です。それが私が目指していることです。

時間を計算するセグメントを次のように変更してみてください。

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);

さらに、XP での CygWin のいくつかの実験では、が 2 未満の構造mktimeに対して常に -1 を返すように見えます。これが実際のバグであるかどうかは疑問です。なぜなら、実装がエポック (1970 年 1 月 1 日) より前の日付を常にサポートしているとは限らないことがよくあるからです。struct tmtm_year

tm_yearUNIX の中には、70 未満の値を指定できるものもあり、多くの場合、これらの「負の」値を使用time_tして 1970 年まで遡ることができました。

しかし、標準は実際にはそれに入っていないので、実装に任されています。C++ に引き継がれる C99 標準 (およびおそらくそれ以前のイテレーション) の関連部分は、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 月 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 秒になります。

注: 式の最後の 3 つの項は、エポック以降の最初のうるう年から始まる、うるう年の後の各年の 1 日を加算します。第 1 項は 1973 年から 4 年ごとに 1 日を加算し、第 2 項は 2001 年から 100 年ごとに 1 日を減算し、第 3 項は 2001 年から 400 年ごとに 1 日を加算します。式の除算は整数除算です。 ; つまり、剰余は破棄され、整数の商のみが残ります。


つまり ( 「年が <1970 であるか、値が負の場合、関係は定義されていません」を参照してください)、自己責任で 1970 年より前の日付を使用してください。

于 2011-08-04T07:29:23.580 に答える