C 代码获取相对于 UTC 的本地时间偏移量(以分钟为单位)?
C code to get local time offset in minutes relative to UTC?
我没有找到一种简单的方法来获取本地时间和 UTC 时间之间的时间偏移(以分钟为单位)。
起初我打算使用tzset()
,但它不提供夏令时。根据手册页,如果夏令时有效,它只是一个不为零的整数。虽然通常是一个小时,但在某些国家/地区可能是半小时。
我宁愿避免计算 gmtime()
和 localtime()
返回的当前 UTC 之间的时差。
一个更通用的解决方案会为我提供指定位置的此信息和正 time_t 值,或者至少在本地。
编辑 1:用例是为 https://github.com/chmike/timez 获取正确的本地时间偏移量。
顺便说一句,如果您认为操纵时间的 libc 函数还可以,请阅读此 https://rachelbythebay.com/w/2013/03/17/time/。
编辑 2:到目前为止,以分钟为单位计算与 UTC 的时间偏移的最佳和最简单的解决方案是
// Bogus: assumes DST is always one hour
tzset();
int offset = (int)(-timezone / 60 + (daylight ? 60 : 0));
问题是确定真正的夏令时。
编辑3:受回答的启发,我提出了以下解决方案。这是一个 hack,因为它欺骗 mktime()
将 gmtime()
的输出视为本地时间。当夏令时变化在 UTC 时间和本地时间之间的时间跨度时,结果不准确。
#include <stdio.h>
#include <time.h>
int main()
{
time_t rawtime = time(NULL);
struct tm *ptm = gmtime(&rawtime);
// Request that mktime() looksup dst in timezone database
ptm->tm_isdst = -1;
time_t gmt = mktime(ptm);
double offset = difftime(rawtime, gmt) / 60;
printf("%f\n", offset);
return 0;
}
您系统的 strftime() 函数是否支持 %z
和 %Z
说明符?在 FreeBSD 上,
%Z is replaced by the time zone name.
%z is replaced by the time zone offset from UTC; a leading plus sign
stands for east of UTC, a minus sign for west of UTC, hours and
minutes follow with two digits each and no delimiter between them
(common form for RFC 822 date headers).
我可以用它来打印:
$ date +"%Z: %z"
CEST: +0200
ISO C99 在 7.23.3.5 中有这个 strftime
函数:
%z is replaced by the offset from UTC in the ISO 8601 format
‘‘−0430’’ (meaning 4 hours 30 minutes behind UTC, west of Greenwich),
or by no characters if no time zone is determinable. [tm_isdst]
%Z is replaced by the locale’s time zone name or abbreviation, or by no
characters if no time zone is determinable. [tm_isdst]
此 C 代码计算本地时间相对于 UTC 的偏移量(以分钟为单位)。它假定 DST 始终是一个小时的偏移量。
#include <stdio.h>
#include <time.h>
int main()
{
time_t rawtime = time(NULL);
struct tm *ptm = gmtime(&rawtime);
time_t gmt = mktime(ptm);
ptm = localtime(&rawtime);
time_t offset = rawtime - gmt + (ptm->tm_isdst ? 3600 : 0);
printf("%i\n", (int)offset);
}
虽然它使用 gmtime 和 localtime。你为什么不想使用这些功能?
恕我直言,唯一万无一失且可移植的方法是使用 localtime
和 gmtime
并在一分钟内手动计算增量,因为这两个函数存在于所有已知系统中。例如:
int deltam() {
time_t t = time(NULL);
struct tm *loc = localtime(&t);
/* save values because they could be erased by the call to gmtime */
int loc_min = loc->tm_min;
int loc_hour = loc->tm_hour;
int loc_day = loc->tm_mday;
struct tm *utc = gmtime(&t);
int delta = loc_min - utc->tm_min;
int deltaj = loc_day - utc->tm_mday;
delta += (loc_hour - utc->tm_hour) * 60;
/* hack for the day because the difference actually is only 0, 1 or -1 */
if ((deltaj == 1) || (deltaj < -1)) {
delta += 1440;
}
else if ((deltaj == -1) || (deltaj > 1)) {
delta -= 1440;
}
return delta;
}
请注意,我没有测试所有可能的极端情况,但这可能是满足您要求的起点。
... to get local time offset ... relative to UTC?
答案是好。所以我虽然会测试它并清理一些细节。我会发表评论但显然太大了。我只针对我的时区进行了练习,但其他人可能想在他们的机器和时区上尝试。
我加入社区维基是为了不获得代表。 Imitation is the sincerest form of flattery
这个答案类似于 除了它减去附近的 struct tm
值而不是假设 DST 偏移是 1 小时而 time_t
以秒为单位。
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
// return difference in **seconds** of the tm_mday, tm_hour, tm_min, tm_sec members.
long tz_offset_second(time_t t) {
struct tm local = *localtime(&t);
struct tm utc = *gmtime(&t);
long diff = ((local.tm_hour - utc.tm_hour) * 60 + (local.tm_min - utc.tm_min))
* 60L + (local.tm_sec - utc.tm_sec);
int delta_day = local.tm_mday - utc.tm_mday;
// If |delta_day| > 1, then end-of-month wrap
if ((delta_day == 1) || (delta_day < -1)) {
diff += 24L * 60 * 60;
} else if ((delta_day == -1) || (delta_day > 1)) {
diff -= 24L * 60 * 60;
}
return diff;
}
void testtz(void) {
long off = -1;
int delta = 600;
for (time_t t = 0; t < LONG_MAX-delta; t+=delta) {
long off2 = tz_offset_second(t);
// Print time whenever offset changes.
if (off != off2) {
struct tm utc = *gmtime(&t);
printf("%10jd %04d-%02d-%02dT%02d:%02d:%02dZ\n", (intmax_t) t,
utc.tm_year + 1900, utc.tm_mon + 1, utc.tm_mday,
utc.tm_hour, utc.tm_min, utc.tm_sec);
struct tm local = *localtime(&t);
off = off2;
printf("%10s %04d-%02d-%02d %02d:%02d:%02d %2d %6ld\n\n", "",
local.tm_year + 1900, local.tm_mon + 1, local.tm_mday,
local.tm_hour, local.tm_min, local.tm_sec, local.tm_isdst ,off);
fflush(stdout);
}
}
puts("Done");
}
输出
v----v Difference in seconds
0 1970-01-01T00:00:00Z
1969-12-31 18:00:00 0 -21600
5731200 1970-03-08T08:00:00Z
1970-03-08 03:00:00 1 -18000
26290800 1970-11-01T07:00:00Z
1970-11-01 01:00:00 0 -21600
...
2109222000 2036-11-02T07:00:00Z
2036-11-02 01:00:00 0 -21600
2120112000 2037-03-08T08:00:00Z
2037-03-08 03:00:00 1 -18000
2140671600 2037-11-01T07:00:00Z
2037-11-01 01:00:00 0 -21600
Done
我想提交这个问题的另一个答案,AFAICS 也处理 IDL。
此解决方案取决于 timegm
和 mktime
。在 Windows 上,timegm
在 CRT 中可用作 _mkgmtime
,换句话说,定义一个条件宏。
#if _WIN32
# define timegm _mkgmtime
#endif
int local_utc_offset_minutes ( ) {
time_t t = time ( NULL );
struct tm * locg = localtime ( &t );
struct tm locl;
memcpy ( &locl, locg, sizeof ( struct tm ) );
return (int)( timegm ( locg ) - mktime ( &locl ) ) / 60;
}
这是我的方法:
time_t z = 0;
struct tm * pdt = gmtime(&z);
time_t tzlag = mktime(pdt);
替代 struct tm
的自动本地存储:
struct tm dt;
memset(&dt, 0, sizeof(struct tm));
dt.tm_mday=1; dt.tm_year=70;
time_t tzlag = mktime(&dt);
tzlag
,以秒为单位,将是UTC偏移量的负数;与 UTC 相比,您的时区标准时间滞后:
LocalST + tzlag = UTC
如果您还想考虑“夏令时”,请从 tzlag
中减去 tm_isdst
,其中 tm_isdst
是特定当地时间 struct tm
的字段,在对其应用 mktime
之后(或在使用 localtime
获得它之后)。
为什么有效:
集合 struct tm
用于“纪元”时刻,即 1970 年 1 月 1 日,对应于 time_t
的 0。
在该日期调用 mktime()
会将其转换为 time_t
,就好像它是 UTC(因此得到 0),然后从中减去 UTC 偏移量以生成输出 time_t
。因此它产生 UTC_offset.
的负数
我没有找到一种简单的方法来获取本地时间和 UTC 时间之间的时间偏移(以分钟为单位)。
起初我打算使用tzset()
,但它不提供夏令时。根据手册页,如果夏令时有效,它只是一个不为零的整数。虽然通常是一个小时,但在某些国家/地区可能是半小时。
我宁愿避免计算 gmtime()
和 localtime()
返回的当前 UTC 之间的时差。
一个更通用的解决方案会为我提供指定位置的此信息和正 time_t 值,或者至少在本地。
编辑 1:用例是为 https://github.com/chmike/timez 获取正确的本地时间偏移量。 顺便说一句,如果您认为操纵时间的 libc 函数还可以,请阅读此 https://rachelbythebay.com/w/2013/03/17/time/。
编辑 2:到目前为止,以分钟为单位计算与 UTC 的时间偏移的最佳和最简单的解决方案是
// Bogus: assumes DST is always one hour
tzset();
int offset = (int)(-timezone / 60 + (daylight ? 60 : 0));
问题是确定真正的夏令时。
编辑3:受mktime()
将 gmtime()
的输出视为本地时间。当夏令时变化在 UTC 时间和本地时间之间的时间跨度时,结果不准确。
#include <stdio.h>
#include <time.h>
int main()
{
time_t rawtime = time(NULL);
struct tm *ptm = gmtime(&rawtime);
// Request that mktime() looksup dst in timezone database
ptm->tm_isdst = -1;
time_t gmt = mktime(ptm);
double offset = difftime(rawtime, gmt) / 60;
printf("%f\n", offset);
return 0;
}
您系统的 strftime() 函数是否支持 %z
和 %Z
说明符?在 FreeBSD 上,
%Z is replaced by the time zone name. %z is replaced by the time zone offset from UTC; a leading plus sign stands for east of UTC, a minus sign for west of UTC, hours and minutes follow with two digits each and no delimiter between them (common form for RFC 822 date headers).
我可以用它来打印:
$ date +"%Z: %z"
CEST: +0200
ISO C99 在 7.23.3.5 中有这个 strftime
函数:
%z is replaced by the offset from UTC in the ISO 8601 format ‘‘−0430’’ (meaning 4 hours 30 minutes behind UTC, west of Greenwich), or by no characters if no time zone is determinable. [tm_isdst] %Z is replaced by the locale’s time zone name or abbreviation, or by no characters if no time zone is determinable. [tm_isdst]
此 C 代码计算本地时间相对于 UTC 的偏移量(以分钟为单位)。它假定 DST 始终是一个小时的偏移量。
#include <stdio.h>
#include <time.h>
int main()
{
time_t rawtime = time(NULL);
struct tm *ptm = gmtime(&rawtime);
time_t gmt = mktime(ptm);
ptm = localtime(&rawtime);
time_t offset = rawtime - gmt + (ptm->tm_isdst ? 3600 : 0);
printf("%i\n", (int)offset);
}
虽然它使用 gmtime 和 localtime。你为什么不想使用这些功能?
恕我直言,唯一万无一失且可移植的方法是使用 localtime
和 gmtime
并在一分钟内手动计算增量,因为这两个函数存在于所有已知系统中。例如:
int deltam() {
time_t t = time(NULL);
struct tm *loc = localtime(&t);
/* save values because they could be erased by the call to gmtime */
int loc_min = loc->tm_min;
int loc_hour = loc->tm_hour;
int loc_day = loc->tm_mday;
struct tm *utc = gmtime(&t);
int delta = loc_min - utc->tm_min;
int deltaj = loc_day - utc->tm_mday;
delta += (loc_hour - utc->tm_hour) * 60;
/* hack for the day because the difference actually is only 0, 1 or -1 */
if ((deltaj == 1) || (deltaj < -1)) {
delta += 1440;
}
else if ((deltaj == -1) || (deltaj > 1)) {
delta -= 1440;
}
return delta;
}
请注意,我没有测试所有可能的极端情况,但这可能是满足您要求的起点。
... to get local time offset ... relative to UTC?
我加入社区维基是为了不获得代表。 Imitation is the sincerest form of flattery
这个答案类似于 struct tm
值而不是假设 DST 偏移是 1 小时而 time_t
以秒为单位。
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
// return difference in **seconds** of the tm_mday, tm_hour, tm_min, tm_sec members.
long tz_offset_second(time_t t) {
struct tm local = *localtime(&t);
struct tm utc = *gmtime(&t);
long diff = ((local.tm_hour - utc.tm_hour) * 60 + (local.tm_min - utc.tm_min))
* 60L + (local.tm_sec - utc.tm_sec);
int delta_day = local.tm_mday - utc.tm_mday;
// If |delta_day| > 1, then end-of-month wrap
if ((delta_day == 1) || (delta_day < -1)) {
diff += 24L * 60 * 60;
} else if ((delta_day == -1) || (delta_day > 1)) {
diff -= 24L * 60 * 60;
}
return diff;
}
void testtz(void) {
long off = -1;
int delta = 600;
for (time_t t = 0; t < LONG_MAX-delta; t+=delta) {
long off2 = tz_offset_second(t);
// Print time whenever offset changes.
if (off != off2) {
struct tm utc = *gmtime(&t);
printf("%10jd %04d-%02d-%02dT%02d:%02d:%02dZ\n", (intmax_t) t,
utc.tm_year + 1900, utc.tm_mon + 1, utc.tm_mday,
utc.tm_hour, utc.tm_min, utc.tm_sec);
struct tm local = *localtime(&t);
off = off2;
printf("%10s %04d-%02d-%02d %02d:%02d:%02d %2d %6ld\n\n", "",
local.tm_year + 1900, local.tm_mon + 1, local.tm_mday,
local.tm_hour, local.tm_min, local.tm_sec, local.tm_isdst ,off);
fflush(stdout);
}
}
puts("Done");
}
输出
v----v Difference in seconds
0 1970-01-01T00:00:00Z
1969-12-31 18:00:00 0 -21600
5731200 1970-03-08T08:00:00Z
1970-03-08 03:00:00 1 -18000
26290800 1970-11-01T07:00:00Z
1970-11-01 01:00:00 0 -21600
...
2109222000 2036-11-02T07:00:00Z
2036-11-02 01:00:00 0 -21600
2120112000 2037-03-08T08:00:00Z
2037-03-08 03:00:00 1 -18000
2140671600 2037-11-01T07:00:00Z
2037-11-01 01:00:00 0 -21600
Done
我想提交这个问题的另一个答案,AFAICS 也处理 IDL。
此解决方案取决于 timegm
和 mktime
。在 Windows 上,timegm
在 CRT 中可用作 _mkgmtime
,换句话说,定义一个条件宏。
#if _WIN32
# define timegm _mkgmtime
#endif
int local_utc_offset_minutes ( ) {
time_t t = time ( NULL );
struct tm * locg = localtime ( &t );
struct tm locl;
memcpy ( &locl, locg, sizeof ( struct tm ) );
return (int)( timegm ( locg ) - mktime ( &locl ) ) / 60;
}
这是我的方法:
time_t z = 0;
struct tm * pdt = gmtime(&z);
time_t tzlag = mktime(pdt);
替代 struct tm
的自动本地存储:
struct tm dt;
memset(&dt, 0, sizeof(struct tm));
dt.tm_mday=1; dt.tm_year=70;
time_t tzlag = mktime(&dt);
tzlag
,以秒为单位,将是UTC偏移量的负数;与 UTC 相比,您的时区标准时间滞后:
LocalST + tzlag = UTC
如果您还想考虑“夏令时”,请从 tzlag
中减去 tm_isdst
,其中 tm_isdst
是特定当地时间 struct tm
的字段,在对其应用 mktime
之后(或在使用 localtime
获得它之后)。
为什么有效:
集合 struct tm
用于“纪元”时刻,即 1970 年 1 月 1 日,对应于 time_t
的 0。
在该日期调用 mktime()
会将其转换为 time_t
,就好像它是 UTC(因此得到 0),然后从中减去 UTC 偏移量以生成输出 time_t
。因此它产生 UTC_offset.