C++:Windows Vista 上 MinGW 下的 mktime 错误?

发布于 2024-11-27 23:57:22 字数 1926 浏览 7 评论 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;
}

So I'm trying to convert dates in the format "2000-01-01" into integers representing the number of days since some arbitrary origin (e.g. 1900/01/01) so I can treat them as integer indices. To do this I wrote a conversion function which works fine on MinGW under Windows XP but not under Vista. I've added some logging code:

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

and the output I get from that is

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

which seems clearly wrong. What can I do about this?

EDIT: as the answer below says, there seems to be a problem with years prior to 1970. To avoid this I've handrolled my own day-counting function:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

玩世 2024-12-04 23:57:22

将所有其他 struct tm 字段保留为其默认值(在本例中为随机值)不一定是个好主意。

该标准对于在调用 mktime 之前需要设置哪些字段并没有过于明确,但它确实表示它根据以下内容设置 tm_wday 和 tm_yday其他字段,并且这些其他字段不限于有效。

标准所做的展示的一件事是示例代码,它设置除上面提到的两个之外的所有字段,这就是我的目标。

尝试将计算时间的段从:

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 进行的一些实验导致 mktime 似乎总是为 struct tm 返回 -1 tm_year 小于 2 的结构。这是否是一个实际的错误是值得怀疑的,因为我经常发现实现并不总是支持纪元(1970 年 1 月 1 日)之前的日期。

某些 UNIX 确实允许您指定小于 70 的 tm_year 值,并且它们通常可以使用 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 有 this表示:


自纪元以来的 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 秒。

注意:表达式的最后三项为从纪元以来的第一个闰年开始的闰年之后的每年添加一天。第一项从 1973 年开始每 4 年增加一天,第二项从 2001 年开始每 100 年减少一天,第三项从 2001 年开始每 400 年增加一天。公式中的除法是整数除法;也就是说,余数被丢弃,只留下整数商。


换句话说(请参阅“如果年份 <1970 或值为负,则关系未定义”),使用 1970 年之前的日期需要您自担风险。

It's not necessarily a good idea leaving all of those other struct tm fields at their default (random in this case) values.

The standard is not overly explicit about what fields need to be set before calling mktime but it does say that it sets tm_wday and tm_yday based on the other fields, and that those other fields are not restricted to being valid.

One thing the standard does show is example code which sets all fields except those two mentioned above so that's what I'd be aiming for.

Try to change the segment that calculates the times from:

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

to something like:

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

In addition, some experimentation with CygWin under XP results in mktime alway seeming to return -1 for struct tm structures where the tm_year is less than two. Whether that's an actual bug or not is questionable since I've often found that implementations don't always support dates before the epoch (Jan 1, 1970).

Some UNIXes did allow you to specify tm_year values less than 70 and they could often use these "negative" values of time_t to access years back to 1970.

But, since the standard doesn't really go into that, it's left to the implementation. The relevant bit of the C99 standard (and probably earlier iterations), which carries forward to C++, is found in 7.23.1/4:

The range and precision of times representable in clock_t and time_t are implementation-defined.

The safest bet would be to use a date after the start of the epoch as the baseline date. This is shown in the following code:

#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;
}

This outputs the expected results:

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

As an addendum, POSIX has this to say:


4.14 Seconds Since the Epoch

A value that approximates the number of seconds that have elapsed since the Epoch. A Coordinated Universal Time name (specified in terms of seconds (tm_sec), minutes (tm_min), hours (tm_hour), days since January 1 of the year (tm_yday), and calendar year minus 1900, (tm_year)) is related to a time represented as seconds since the Epoch, according to the expression below.

If the year is <1970 or the value is negative, the relationship is undefined. If the year is >=1970 and the value is non-negative, the value is related to a Coordinated Universal Time name according to the C-language expression, where tm_sec, tm_min, tm_hour, tm_yday, and tm_year are all integer types:

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

The relationship between the actual time of day and the current value for seconds since the Epoch is unspecified.

How any changes to the value of seconds since the Epoch are made to align to a desired relationship with the current actual time is implementation-defined. As represented in seconds since the Epoch, each and every day shall be accounted for by exactly 86400 seconds.

Note: The last three terms of the expression add in a day for each year that follows a leap year starting with the first leap year since the Epoch. The first term adds a day every 4 years starting in 1973, the second subtracts a day back out every 100 years starting in 2001, and the third adds a day back in every 400 years starting in 2001. The divisions in the formula are integer divisions; that is, the remainder is discarded leaving only the integer quotient.


In other words (see "If the year is <1970 or the value is negative, the relationship is undefined"), use dates before 1970 at your own risk.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文