Linux夏令时通知

我试图找到一种方法来在系统应用夏令时从系统(Linux)接收通知,但我似乎无法找到类似的东西。

考虑一个程序位于一个等待多个定时器fdpselect() ,所有这些定时器都有24小时的间隔,但是由用户定义的开始时间不同, “ 07:00 ON07:00 ON 07:25 OFF ”(例如,如果它是咖啡机)。

由于用户在当地时间给出了这些时间,并且Linux在UTC上运行,所以每当夏令时发生时,都需要重新调整时区调整的计时器fd 。 (用户在兼容夏令时的闹钟唤醒他时,会期待咖啡......)

正如我想象的那样,智能化的方法是在夏令时应用时向系统/内核/ init /注册任何要通知的内容,并避免陷入尝试自己确定这样的日期和时间的混乱业务并希望系统能够与您的结果一致(即您的再同步操作和实际夏令时同时发生)。

有什么方法可以通知DST更改吗? 或者可能对本地时间进行任何更改(假设DST更改会修改该更改)?


考虑一个程序坐在一个等待多个定时器fd的pselect()上,所有这些定时器都有24小时的间隔,但开始时间不同

这就是你的根本问题。 所有日子都不是完全24小时 - 有时他们是一个小时(夏令时),或秒(闰秒)关闭; 就像不是每个二月有28天。

一个更简单,更轻量级(更少资源消耗)的方式是使用UTC中未来事件的最小堆,就像

struct trigger {
    /* Details on how the event is defined;
       for example, "each day at 07:00 local time".
    */
};

struct utc_event {
    struct trigger  *trigger;
    time_t           when;
};

struct event_min_heap {
    size_t           max_events;
    size_t           num_events;
    struct utc_event event[];
};

struct event_min_heapevent C99灵活数组成员是一个数组,其中包含num_events事件(分配给max_events内存;如果需要更多事件,则可以重新分配内存),该事件由每个event条目中的when字段键入的最小堆中。 也就是说,最早的事件总是在根。

无论何时当前时间至少是event[0].when它被“触发” - 意味着要采取的任何动作 - 被采用 - 并且基于它指的struct trigger ,下一次出现该事件被更新为event[0] ,然后它在堆中渗透到适当的位置。 请注意,您只需使用mktime()从分解的本地时间字段获取UTC时间。

(如果这是一个多用户服务,那么通过将TZ环境变量设置为相应的时区定义,并在调用mktime()之前调用tzset() ,您可以支持多个并发时区,每个触发器一个时区,因为环境由进程中的所有线程共享,如果您有多线程进程,您需要确保一次只有一个线程执行此操作。通常情况下,使用单线程进程可以很好地实现这种功能。)

