Tools: Debian Bookworm + Chrony in a Dual-Boot Setup: Why Windows and Linux Disagree on Time & Solution

Tools: Debian Bookworm + Chrony in a Dual-Boot Setup: Why Windows and Linux Disagree on Time & Solution

What if I want to make Linux see RTC as localtime not UTC?

Why this happens?

Chrony uses rtcsync

How does rtcsync work

How to check whether rtcsync is enabled

What is adjtimex()

How to tell the kernel RTC is in localtime

Where is the time set in UTC

What actually happens with dual boot Windows/Linux + chrony (rtcsync):

Fixing the Linux/Windows Dual-Boot Clock Issue with Chrony and rtcsyncon Linux side

Edge cases: The RTC is the motherboard clock that keeps time even when the machine is powered off. On boot, the OS reads that clock and decides how to interpret it. The conflict:

Linux usually treats the RTC as UTCWindows usually treats the RTC as local time The easiest solution is to change how Windows sees RTC: see article. A fast google search (example: linuxmint.com)will find this common solution that is detailed in timedatectl(1): After applying this fix, some people run into this problem (that I also found on this fedora forum): The issue is that Linux still writes the RTC in UTC. At shutdown, the hardware clock is left in UTC again. When Windows boots afterward, it reads that RTC value as local time, so the Windows system clock is shifted and appears incorrect. Let's take a look at the setup and configuration: Documentation note: chrony.conf(5); adjtimex(2); rtc.4; hwclock(8); With rtcsync enabled, the RTC is periodically synchronized with the system clock. According to chrony documentation: Using this command you do not check directly if rtcsync is on, but whether the hardware clock update is active when the kernel clock is synchronized (you can check kernel’s RTC “11 minute mode” behavior on/off). Bit 6 from status shows this. adjtimex() is a syscall. This is the struct sent to kernel: From what I read in the documentation, There is: In the struct timex definition, time is documented as “current time (read only)” as shown in the doc below. It’s a value we get back from the kernel, not something we set to change the system clock (from what I understood). The offset that can be adjusted is way too small to make up for a timezone. So chrony tells kernel to update RTC every 11 minutes. And kernel updates it in UTC, not local time. On kernel.org they say: When Linux developers talk about a “Real Time Clock”, they usually mean something that tracks wall clock time and is battery backed so that it works even with system power off. Such clocks will normally not track the local time zone or daylight savings time -- unless they dual boot with MS-Windows -- but will instead be set to Coordinated Universal Time (UTC, formerly “Greenwich Mean Time”). There is an option to inform the kernel that the RTC is in localtime: persistent_clock_is_local. This is documented here: hwclock(8):_ The Linux kernel’s timezone is set by hwclock. But don’t be misled — almost nobody cares what timezone the kernel thinks it is in. Instead, programs that care about the timezone (perhaps because they want to display a local time for you) almost always use a more traditional method of determining the timezone: They use the TZ environment variable or the /etc/localtime file, as explained in the man page for tzset(3). However, some programs and fringe parts of the Linux kernel such as filesystems use the kernel’s timezone value. An example is the vfat filesystem. If the kernel timezone value is wrong, the vfat filesystem will report and set the wrong timestamps on files. Another example is the kernel’s NTP '11 minute mode'. If the kernel’s timezone value and/or the persistent_clock_is_local variable are wrong, then the Hardware Clock will be set incorrectly by '11 minute mode'. See the discussion below, under Automatic Hardware Clock Synchronization by the Kernel. hwclock sets the kernel’s timezone to the value indicated by TZ or /etc/localtime with the --hctosys or --systz functions. If your system runs with '11 minute mode' on, it may need to use either --hctosys or --systz in a startup script, especially if the Hardware Clock is configured to use the local timescale. Unless the kernel is informed of what timescale the Hardware Clock is using, it may clobber it with the wrong one. The kernel uses UTC by default. The first userspace command to set the System Clock informs the kernel what timescale the Hardware Clock is using. This happens via the persistent_clock_is_local kernel variable. If --hctosys or --systz is the first, it will set this variable according to the adjtime file or the appropriate command-line argument. Note that when using this capability and the Hardware Clock timescale configuration is changed, then a reboot is required to notify the kernel. Inside the kernel code, time.c file: Seems like the kernel really only wants to treat the hardware RTC as UTC.Chrony is configured with rtcsync, which helps keep the RTC from drifting by having the kernel periodically copy the current system time into the RTC. Enabling rtcsync causes the kernel to use the classic “11-minute mode.” 11-minute mode means: when the system clock is synchronized, the kernel will automatically write the system time back to the RTC at intervals and also at certain events, so the RTC stays close to the correct time. Because the kernel assumes the RTC is UTC, if the RTC is configured as localtime, when the kernel reads the RTC expecting UTC but finds a localtime-based value, it applies a correction and “warps” RTC back to UTC. So with chrony using rtcsync to enable the 11-minute mode for kernel, the kernel will always write RTC as UTC. According to the documentation, chrony itself, does not write into the RTC with this mode on, but the kernel does. For better understanding I generated this image using Gemeni: This is not as easy as making Windows see RTC in UTC not localtime.

