smearing servers.
[[leapsectz]]*leapsectz* _timezone_::
-This directive is used to set the name of the timezone in the system tz
-database which *chronyd* can use to find out when will the next leap second
-occur. It will periodically check if the times 23:59:59 and 23:59:60 are valid
-on Jun 30 and Dec 31 in the timezone. This typically works with the *right/UTC*
-timezone.
+This directive specifies a timezone in the system tz database which *chronyd*
+can use to determine when will the next leap second occur and what is the
+current offset between TAI and UTC. It will periodically check if 23:59:59 and
+23:59:60 are valid times in the timezone. This typically works with the
+_right/UTC_ timezone.
+
-This directive is mainly useful with reference clocks which do not provide
-leap second information. It is not necessary to restart *chronyd* if the tz
-database is updated with a new leap second at least 12 hours before the event.
+When a leap second is announced, the timezone needs to be updated at least 12
+hours before the leap second. It is not necessary to restart *chronyd*.
++
+This directive is useful with reference clocks and other time sources which do
+not announce leap seconds, or announce them too late for an NTP server to
+forward them to its own clients. Clients of leap smearing servers must not
+use this directive.
++
+It is also useful when the system clock is required to have correct TAI-UTC
+offset. Note that the offset is set only when leap seconds are handled by the
+kernel, i.e. <<leapsecmode,*leapsecmode*>> is set to *system*.
+
An example of the directive is:
+
to enable clients to use the interleaved mode even when the server has a large
number of clients, and better support rate limiting if it is enabled by the
<<ratelimit,*ratelimit*>> directive. The system timezone database, if it is
-kept up to date and includes the *right/UTC* timezone, can be used as a
+kept up to date and includes the _right/UTC_ timezone, can be used as a
reliable source to determine when a leap second will be applied to UTC. The
*-r* option with the <<dumpdir,*dumpdir*>> directive shortens the time in which
*chronyd* will not be able to serve time to its clients when it needs to be
static double local_distance;
static NTP_Leap our_leap_status;
static int our_leap_sec;
+static int our_tai_offset;
static int our_stratum;
static uint32_t our_ref_id;
static IPAddr our_ref_ip;
/* ================================================== */
-static NTP_Leap get_tz_leap(time_t when);
+static NTP_Leap get_tz_leap(time_t when, int *tai_offset);
static void update_leap_status(NTP_Leap leap, time_t now, int reset);
/* ================================================== */
FILE *in;
double file_freq_ppm, file_skew_ppm;
double our_frequency_ppm;
+ int tai_offset;
mode = REF_ModeNormal;
are_we_synchronised = 0;
our_leap_status = LEAP_Unsynchronised;
our_leap_sec = 0;
+ our_tai_offset = 0;
initialised = 1;
our_root_dispersion = 1.0;
our_root_delay = 1.0;
leap_tzname = CNF_GetLeapSecTimezone();
if (leap_tzname) {
/* Check that the timezone has good data for Jun 30 2012 and Dec 31 2012 */
- if (get_tz_leap(1341014400) == LEAP_InsertSecond &&
- get_tz_leap(1356912000) == LEAP_Normal) {
+ if (get_tz_leap(1341014400, &tai_offset) == LEAP_InsertSecond && tai_offset == 34 &&
+ get_tz_leap(1356912000, &tai_offset) == LEAP_Normal && tai_offset == 35) {
LOG(LOGS_INFO, "Using %s timezone to obtain leap second data", leap_tzname);
} else {
LOG(LOGS_WARN, "Timezone %s failed leap second check, ignoring", leap_tzname);
/* ================================================== */
static NTP_Leap
-get_tz_leap(time_t when)
+get_tz_leap(time_t when, int *tai_offset)
{
static time_t last_tz_leap_check;
static NTP_Leap tz_leap;
+ static int tz_tai_offset;
+
struct tm stm;
time_t t;
char *tz_env, tz_orig[128];
+ *tai_offset = tz_tai_offset;
+
/* Do this check at most twice a day */
when = when / (12 * 3600) * (12 * 3600);
if (last_tz_leap_check == when)
last_tz_leap_check = when;
tz_leap = LEAP_Normal;
+ tz_tai_offset = 0;
stm = *gmtime(&when);
- if (!is_leap_second_day(&stm))
- return tz_leap;
-
/* Temporarily switch to the timezone containing leap seconds */
tz_env = getenv("TZ");
if (tz_env) {
setenv("TZ", leap_tzname, 1);
tzset();
+ /* Get the TAI-UTC offset, which started at the epoch at 10 seconds */
+ t = mktime(&stm);
+ if (t != -1)
+ tz_tai_offset = t - when + 10;
+
/* Set the time to 23:59:60 and see how it overflows in mktime() */
stm.tm_sec = 60;
stm.tm_min = 59;
else if (stm.tm_sec == 1)
tz_leap = LEAP_DeleteSecond;
+ *tai_offset = tz_tai_offset;
+
return tz_leap;
}
{
leap_timeout_id = 0;
leap_in_progress = 0;
+
+ if (our_tai_offset)
+ our_tai_offset += our_leap_sec;
our_leap_sec = 0;
if (leap_mode == REF_LeapModeSystem)
- LCL_SetSystemLeap(our_leap_sec, 0);
+ LCL_SetSystemLeap(our_leap_sec, our_tai_offset);
if (our_leap_status == LEAP_InsertSecond ||
our_leap_status == LEAP_DeleteSecond)
static void
update_leap_status(NTP_Leap leap, time_t now, int reset)
{
- int leap_sec;
+ NTP_Leap tz_leap;
+ int leap_sec, tai_offset;
leap_sec = 0;
+ tai_offset = 0;
- if (leap_tzname && now && leap == LEAP_Normal)
- leap = get_tz_leap(now);
+ if (leap_tzname && now) {
+ tz_leap = get_tz_leap(now, &tai_offset);
+ if (leap == LEAP_Normal)
+ leap = tz_leap;
+ }
if (leap == LEAP_InsertSecond || leap == LEAP_DeleteSecond) {
/* Check that leap second is allowed today */
}
}
- if (leap_sec != our_leap_sec && !REF_IsLeapSecondClose()) {
+ if ((leap_sec != our_leap_sec || tai_offset != our_tai_offset)
+ && !REF_IsLeapSecondClose()) {
our_leap_sec = leap_sec;
+ our_tai_offset = tai_offset;
switch (leap_mode) {
case REF_LeapModeSystem:
- LCL_SetSystemLeap(our_leap_sec, 0);
+ LCL_SetSystemLeap(our_leap_sec, our_tai_offset);
/* Fall through */
case REF_LeapModeSlew:
case REF_LeapModeStep: