【问题标题】:C++ vs. python: daylight saving time not recognized when extracting Unix time?C++ 与 python:提取 Unix 时间时无法识别夏令时?
【发布时间】:2022-02-03 09:38:52
【问题描述】:

我正在尝试计算由两个整数表示的给定日期和时间的 Unix 时间,例如

testdate1 = 20060711(2006 年 7 月 11 日)

testdate2 = 4(00:00:04,午夜后 4 秒)

在我当地时区以外的时区。为了计算 Unix 时间,我将 testdate1testdate2 输入到我改编自 Convert date to unix time stamp in c++ 的函数中

int unixtime (int testdate1, int testdate2) {

time_t rawtime; 
struct tm * timeinfo; 

//time1, ..., time6 are external functions that extract the 
//year, month, day, hour, minute, seconds digits from testdate1, testdate2

int year=time1(testdate1);
int month=time2(testdate1);
int day=time3(testdate1);
int hour=time4(testdate2);
int minute=time5(testdate2);
int second=time6(testdate2);

time ( &rawtime );
timeinfo = localtime ( &rawtime );  

timeinfo->tm_year   = year - 1900; 
timeinfo->tm_mon    = month - 1;   
timeinfo->tm_mday   = day;         
timeinfo->tm_hour   = hour;        
timeinfo->tm_min    = minute;          
timeinfo->tm_sec    = second;       

int date;
date = mktime(timeinfo);

return date;

}

我从主代码调用的

using namespace std;
int main(int argc, char* argv[])
{

int testdate1 = 20060711;
int testdate2 = 4;


//switch to CET time zone 
setenv("TZ","Europe/Berlin", 1); 
tzset();

cout << testdate1 << "\t" << testdate2 << "\t" << unixtime(testdate1,testdate2) << "\n";

    return 0;
}

通过给定的示例,我得到unixtime(testdate1,testdate2) = 1152572404,根据

https://www.epochconverter.com/timezones?q=1152572404&tz=Europe%2FBerlin

是 1:00:04 am CEST,但我希望这是 0:00:04 CEST。

如果我选择testdate1testdate2,代码似乎运行良好,在该testdate2没有遵守夏令时。例如,通过设置testdate1 = 20060211 来简单地将月份设置为二月,而其他所有内容都保持不变。这给 unixtime(testdate1,testdate2) = 1139612404,根据需要对应于 CET 中的 hh:mm:ss = 00:00:04。

我的印象是setenv("TZ","Europe/Berlin", 1) 应该在适用时考虑 DST,但也许我弄错了。 TZ 能否解释 testdate1testdate2 以解释夏令时?

有趣的是,我有一个 python 代码通过os.environ['TZ'] = 'Europe/Berlin' 更改本地时间来执行相同的任务。在这里我没有问题,因为它似乎计算了正确的 Unix 时间,而不管 DST/非 DST。

【问题讨论】:

    标签: python c++ date time


    【解决方案1】:

    localtimetimeinfo-&gt;tm_isdst 设置为 当前 时间 - 而不是您解析的日期。

    不要打电话给localtime。将timeinfo-&gt;tm_isdst 设置为-1

    tm_isdst 字段中指定的值通知mktime() 夏令时 (DST) 是否对 tm 结构中提供的时间有效:正值表示 DST 有效;零表示 DST 无效; 负值表示mktime() 应该(使用时区信息和系统数据库来)尝试确定 DST 是否在指定时间生效

    查看https://en.cppreference.com/w/cpp/chrono/c/mktime中的代码示例

    【讨论】:

    • 完美回复和解决方案!我的代码现在可以完全按照我的预期工作。
    • @renorm877 谢谢。正如@howard-hinnant 建议的那样,您可能还想使用std::time_t 而不是int
    【解决方案2】:

    Maxim 的回答是正确的,我已经投了赞成票。但我也认为展示如何使用较新的&lt;chrono&gt; 工具在 C++20 中完成此操作可能会有所帮助。这还没有在所有地方实现,但它在 Visual Studio 中,很快就会出现在其他地方。

    这里我想说明两个要点:

    1. &lt;chrono&gt; 方便这样的转换,即使输入和输出都不涉及std::chrono 类型。可以将积分输入转换为 chrono,进行转换,然后将 chrono 结果转换回积分。

    2. 使用TZ 环境变量存在线程安全漏洞,因为这是一种全局变量。如果另一个线程也在进行某种类型的时间计算,如果计算机的时区意外地从它下面改变,它可能无法得到正确的答案。 &lt;chrono&gt; 解决方案是线程安全的。它不涉及全局变量或环境变量。

    第一个工作是解包积分数据。这里我展示了如何做到这一点,并一步将其转换为chrono 类型:

    std::chrono::year_month_day
    get_ymd(int ymd)
    {
        using namespace std::chrono;
    
        day d(ymd % 100);
        ymd /= 100;
        month m(ymd % 100);
        ymd /= 100;
        year y{ymd};
        return y/m/d;
    }
    

    get_ymd 采用“testdate1”,提取日、月和年的各个整数字段,然后将每个整数字段转换为std::chrono 类型daymonthyear,以及最后将这三个单独的字段组合成一个std::chrono::year_month_day 以将其作为一个值返回。这个返回类型是一个简单的{year, month, day} 数据结构——类似于tuple,但具有日历含义。

    / 语法只是一个方便的工厂函数,用于构造year_month_day。并且可以使用以下三个排序中的任何一个来完成此构造:y/m/dd/m/ym/d/y。此语法与auto 结合使用时,也意味着您通常不必拼出冗长的名称year_month_day

    auto
    get_ymd(int ymd)
    {
        // ...
        return y/m/d;
    }
    

    get_hms 解压缩小时、分钟和秒字段并将其作为std::chrono::seconds 返回:

    std::chrono::seconds
    get_hms(int hms)
    {
        using namespace std::chrono;
    
        seconds s{hms % 100};
        hms /= 100;
        minutes m{hms % 100};
        hms /= 100;
        hours h{hms};
        return h + m + s;
    }
    

    代码与get_ymd 的代码非常相似,只是返回的是小时、分钟和秒的总和。 chrono 库可以在执行求和时将小时和分钟转换为秒。

    接下来是执行转换并将结果作为int 返回的函数。

    int
    unixtime(int testdate1, int testdate2)
    {
        using namespace std::chrono;
    
        auto ymd = get_ymd(testdate1);
        auto hms = get_hms(testdate2);
        auto ut = locate_zone("Europe/Berlin")->to_sys(local_days{ymd} + hms);
        return ut.time_since_epoch().count();
    }
    

    std::chrono::locate_zone 被调用以获取指向名为“Europe/Berlin”的std::chrono::time_zone 的指针。 std::lib 管理这个对象的生命周期,所以你不必担心它。它是一个 const 单例,按需创建。它对您的计算机认为其“本地时区”的时区没有影响。

    std::chrono::time_zone 有一个名为 to_sys 的成员函数,它接受 local_time,并将其转换为 sys_time,使用该时区的正确 UTC 偏移量(在适用时考虑夏令时规则) .

    local_timesys_time 都是 std::chrono::time_point 类型。 local_time 是“某个当地时间”,不一定是您计算机的当地时间。您可以将本地时间与时区相关联,以指定该时间的位置。

    sys_time 是基于system_clocktime_point。这将跟踪 UTC(Unix 时间)。

    表达式local_days{ymd} + hmsymdhms 转换为local_time,精度为secondslocal_days 只是另一个 local_time time_point,但精度为 days

    ut 的类型是time_point&lt;system_clock, seconds&gt;,它有一个名为sys_seconds 的便利类型别名,尽管auto 使得该名称在此代码中是不必要的。

    为了将sys_seconds解压成一个整数类型,调用.time_since_epoch()成员函数得到durationseconds,然后调用.count()成员函数从中提取整数值duration.

    int 为32 位时,此函数易受year 2038 overflow problem 的影响。要解决这个问题,只需将 unixtime 的返回类型更改为返回 64 位整数类型(或返回 auto)。没有什么需要改变的,因为std::chrono::seconds 已经被要求大于 32 位并且在 68 年不会溢出。事实上,std::chrono::seconds 在实践中通常由带符号的 64 位整数类型表示,使其范围大于宇宙年龄(即使科学家相差一个数量级)。

    【讨论】:

    • 谢谢霍华德,你太客气了。 TIL std::chrono::locate_zone,这对我来说是一个非常令人惊喜的惊喜,谢谢。这将消除我在我的代码库中维护一个库的需要,该库导出tzfreetzallocmktime_zlocaltime_rz 以进行时区转换,而不会弄乱TZ 环境变量。 +1
    • 哇,感谢您的详细回复!我肯定也会给&lt;chrono&gt; 一个机会。
    • 很高兴这很有用。 Fwiw,这里有 C++20 这一部分的免费开源预览:github.com/HowardHinnant/date 这适用于 C++11/14/17。它的存在只是因为它是成为 C++20 chrono 的现场经验。也就是说,我不想卖掉它。我已经将它“卖”给了 C++ 委员会。 :-)
    猜你喜欢
    • 1970-01-01
    • 2016-08-10
    • 2020-03-01
    • 1970-01-01
    • 2014-03-23
    • 1970-01-01
    • 2014-11-03
    • 2014-09-30
    • 2012-07-09
    相关资源
    最近更新 更多