The first step is to make the changes described at wiki.archlinux.org. If you are using chrony with rtcsync, you need to modify the chrony configuration to (I assume NTP is configured as well for chrony): Note: When I try to run hwclock, I get: “device or resource busy, no usable interface found”. This happens (PROBABLY) because chronyd is holding/locking the RTC device. This would probably be a problem if other process tries to use it. Final Note: All artistic images were generated using Gemeni. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Code Block

Copy

[1] Boot Linux | v Linux writes RTC = UTC | | Example: | Real local time = 15:00 | Timezone = UTC+2 | RTC stored = 13:00 (UTC) v [2] Reboot into Windows | v Windows reads same RTC value as LOCAL TIME | | Reads RTC 13:00 | Interprets it as local time v [3] Windows shows wrong clock | | Displayed time = 13:00 | Expected time = 15:00 | Error = -2 hours v (Not tested yet, but intuitively expected - the next steps) [4] Windows may write RTC back as LOCAL TIME | | RTC becomes 15:00 (but stored as localtime) v [5] Reboot back into Linux | v Linux reads RTC again as UTC | | Reads RTC 15:00 | Interprets it as UTC v [6] Linux shows wrong clock too | | 15:00 UTC = 17:00 local time (UTC+2) | Linux clock is now wrong v Result: Linux and Windows keep reinterpreting the same hardware clock in different ways, so the system time differs after rebooting. [1] Boot Linux | v Linux writes RTC = UTC | | Example: | Real local time = 15:00 | Timezone = UTC+2 | RTC stored = 13:00 (UTC) v [2] Reboot into Windows | v Windows reads same RTC value as LOCAL TIME | | Reads RTC 13:00 | Interprets it as local time v [3] Windows shows wrong clock | | Displayed time = 13:00 | Expected time = 15:00 | Error = -2 hours v (Not tested yet, but intuitively expected - the next steps) [4] Windows may write RTC back as LOCAL TIME | | RTC becomes 15:00 (but stored as localtime) v [5] Reboot back into Linux | v Linux reads RTC again as UTC | | Reads RTC 15:00 | Interprets it as UTC v [6] Linux shows wrong clock too | | 15:00 UTC = 17:00 local time (UTC+2) | Linux clock is now wrong v Result: Linux and Windows keep reinterpreting the same hardware clock in different ways, so the system time differs after rebooting. [1] Boot Linux | v Linux writes RTC = UTC | | Example: | Real local time = 15:00 | Timezone = UTC+2 | RTC stored = 13:00 (UTC) v [2] Reboot into Windows | v Windows reads same RTC value as LOCAL TIME | | Reads RTC 13:00 | Interprets it as local time v [3] Windows shows wrong clock | | Displayed time = 13:00 | Expected time = 15:00 | Error = -2 hours v (Not tested yet, but intuitively expected - the next steps) [4] Windows may write RTC back as LOCAL TIME | | RTC becomes 15:00 (but stored as localtime) v [5] Reboot back into Linux | v Linux reads RTC again as UTC | | Reads RTC 15:00 | Interprets it as UTC v [6] Linux shows wrong clock too | | 15:00 UTC = 17:00 local time (UTC+2) | Linux clock is now wrong v Result: Linux and Windows keep reinterpreting the same hardware clock in different ways, so the system time differs after rebooting. timedatectl set-local-rtc 1 timedatectl set-local-rtc 1 timedatectl set-local-rtc 1 Linux running | +--> system clock synced by Chrony | +--> RTC written in UTC | Linux shutdown | +--> RTC not written at shutdown (see note) | Boot Windows | +--> Windows reads RTC as local time | +--> Windows system clock is wrong Linux running | +--> system clock synced by Chrony | +--> RTC written in UTC | Linux shutdown | +--> RTC not written at shutdown (see note) | Boot Windows | +--> Windows reads RTC as local time | +--> Windows system clock is wrong Linux running | +--> system clock synced by Chrony | +--> RTC written in UTC | Linux shutdown | +--> RTC not written at shutdown (see note) | Boot Windows | +--> Windows reads RTC as local time | +--> Windows system clock is wrong +-----------------------------------------------------------+ | System setup | +-----------------------------------------------------------+ +------------------- machine -------------------+ | | | Dual boot: Linux <-> Windows | | | +--------------------+--------------------------+ | v +----------------------+ | Hardware clock | | (RTC) | +----------------------+ ^ | rtcsync on | (chrony config) | | +------------------------- Linux side -------------------------+ | | | +-------------------+ syncs time +---------+ | | | chronyd | -----------------------> | System | | | | (Chrony) | | Clock | | | +-------------------+ +---------+ | | | | - Chrony manages Linux time | | - rtcsync takes care of RTC through kernel 11 minutes | +--------------------------------------------------------------+ +----------------------- Windows side -------------------------+ | | | Windows also reads the same RTC | | but may interpret it differently than Linux | | | +--------------------------------------------------------------+ +-----------------------------------------------------------+ | System setup | +-----------------------------------------------------------+ +------------------- machine -------------------+ | | | Dual boot: Linux <-> Windows | | | +--------------------+--------------------------+ | v +----------------------+ | Hardware clock | | (RTC) | +----------------------+ ^ | rtcsync on | (chrony config) | | +------------------------- Linux side -------------------------+ | | | +-------------------+ syncs time +---------+ | | | chronyd | -----------------------> | System | | | | (Chrony) | | Clock | | | +-------------------+ +---------+ | | | | - Chrony manages Linux time | | - rtcsync takes care of RTC through kernel 11 minutes | +--------------------------------------------------------------+ +----------------------- Windows side -------------------------+ | | | Windows also reads the same RTC | | but may interpret it differently than Linux | | | +--------------------------------------------------------------+ +-----------------------------------------------------------+ | System setup | +-----------------------------------------------------------+ +------------------- machine -------------------+ | | | Dual boot: Linux <-> Windows | | | +--------------------+--------------------------+ | v +----------------------+ | Hardware clock | | (RTC) | +----------------------+ ^ | rtcsync on | (chrony config) | | +------------------------- Linux side -------------------------+ | | | +-------------------+ syncs time +---------+ | | | chronyd | -----------------------> | System | | | | (Chrony) | | Clock | | | +-------------------+ +---------+ | | | | - Chrony manages Linux time | | - rtcsync takes care of RTC through kernel 11 minutes | +--------------------------------------------------------------+ +----------------------- Windows side -------------------------+ | | | Windows also reads the same RTC | | but may interpret it differently than Linux | | | +--------------------------------------------------------------+ adjtimex --print adjtimex --print adjtimex --print status & 0x0040 != 0 -> clock is UNSYNCHRONIZED status & 0x0040 == 0 -> clock is synchronized status & 0x0040 != 0 -> clock is UNSYNCHRONIZED status & 0x0040 == 0 -> clock is synchronized status & 0x0040 != 0 -> clock is UNSYNCHRONIZED status & 0x0040 == 0 -> clock is synchronized status = 0 -> synchronized -> RTC sync can be active status = 1 -> synchronized -> RTC sync can be active status = 64 -> unsynchronized -> RTC sync not active status = 65 -> unsynchronized -> RTC sync not active status = 0 -> synchronized -> RTC sync can be active status = 1 -> synchronized -> RTC sync can be active status = 64 -> unsynchronized -> RTC sync not active status = 65 -> unsynchronized -> RTC sync not active status = 0 -> synchronized -> RTC sync can be active status = 1 -> synchronized -> RTC sync can be active status = 64 -> unsynchronized -> RTC sync not active status = 65 -> unsynchronized -> RTC sync not active asmlinkage long sys_gettimeofday(struct timeval __user *tv, struct timezone __user *tz) { if (likely(tv != NULL)) { struct timeval ktv; do_gettimeofday(&ktv); if (copy_to_user(tv, &ktv, sizeof(ktv))) return -EFAULT; } if (unlikely(tz != NULL)) { if (copy_to_user(tz, &sys_tz, sizeof(sys_tz))) return -EFAULT; } return 0; } /* * Adjust the time obtained from the CMOS to be UTC time instead of * local time. * * This is ugly, but preferable to the alternatives. Otherwise we * would either need to write a program to do it in /etc/rc (and risk * confusion if the program gets run more than once; it would also be * hard to make the program warp the clock precisely n hours) or * compile in the timezone information into the kernel. Bad, bad.... * * - TYT, 1992-01-01 * * The best thing to do is to keep the CMOS clock in universal time (UTC) * as real UNIX machines always do it. This avoids all headaches about * daylight saving times and warping kernel clocks. */ static inline void warp_clock(void) { write_seqlock_irq(&xtime_lock); wall_to_monotonic.tv_sec -= sys_tz.tz_minuteswest * 60; xtime.tv_sec += sys_tz.tz_minuteswest * 60; time_interpolator_reset(); write_sequnlock_irq(&xtime_lock); clock_was_set(); } /* * In case for some reason the CMOS clock has not already been running * in UTC, but in some local time: The first time we set the timezone, * we will warp the clock so that it is ticking UTC time instead of * local time. Presumably, if someone is setting the timezone then we * are running in an environment where the programs understand about * timezones. This should be done at boot time in the /etc/rc script, * as soon as possible, so that the clock can be set right. Otherwise, * various programs will get confused when the clock gets warped. */ int do_sys_settimeofday(struct timespec *tv, struct timezone *tz) { static int firsttime = 1; int error = 0; if (tv && !timespec_valid(tv)) return -EINVAL; error = security_settime(tv, tz); if (error) return error; if (tz) { /* SMP safe, global irq locking makes it work. */ sys_tz = *tz; if (firsttime) { firsttime = 0; if (!tv) warp_clock(); } } if (tv) { /* SMP safe, again the code in arch/foo/time.c should * globally block out interrupts when it runs. */ return do_settimeofday(tv); } return 0; } asmlinkage long sys_gettimeofday(struct timeval __user *tv, struct timezone __user *tz) { if (likely(tv != NULL)) { struct timeval ktv; do_gettimeofday(&ktv); if (copy_to_user(tv, &ktv, sizeof(ktv))) return -EFAULT; } if (unlikely(tz != NULL)) { if (copy_to_user(tz, &sys_tz, sizeof(sys_tz))) return -EFAULT; } return 0; } /* * Adjust the time obtained from the CMOS to be UTC time instead of * local time. * * This is ugly, but preferable to the alternatives. Otherwise we * would either need to write a program to do it in /etc/rc (and risk * confusion if the program gets run more than once; it would also be * hard to make the program warp the clock precisely n hours) or * compile in the timezone information into the kernel. Bad, bad.... * * - TYT, 1992-01-01 * * The best thing to do is to keep the CMOS clock in universal time (UTC) * as real UNIX machines always do it. This avoids all headaches about * daylight saving times and warping kernel clocks. */ static inline void warp_clock(void) { write_seqlock_irq(&xtime_lock); wall_to_monotonic.tv_sec -= sys_tz.tz_minuteswest * 60; xtime.tv_sec += sys_tz.tz_minuteswest * 60; time_interpolator_reset(); write_sequnlock_irq(&xtime_lock); clock_was_set(); } /* * In case for some reason the CMOS clock has not already been running * in UTC, but in some local time: The first time we set the timezone, * we will warp the clock so that it is ticking UTC time instead of * local time. Presumably, if someone is setting the timezone then we * are running in an environment where the programs understand about * timezones. This should be done at boot time in the /etc/rc script, * as soon as possible, so that the clock can be set right. Otherwise, * various programs will get confused when the clock gets warped. */ int do_sys_settimeofday(struct timespec *tv, struct timezone *tz) { static int firsttime = 1; int error = 0; if (tv && !timespec_valid(tv)) return -EINVAL; error = security_settime(tv, tz); if (error) return error; if (tz) { /* SMP safe, global irq locking makes it work. */ sys_tz = *tz; if (firsttime) { firsttime = 0; if (!tv) warp_clock(); } } if (tv) { /* SMP safe, again the code in arch/foo/time.c should * globally block out interrupts when it runs. */ return do_settimeofday(tv); } return 0; } asmlinkage long sys_gettimeofday(struct timeval __user *tv, struct timezone __user *tz) { if (likely(tv != NULL)) { struct timeval ktv; do_gettimeofday(&ktv); if (copy_to_user(tv, &ktv, sizeof(ktv))) return -EFAULT; } if (unlikely(tz != NULL)) { if (copy_to_user(tz, &sys_tz, sizeof(sys_tz))) return -EFAULT; } return 0; } /* * Adjust the time obtained from the CMOS to be UTC time instead of * local time. * * This is ugly, but preferable to the alternatives. Otherwise we * would either need to write a program to do it in /etc/rc (and risk * confusion if the program gets run more than once; it would also be * hard to make the program warp the clock precisely n hours) or * compile in the timezone information into the kernel. Bad, bad.... * * - TYT, 1992-01-01 * * The best thing to do is to keep the CMOS clock in universal time (UTC) * as real UNIX machines always do it. This avoids all headaches about * daylight saving times and warping kernel clocks. */ static inline void warp_clock(void) { write_seqlock_irq(&xtime_lock); wall_to_monotonic.tv_sec -= sys_tz.tz_minuteswest * 60; xtime.tv_sec += sys_tz.tz_minuteswest * 60; time_interpolator_reset(); write_sequnlock_irq(&xtime_lock); clock_was_set(); } /* * In case for some reason the CMOS clock has not already been running * in UTC, but in some local time: The first time we set the timezone, * we will warp the clock so that it is ticking UTC time instead of * local time. Presumably, if someone is setting the timezone then we * are running in an environment where the programs understand about * timezones. This should be done at boot time in the /etc/rc script, * as soon as possible, so that the clock can be set right. Otherwise, * various programs will get confused when the clock gets warped. */ int do_sys_settimeofday(struct timespec *tv, struct timezone *tz) { static int firsttime = 1; int error = 0; if (tv && !timespec_valid(tv)) return -EINVAL; error = security_settime(tv, tz); if (error) return error; if (tz) { /* SMP safe, global irq locking makes it work. */ sys_tz = *tz; if (firsttime) { firsttime = 0; if (!tv) warp_clock(); } } if (tv) { /* SMP safe, again the code in arch/foo/time.c should * globally block out interrupts when it runs. */ return do_settimeofday(tv); } return 0; } rtcfile /var/lib/chrony/rtc rtcautotrim 20 #rtcsync disabled rtcfile /var/lib/chrony/rtc rtcautotrim 20 #rtcsync disabled rtcfile /var/lib/chrony/rtc rtcautotrim 20 #rtcsync disabled - No timezone parameter - No localtime/UTC flag - No RTC mode field - No hardware clock conversion setting - When offline or without NTP server set, RTC will not be written by chrony. Systemd does not write the RTC at shutdown anymore (see archieve)so any manual time set on system will happen for system clock and perhaps nothing will write the system clock to RTC.