确定 DST 是不是对给定 time_t 的指定时区有效

Posted

技术标签:

【中文标题】确定 DST 是不是对给定 time_t 的指定时区有效【英文标题】:Determine if DST is in effect for a specified timezone given a time_t确定 DST 是否对给定 time_t 的指定时区有效 【发布时间】:2016-08-11 18:34:53 【问题描述】:

如果仅给定time_t,我如何确定夏令时是否对指定时区有效?我read 说“在 POSIX 系统中,用户可以通过 TZ 环境变量指定时区。”我在想我可以保存当前的 TZ 值(如果已设置),将其更改为我感兴趣的 TZ,调用localtime() 并检查tm_isdst,然后将 TZ 更改回原来的值。我只是不确定它的便携性如何。

在给定 C 语言 time_t 的情况下,是否有一种可移植的方法来确定时区的 DST?

【问题讨论】:

"便携方式" --> 不。高度便携(在 POSIX 中) --> 可能。由于帖子未标记 [POSIX] ,因此不清楚您是在寻找 POSIX 答案还是便携式(标准 C)答案。 请问为什么你想知道夏令时是否生效?您是否只是计划显示类似“嘿,用户,您知道 DST 现在生效了吗?”或者这是您正在尝试的更复杂的事情的一部分?通常我问这个的时候,答案都是后者,解决办法就是使用库,比如CCTZ或者HHinnet的date和tz库。 @chux 如果可能的话,我正在寻找便携版。截至目前,代码将在 POSIX 环境中运行,但谁知道未来会怎样。 @MattJohnson 我想在特定时间(东部/美国)执行操作,但代码将在不同的时区运行。我会研究这些库。 不要在代码中写入任何取决于代码运行的时区的内容,除非您实际上打算暗示“本地时间”,例如在显示给用户的时钟中设备。在服务器端代码中,以及在许多其他情况下,只需从机器获取 UTC 时间,并自行处理时区转换。此外,使用标准标识符,US/Eastern 是可以的,但它是 America/New_York 的向后兼容条目,这是首选。 【参考方案1】:

这是我能做到的便携性。我会对任何更好的解决方案感兴趣。我所做的是计算给定年份的 America/New_York 时区从纪元到 DST 开始和结束的时间,并测试给定的 time_t 是否介于两者之间。这是特定于 America/New_York 时区的,但我想它可以很容易地适应另一个时区,或者通过一些努力适应任何/所有时区。

如果使用 GNU C 库,timegm 可以用来代替getenvmktimesetenv但是根据GNU.org:

mktime 基本上是普遍可用的。 timegm 相当罕见。 对于从 UTC 分解时间到最便携的转换 简单的时候,将TZ环境变量设置为UTC,调用mktime,然后 将TZ 设置回来。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>

/***********************************************\
 * In America/New_York:
 * DST begins: second Sunday in March 02:00 local, after which EDT == UTC-04:00
 * DST ends: first  Sunday in November 02:00 local, after which EST == UTC-05:00
\***********************************************/

//Return 1 if the year at UTC is greater than the year in America/New_York at
//the given time t.  In other words, at time t, is it between 00:00:00 UTC 
//(midnight) Jan 1 and 05:00:00 UTC Jan 1.  Return 0 if the year at UTC is the 
//same as America/New_York at time t.
int UTCyearIsGreater(time_t when) 
    time_t begin, end;
    struct tm* tm;
    tm = gmtime(&when);
    if (tm->tm_mon == 11 && tm->tm_mday == 31 &&
            (tm->tm_hour >= 19 && tm->tm_hour < 5)) 
        return 1;
    
    return 0;


