Рассмотрим программу сидит на 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[];
};
Гибкий элемент массива event
C99 в struct event_min_heap
массив с num_events
события (память, выделенная для max_events
; может быть перераспределена, если требуется больше событий) в min heap, введенном в поле when
в каждой записи event
. То есть, самое раннее событие всегда в корне.
Когда текущее время составляет не менее event[0].when
, оно «срабатывает» - означает, что любое действие должно быть принято, и на основании struct trigger
оно относится к времени следующего появления этого события обновляется до event[0]
, затем он просачивается в кучу в нужное место. Обратите внимание, что вы просто используете mktime()
, чтобы получить время UTC из разбитых локальных полей времени.
(Если бы это была услуга многопользовательской, то вы можете поддерживать несколько одновременных часовые пояса, по одному для каждого триггера, установив переменную TZ
окружения соответствующего определения часового пояса, и вызова tzset()
перед вызовом mktime()
. Поскольку среда разделяется всеми потоками процесса, вам нужно будет обеспечить, чтобы только один поток выполнял это за один раз, если у вас многопоточный процесс. Обычно такие вещи прекрасно реализуются с использованием однопоточного процесса.)
Когда событие в корне (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;
}
компилировать его использованием, например,
gcc -Wall -O2 mktime-example.c -o mktime-example
Запустите его без аргументов, чтобы увидеть использование командной строки. Запуск
./mktime-example :Europe/Helsinki 20161030-035959+ 20161030-030000- 20161030-030000+ 20161030-035959- 20161030-040000-
изучить Unix метки вокруг того времени, когда DST заканчивается в 2016 году в Хельсинки, Финляндия. Команда вывод
будет
(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
выход будет одинаковым, независимо от того, в момент запуска этого перехода на летнее время действует в каком-то часовом поясе или нет!
При вызове mktime()
с .tm_isdst = 0
или .tm_isdst = 1
и mktime()
изменяет его, он также изменяет заданное время (а в летнее время). Когда .tm_isdst = -1
, это означает, что вызывающий абонент не знает, применяется ли DST или нет, и библиотека узнает; но если есть и действующее стандартное время и время DST, библиотека C выберет один (вы должны предположить, что он делает это случайным образом). Функция epoch()
выше, если необходимо, исправляет это, отменяя время, если пользователь не подходит к DST.
Если я полностью понял, основной смысл заключается в перепланировании после истечения каждого события, но только один (следующий за триггером), чтобы сохранить таймеры. Перепланирование будет поддерживать события в эффективном локальном времени, и в худшем случае я бы смотрел 23-25-часовые периоды два раза в год, когда событие не срабатывало в правильное время. (случай, когда запускается только одно событие, запускающее 23:59 и получение перенесенного, что за одну минуту до того, как местное время перескочит с DST). И если я точно знаю, что DST прыгает всегда, независимо от TZ, в полночь, я мог бы ежедневно проверять и корректировать, если я нахожу '.tm_gmtoff' изменено. – Tammi
@Tammi: ** Нет! ** Всякий раз, когда событие срабатывает, вы используете 'mktime()', чтобы узнать время UTC в следующий раз, когда должно произойти событие. Обработка часового пояса библиотеки C достаточно интеллектуальна, чтобы определить это время в UTC, в том числе применительно к летнему времени. * Все события будут срабатывать в правильное время. * Я попытаюсь добавить практический пример в свой ответ. –
@Tammi: В системах POSIXy, включая Linux и Mac OS, переход на летнее время - это не какой-то глобальный флаг, который включен и выключен некоторыми службами операционной системы. Это свойство часового пояса. Библиотека 'mktime()' библиотеки C выполняет разбитые локальные поля времени и преобразует ее в UTC (Epoch), применяя текущие правила часового пояса, включая DST * для целевого времени *. Не имеет значения, применяется ли DST в момент вызова. Когда вы используете временные метки UTC, «прыжки с DST» не имеют значения: * мы готовимся к ним заранее *, поэтому на самом деле нет «прыжков». –