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 时间,我将 testdate1
、testdate2
送入我改编自 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。
如果我选择 testdate1
、testdate2
,其中夏令时 (DST) 未被观察到,代码似乎工作得很好.例如,通过设置 testdate1 = 20060211
来简单地将月份设置为二月而其他所有内容都保持不变。这给
unixtime(testdate1,testdate2) = 1139612404
,对应CET中的hh:mm:ss = 00:00:04,随心所欲。
我的印象是 setenv("TZ","Europe/Berlin", 1)
应该在适用时考虑夏令时,但也许我错了。 TZ 能否以解释夏令时的方式解释 testdate1
、testdate2
?
有趣的是,我有一个 python 代码,它通过 os.environ['TZ'] = 'Europe/Berlin'
更改当地时间来执行相同的任务。在这里我没有问题,因为它似乎计算了正确的 Unix 时间,而不管 DST/non-DST.
localtime
将 timeinfo->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.
中的代码示例
Maxim 的回答是正确的,我点赞了。但我还认为,展示如何使用较新的 <chrono>
工具在 C++20 中完成此操作可能会有所帮助。这还没有在所有地方实现,但它在 Visual Studio 中已经实现,很快就会在其他地方实现。
我想在这里说明两个要点:
<chrono>
这样的转换很方便,即使输入和输出都不涉及std::chrono
类型。可以将积分输入转换为计时,进行转换,然后将计时结果转换回积分。
使用 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
, month
和 year
, 最后将这三个单独的字段组合成一个 std::chrono::year_month_day
到 return 的一个值。 return 类型是一个简单的 {year, month, day}
数据结构——类似于 tuple
但具有日历意义。
/
语法只是一个方便的工厂函数,用于构造 year_month_day
。并且可以使用以下三种顺序中的任何一种来完成此构造:y/m/d
、d/m/y
和 m/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_time
和sys_time
都是std::chrono::time_point
类型。 local_time
是“某个当地时间”,不一定是您计算机的当地时间。您可以将本地时间与时区相关联,以指定该时间的地点。
sys_time
是基于 system_clock
的 time_point
。这跟踪 UTC(Unix 时间)。
表达式 local_days{ymd} + hms
将 ymd
和 hms
转换为 local_time
,精度为 seconds
。 local_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 位整数类型表示,给它一个大于宇宙年龄的范围(即使科学家们偏离了一个数量级)。
我正在尝试计算由两个整数表示的给定日期和时间的 Unix 时间,例如
testdate1 = 20060711
(2006 年 7 月 11 日)
testdate2 = 4
(00:00:04,午夜后 4 秒)
在我当地时区以外的时区。为了计算 Unix 时间,我将 testdate1
、testdate2
送入我改编自 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。
如果我选择 testdate1
、testdate2
,其中夏令时 (DST) 未被观察到,代码似乎工作得很好.例如,通过设置 testdate1 = 20060211
来简单地将月份设置为二月而其他所有内容都保持不变。这给
unixtime(testdate1,testdate2) = 1139612404
,对应CET中的hh:mm:ss = 00:00:04,随心所欲。
我的印象是 setenv("TZ","Europe/Berlin", 1)
应该在适用时考虑夏令时,但也许我错了。 TZ 能否以解释夏令时的方式解释 testdate1
、testdate2
?
有趣的是,我有一个 python 代码,它通过 os.environ['TZ'] = 'Europe/Berlin'
更改当地时间来执行相同的任务。在这里我没有问题,因为它似乎计算了正确的 Unix 时间,而不管 DST/non-DST.
localtime
将 timeinfo->tm_isdst
设置为 当前 时间的时间 - 而不是您解析的日期。
不要打电话给 localtime
。将 timeinfo->tm_isdst
设置为 -1
:
中的代码示例The value specified in the
tm_isdst
field informsmktime()
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 thatmktime()
should (use timezone information and system databases to) attempt to determine whether DST is in effect at the specified time.
Maxim 的回答是正确的,我点赞了。但我还认为,展示如何使用较新的 <chrono>
工具在 C++20 中完成此操作可能会有所帮助。这还没有在所有地方实现,但它在 Visual Studio 中已经实现,很快就会在其他地方实现。
我想在这里说明两个要点:
<chrono>
这样的转换很方便,即使输入和输出都不涉及std::chrono
类型。可以将积分输入转换为计时,进行转换,然后将计时结果转换回积分。使用
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
, month
和 year
, 最后将这三个单独的字段组合成一个 std::chrono::year_month_day
到 return 的一个值。 return 类型是一个简单的 {year, month, day}
数据结构——类似于 tuple
但具有日历意义。
/
语法只是一个方便的工厂函数,用于构造 year_month_day
。并且可以使用以下三种顺序中的任何一种来完成此构造:y/m/d
、d/m/y
和 m/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_time
和sys_time
都是std::chrono::time_point
类型。 local_time
是“某个当地时间”,不一定是您计算机的当地时间。您可以将本地时间与时区相关联,以指定该时间的地点。
sys_time
是基于 system_clock
的 time_point
。这跟踪 UTC(Unix 时间)。
表达式 local_days{ymd} + hms
将 ymd
和 hms
转换为 local_time
,精度为 seconds
。 local_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 位整数类型表示,给它一个大于宇宙年龄的范围(即使科学家们偏离了一个数量级)。