//Return number of seconds from epoch until DST begins/began in America/New_York, the second Sunday in March (ssim).
//for the given year.
time_t ssim(int year) 
    time_t t, t2;
    int sim = 0;
    struct tm tm = 0;
    tm.tm_year = year;
    tm.tm_mon = 2;
    tm.tm_mday = 1;
    tm.tm_hour = 7;
    char* env;
    env = getenv("TZ");
    setenv("TZ", "UTC", 1);
    t = mktime(&tm);
    tm = *gmtime(&t);
    while (sim < 2) 
        if (tm.tm_wday == 0) 
            sim += 1;
            if (sim == 2)  break; 
        
        tm.tm_mday += 1;
        tm.tm_wday = 0;
        t = mktime(&tm);
        tm = *gmtime(&t);
    
    t = mktime(&tm);
    if (env == NULL) 
        unsetenv("TZ");
     else 
        setenv("TZ", env, 1);
    
    return t;


//Return number of seconds from epoch until DST ends/ended in America/New_York, the first Sunday in November (fsin).
//for the given year.
time_t fsin(int year) 
    time_t t;
    struct tm tm = 0;
    tm.tm_year = year;
    tm.tm_mon = 10;
    tm.tm_mday = 1;
    tm.tm_hour = 6;
    char* env;
    env = getenv("TZ");
    setenv("TZ", "UTC", 1);
    t = mktime(&tm);
    tm = *gmtime(&t);
    while (1) 
        if (tm.tm_wday == 0)  break; 
        tm.tm_mday += 1;
        tm.tm_wday = 0;
        t = mktime(&tm);
        tm = *gmtime(&t);
    
    t = mktime(&tm);
    if (env == NULL) 
        unsetenv("TZ");
     else 
        setenv("TZ", env, 1);
    
    return t;


//Return 1 if DST is in effect in America/New_York at time t, return 0 otherwise
int DSTinNYC(time_t t) 
    time_t beginDST, endDST;
    struct tm* tm_ptr;
    tm_ptr = gmtime(&t);
    if (UTCyearIsGreater(t)) 
        tm_ptr->tm_year -= 1;
    
    beginDST = ssim(tm_ptr->tm_year);
    endDST = fsin(tm_ptr->tm_year);
    return (t >= beginDST && t < endDST);


int main() 
    //test it
    if (DSTinNYC(1461179392))  
        printf("CORRECT 20 Apr 2016 15:09:52 EDT\n");
     else 
        printf("FAILED 20 Apr 2016 15:09:52 EDT\n");
    
    if (DSTinNYC(1455993975))  
        printf("FAILED 20 Feb 2016 13:46:15 EST\n");
     else 
        printf("CORRECT 20 Feb 2016 13:46:15 EST\n");
    
    if (DSTinNYC(1571179392))  
        printf("CORRECT 15 Oct 2019 18:43:12 EDT\n");
     else 
        printf("FAILED 15 Oct 2019 18:43:12 EDT\n");
    
    //results checked with http://www.epochconverter.com/
    return 0;

【讨论】:

这也仅限于纽约的 当前 组 DST 转换。美国上一次改变规则是在 2007 年,但其他国家一直在改变。这就是为什么 POSIX TZ 变量不够好 - 它们只能支持两个转换,并且不能处理逐年变化。 TZ database 对于正确执行此操作至关重要。 顺便说一句 - 您的代码基本上重新创建了 EST5EDT,M3.2.0/2,M11.1.0/2 的 POSIX TZ var 的效果 没错,但在没有可行的 C 库的情况下,还能做什么? GNU C 库支持 POSIX 字符串,就像我刚才提到的那样。见gnu.org/software/libc/manual/html_node/TZ-Variable.html 这里列出了许多其他的实现:iana.org/time-zones/repository/tz-link.html也许其中之一可以满足您的目的。

以上是关于确定 DST 是不是对给定 time_t 的指定时区有效的主要内容,如果未能解决你的问题,请参考以下文章

确定夏令时 (DST) 在 Java 中是不是在指定日期处于活动状态

如何确定夏令时是不是在 C 中处于活动状态?

C++ timegm 将 DST 转换为将来给定时间的某个时区?

Python & pytz:确定时区是不是支持 DST 以及 DST 和非 DST UTC 偏移量是多少

给定日期/时间作为字符串,知道当时是不是是 DST 的最佳方法是啥?

如何确定 DST 何时在 Python 中的特定位置开始或结束? [复制]