C++ 与 python:提取 Unix 时间时无法识别夏令时?

C++ vs. python: daylight saving time not recognized when extracting Unix time?

我正在尝试计算由两个整数表示的给定日期和时间的 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 CEST,但我希望这是 0:00:04 CEST。

如果我选择 testdate1testdate2,其中夏令时 (DST) 未被观察到,代码似乎工作得很好.例如,通过设置 testdate1 = 20060211 来简单地将月份设置为二月而其他所有内容都保持不变。这给 unixtime(testdate1,testdate2) = 1139612404,对应CET中的hh:mm:ss = 00:00:04,随心所欲。

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

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

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

不要打电话给 localtime。将 timeinfo->tm_isdst 设置为 -1:

The value specified in the tm_isdst field informs mktime() whether or not daylight saving time (DST) is in effect for the time supplied in the tm structure: a positive value means DST is in effect; zero means that DST is not in effect; and a negative value means that mktime() should (use timezone information and system databases to) attempt to determine whether DST is in effect at the specified time.

参见https://en.cppreference.com/w/cpp/chrono/c/mktime

中的代码示例

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

我想在这里说明两个要点:

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

  2. 使用 TZ 环境变量存在线程安全弱点,因为这是一种全局变量。如果另一个线程也在做某种类型的时间计算,如果计算机的时区意外地从它下面改变,它可能无法得到正确的答案。 <chrono> 的解是 thread-safe。它不涉及全局变量或环境变量。

第一项工作是对积分数据进行解包。在这里,我展示了如何做到这一点,并一步将其转换为 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类型day , monthyear, 最后将这三个单独的字段组合成一个 std::chrono::year_month_day 到 return 的一个值。 return 类型是一个简单的 {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 将小时、分钟和秒字段和 return 解压缩为 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 的代码非常相似,只是 return 是小时、分钟和秒的总和。 chrono 库会在执行求和时为您完成将小时和分钟转换为秒的工作。

接下来是进行转换的函数,return将结果返回为 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<system_clock, seconds>,它有一个方便的类型别名,称为 sys_seconds,尽管 auto 使该名称在此代码中不必要。

为了将 sys_seconds 解压缩为整数类型,调用 .time_since_epoch() 成员函数,结果是 duration seconds,然后是 .count() 调用成员函数从 duration.

中提取整数值

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