当根开始的事件( event[0]被删除或渗滤(筛分),与下一个最小的情况下when将在根。 如果when等于或小于在UTC当前时间,它也被触发。

当下一个when是在未来,该过程可以睡剩余间隔。

这就是它的全部。 你不需要多个计时器 - 这是一个系统范围内的有限资源 - ,你不需要担心当地时间是否是夏令时; C库mktime()将为您处理这些细节。


现在,如果您不喜欢这种方法(与您在问题中概述的方法相比,它再次使用更少的资源),请联系SystemD开发人员。 如果你足够亲近他们,我相信他们会为你提供一个dbus信号。 这并不是说它目前的设计有任何的理智,而且更多的疣肯定不会使它变得更糟。 切换到C#可能被认为是一个优点。


理解mktime()计算指定时刻的Unix Epoch时间( time_t )是非常重要的,如果它适用于特定时刻,则应用夏时制时间。 当函数被调用时,夏令时是否生效并不重要!

此外,UTC时间为协调世界时,并且不受时区或夏令时的限制。

考虑下面的程序mktime-example.c

#define  _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>

static time_t epoch(struct tm *const tm,
                    const int year, const int month, const int day,
                    const int hour, const int minute, const int second,
                    const int isdst)
{
    struct tm  temp;
    time_t     result;

    memset(&temp, 0, sizeof temp);
    temp.tm_year = year - 1900;
    temp.tm_mon = month - 1;
    temp.tm_mday = day;
    temp.tm_hour = hour;
    temp.tm_min = minute;
    temp.tm_sec = second;
    temp.tm_isdst = isdst;

    result = mktime(&temp);

    if (isdst >= 0 && isdst != temp.tm_isdst) {
        /* The caller is mistaken about DST, and mktime()
         * adjusted the time. We readjust it. */
        temp.tm_year = year - 1900;
        temp.tm_mon = month - 1;
        temp.tm_mday = day;
        temp.tm_hour = hour;
        temp.tm_min = minute;
        temp.tm_sec = second;
        /* Note: tmp.tm_isdst is kept unchanged. */

        result = mktime(&temp);
    }

    if (tm)
        memcpy(tm, &temp, sizeof temp);

    return result;
}

static void show(const time_t t, const struct tm *const tm)
{
    printf("(time_t)%lld = %04d-%02d-%02d %02d:%02d:%02d",
           (long long)t, tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
           tm->tm_hour, tm->tm_min, tm->tm_sec);

    if (tm->tm_isdst == 1)
        printf(", DST in effect");
    else
    if (tm->tm_isdst == 0)
        printf(", DST not in effect");
    else
    if (tm->tm_isdst == -1)
        printf(", Unknown if DST in effect");

    if (tzname[0] && tzname[0][0])
        printf(", %s timezone", tzname[0]);

    printf("n");
    fflush(stdout);
}

int main(int argc, char *argv[])
{
    struct tm  tm;
    time_t     t;
    long long  secs;
    int        arg, year, month, day, hour, min, sec, isdst, n;
    char       ch;

    if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "Usage: %s [ -h | --help ]n", argv[0]);
        fprintf(stderr, "       %s [ :REGION/CITY | =TIMEZONE ] @EPOCH | YYYYMMDD-HHMMSS[+-] ...n", argv[0]);
        fprintf(stderr, "Where:n");
        fprintf(stderr, "       EPOCH is in UTC seconds since 19700101T000000,n");
        fprintf(stderr, "       + after time indicates you prefer daylight savings time,n");
        fprintf(stderr, "       - after time indicates you prefer standard time.n");
        fprintf(stderr, "n");
        return EXIT_FAILURE;
    }

    for (arg = 1; arg < argc; arg++) {

        if (argv[arg][0] == ':') {
            if (argv[arg][1])
                setenv("TZ", argv[arg], 1);
            else
                unsetenv("TZ");
            tzset();
            continue;
        }

        if (argv[arg][0] == '=') {
            if (argv[arg][1])
                setenv("TZ", argv[arg] + 1, 1);
            else
                unsetenv("TZ");
            tzset();
            continue;
        }

        if (argv[arg][0] == '@') {
            if (sscanf(argv[arg] + 1, " %lld %c", &secs, &ch) == 1) {
                t = (time_t)secs;
                if (localtime_r(&t, &tm)) {
                    show(t, &tm);
                    continue;
                } 
            }
        }

        n = sscanf(argv[arg], " %04d %02d %02d %*[-Tt] %02d %02d %02d %c",
                              &year, &month, &day, &hour, &min, &sec, &ch);
        if (n >= 6) {
            if (n == 6)
                isdst = -1;
            else
            if (ch == '+')
                isdst = +1; /* DST */
            else
            if (ch == '-')
                isdst = 0;  /* Not DST */
            else
                isdst = -1;

            t = epoch(&tm, year, month, day, hour, min, sec, isdst);
            if (t != (time_t)-1) {
                show(t, &tm);
                continue;
            }
        }

        fflush(stdout);
        fprintf(stderr, "%s: Cannot parse parameter.n", argv[arg]);
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

用eg编译它

gcc -Wall -O2 mktime-example.c -o mktime-example

在没有参数的情况下运行它以查看命令行用法。 跑

./mktime-example :Europe/Helsinki 20161030-035959+ 20161030-030000- 20161030-030000+ 20161030-035959- 20161030-040000-

在芬兰赫尔辛基2016年DST结束的时候检查Unix时间戳。 该命令将输出

(time_t)1477789199 = 2016-10-30 03:59:59, DST in effect, EET timezone
(time_t)1477789200 = 2016-10-30 03:00:00, DST not in effect, EET timezone
(time_t)1477785600 = 2016-10-30 03:00:00, DST in effect, EET timezone
(time_t)1477792799 = 2016-10-30 03:59:59, DST not in effect, EET timezone
(time_t)1477792800 = 2016-10-30 04:00:00, DST not in effect, EET timezone

输出将是相同的,无论在运行此DST时是否在某个时区生效!

当使用.tm_isdst = 0.tm_isdst = 1调用mktime()并且mktime()更改它时,它也会更改指定的时间(按夏令时)。 当.tm_isdst = -1 ,意味着调用者不知道DST是否被应用,并且库会找出; 但是如果有一个有效的标准时间和DST时间,C库会选择一个(你应该假设它是随机的)。 上面的epoch()函数会在必要时纠正此问题,如果用户对DST不正确,请不要调整时间。


Unix / linux系统只处理UTC,并且它们使用time_t数据(自00:00h 1月1日,1970年1月1日至今)的秒数作为内部时间。 只有在向用户显示信息时才能进行当地时间的转换(由于例外情况,夏季 - 冬季期间的变化等原因而导致的复杂性),因此只有在转换为本地时间后才能完成。 如前所述,在Unix系统中没有提供任何安排或准备工作的规定。

zdump(1)你可以得到你想要的所有信息,每个时区,并使用它来构建一个crontab来通知你何时切换。 它咨询当地的时区数据库,并提取从冬季到夏季或相反的所有关于切换(包括历史)的信息。

$ zdump -v Europe/Madrid
Europe/Madrid  Fri Dec 13 20:45:52 1901 UTC = Fri Dec 13 20:45:52 1901 WET isdst=0 gmtoff=0
Europe/Madrid  Sat Dec 14 20:45:52 1901 UTC = Sat Dec 14 20:45:52 1901 WET isdst=0 gmtoff=0
Europe/Madrid  Sat May  5 22:59:59 1917 UTC = Sat May  5 22:59:59 1917 WET isdst=0 gmtoff=0
Europe/Madrid  Sat May  5 23:00:00 1917 UTC = Sun May  6 00:00:00 1917 WEST isdst=1 gmtoff=3600
Europe/Madrid  Sat Oct  6 22:59:59 1917 UTC = Sat Oct  6 23:59:59 1917 WEST isdst=1 gmtoff=3600
Europe/Madrid  Sat Oct  6 23:00:00 1917 UTC = Sat Oct  6 23:00:00 1917 WET isdst=0 gmtoff=0
Europe/Madrid  Mon Apr 15 22:59:59 1918 UTC = Mon Apr 15 22:59:59 1918 WET isdst=0 gmtoff=0
Europe/Madrid  Mon Apr 15 23:00:00 1918 UTC = Tue Apr 16 00:00:00 1918 WEST isdst=1 gmtoff=3600
Europe/Madrid  Sun Oct  6 22:59:59 1918 UTC = Sun Oct  6 23:59:59 1918 WEST isdst=1 gmtoff=3600
Europe/Madrid  Sun Oct  6 23:00:00 1918 UTC = Sun Oct  6 23:00:00 1918 WET isdst=0 gmtoff=0
Europe/Madrid  Sat Apr  5 22:59:59 1919 UTC = Sat Apr  5 22:59:59 1919 WET isdst=0 gmtoff=0
Europe/Madrid  Sat Apr  5 23:00:00 1919 UTC = Sun Apr  6 00:00:00 1919 WEST isdst=1 gmtoff=3600
Europe/Madrid  Mon Oct  6 22:59:59 1919 UTC = Mon Oct  6 23:59:59 1919 WEST isdst=1 gmtoff=3600
Europe/Madrid  Mon Oct  6 23:00:00 1919 UTC = Mon Oct  6 23:00:00 1919 WET isdst=0 gmtoff=0
Europe/Madrid  Wed Apr 16 22:59:59 1924 UTC = Wed Apr 16 22:59:59 1924 WET isdst=0 gmtoff=0
Europe/Madrid  Wed Apr 16 23:00:00 1924 UTC = Thu Apr 17 00:00:00 1924 WEST isdst=1 gmtoff=3600
Europe/Madrid  Sat Oct  4 22:59:59 1924 UTC = Sat Oct  4 23:59:59 1924 WEST isdst=1 gmtoff=3600
Europe/Madrid  Sat Oct  4 23:00:00 1924 UTC = Sat Oct  4 23:00:00 1924 WET isdst=0 gmtoff=0
Europe/Madrid  Sat Apr 17 22:59:59 1926 UTC = Sat Apr 17 22:59:59 1926 WET isdst=0 gmtoff=0
Europe/Madrid  Sat Apr 17 23:00:00 1926 UTC = Sun Apr 18 00:00:00 1926 WEST isdst=1 gmtoff=3600
Europe/Madrid  Sat Oct  2 22:59:59 1926 UTC = Sat Oct  2 23:59:59 1926 WEST isdst=1 gmtoff=3600
Europe/Madrid  Sat Oct  2 23:00:00 1926 UTC = Sat Oct  2 23:00:00 1926 WET isdst=0 gmtoff=0
Europe/Madrid  Sat Apr  9 22:59:59 1927 UTC = Sat Apr  9 22:59:59 1927 WET isdst=0 gmtoff=0
...

顺便说一下,如果您想要告知即将发生的本地时间更改,可以使用先前的信息构建一个crontab文件,其中包括所有信息,或者只需构建一个包含适用于您本地的规则的crontab文件。 例如,如果我希望在西班牙交换机更改前一天(在03/03/03月的最后一个星期日更改),可以在您的crontab文件中添加一些规则:

0 0 24-30 3,10 5 echo Time daylight savings change scheduled for tomorrow | mail $USER@your.domain.com

并且每个星期六(5)会在每个月的00:00(当地时间)的3月24日至30日和10月(3,10部分)的一周内向您发送一封邮件。 我相信你能够将这个例子适应于你的地方或进步的时间(所以,在时间变化发生前一天)。

链接地址: http://www.djcxy.com/p/5003.html

上一篇: Linux daylight savings notification

下一篇: Daylight Saving Time change schedule trigger for a marginal time