在我之前关于 systemd 系列的七篇文章中,尤其是在最近的文章中,时间和日期在多种上下文中被提及。systemd 使用日历时间,指定一个或多个时间点来触发事件(例如备份程序),以及日志中的时间戳条目。它还可以使用时间跨度,时间跨度定义两个事件之间的时间量,但不直接与特定的日历时间相关联。
在本文中,我将更详细地了解如何在 systemd 中使用和指定时间和日期。此外,由于 systemd 使用两种略有不同且不兼容的时间格式,我将解释它们的使用方式和时间。
定义
以下是一些重要的与时间和日历相关的 systemd 术语,需要理解
-
绝对时间戳: 以
YYYY-MM-DD HH:MM:SS
格式定义的单个明确且唯一的时间点。时间戳格式指定计时器触发事件的时间点。绝对时间戳只能表示单个时间点,例如2025-04-15 13:21:05
。 -
精度是指接近真实时间的质量;换句话说,计时器触发事件的时间与指定的日历时间有多接近。systemd 计时器的默认精度定义为从定义的日历时间开始的一分钟时间跨度。例如,指定在
OnCalendar
时间 09:52:17 发生的事件可能在此时刻到 09:53:17 之间的任何时间触发。 -
日历事件是以
YYYY-MM-DD HH:MM:SS
格式的 systemd 时间戳指定的一个或多个特定时间。它可以是单个时间点或一系列时间点,这些时间点定义明确,并且可以计算出确切的时间。systemd 日志使用时间戳来标记每个事件发生的准确时间。在 systemd 中,确切的时间以时间戳格式
YYYY-MM-DD HH:MM:SS
指定。当仅指定YYYY-MM-DD
部分时,时间默认为 00:00:00。当仅指定HH:MM:SS
部分时,日期是该时间的下一个日历实例。如果指定的时间早于当前时间,则下一个实例将是明天,如果指定的时间晚于当前时间,则下一个实例将是今天。这是 systemd 计时器用来表示OnCalendar
时间的格式。可以使用特殊字符和格式来指定重复发生的日历事件,这些字符和格式表示具有多个值匹配的字段。例如,
2026-08-15..25 12:15:00
表示 2026 年 8 月 15 日至 25 日的下午 12:15,将触发 11 个匹配项。日历事件也可以使用绝对时间戳指定。 -
时间跨度是两个事件之间的时间量,或事件之类的持续时间,或两个事件之间的时间。时间跨度可用于指定计时器触发事件所需的精度,并定义事件之间经过的时间。systemd 识别以下时间单位
- usec, us, µs(微秒)
- msec, ms(毫秒)
- seconds, second, sec, s(秒)
- minutes, minute, min, m(分钟)
- hours, hour, hr, h(小时)
- days, day, d(天)
- weeks, week, w(周)
- months, month, M(月,定义为 30.44 天)
- years, year, y(年,定义为 365.25 天)
systemd.time(7)
手册页对计时器和其他 systemd 工具中的时间和日期表达式进行了完整描述。
日历事件表达式
日历事件表达式是重复触发计时器的关键部分。systemd 及其计时器不使用与 crontab 相同的时间和日期表达式样式。systemd 也比 crontab 更灵活,并允许类似于 at
命令的模糊日期和时间。
OnCalendar=
用于日历事件表达式的格式是 DOW YYYY-MM-DD HH:MM:SS
。DOW(星期几)是可选的,其他字段可以使用星号 (*
) 来匹配该位置的任何值。如果未指定时间,则假定为 00:00:00。如果未指定日期但指定了时间,则下一个匹配项可能是今天或明天,具体取决于当前时间。所有各种日历时间表达式格式都将转换为规范化形式,systemd-analyze calendar
命令显示时间表达式的规范化形式。
systemd 提供了一个出色的工具,用于验证和检查表达式中使用的日历事件。systemd-analyze calendar
工具解析日历时间事件表达式,并提供规范化形式和其他信息,例如下一个“elapse”(即,匹配)的日期和时间,以及到达触发时间之前的大概时间。
注意: 所有以下命令都可以由非 root 用户执行,“Next elapse”和“UTC”的时间将根据您的本地时区而有所不同。
首先,查看 systemd-analyze calendar
命令的语法。从没有时间的未来日期开始。由于所有日期单位字段都已明确指定,因此这是一个一次性事件
[student@testvm1 ~]$ systemd-analyze calendar 2030-06-17
Original form: 2030-06-17
Normalized form: 2030-06-17 00:00:00
Next elapse: Mon 2030-06-17 00:00:00 EDT
(in UTC): Mon 2030-06-17 04:00:00 UTC
From now: 10 years 0 months left
添加时间(在本例中,日期和时间被分别分析为不相关的实体)
[root@testvm1 system]# systemd-analyze calendar 2030-06-17 15:21:16
Original form: 2030-06-17
Normalized form: 2030-06-17 00:00:00
Next elapse: Mon 2030-06-17 00:00:00 EDT
(in UTC): Mon 2030-06-17 04:00:00 UTC
From now: 10 years 0 months left
Original form: 15:21:16
Normalized form: *-*-* 15:21:16
Next elapse: Mon 2020-06-15 15:21:16 EDT
(in UTC): Mon 2020-06-15 19:21:16 UTC
From now: 3h 55min left
要将日期和时间作为一个实体进行分析,请将它们一起放在引号中
[student@testvm1 system]# systemd-analyze calendar "2030-06-17 15:21:16"
Normalized form: 2030-06-17 15:21:16
Next elapse: Mon 2030-06-17 15:21:16 EDT
(in UTC): Mon 2030-06-17 19:21:16 UTC
From now: 10 years 0 months left
指定一个早于当前时间的时间和一个晚于当前时间的时间。在本例中,当前时间是 2019-05-15 的 16:16
[student@testvm1 ~]$ systemd-analyze calendar 15:21:16 22:15
Original form: 15:21:16
Normalized form: *-*-* 15:21:16
Next elapse: Fri 2019-05-17 15:21:16 EDT
(in UTC): Fri 2019-05-17 19:21:16 UTC
From now: 23h left
Original form: 22:15
Normalized form: *-*-* 22:15:00
Next elapse: Thu 2019-05-16 22:15:00 EDT
(in UTC): Fri 2019-05-17 02:15:00 UTC
From now: 5h 59min left
systemd-analyze calendar
工具不适用于时间戳。因此,像“tomorrow”或“today”这样的词语,如果您将它们与 calendar 子命令一起使用,将会导致错误,因为它们是时间戳而不是 OnCalendar
时间格式
[student@testvm1 ~]$ systemd-analyze calendar "tomorrow"
Failed to parse calendar expression 'tomorrow': Invalid argument
Hint: this expression is a valid timestamp. Use 'systemd-analyze timestamp "tomorrow"' instead?
术语“tomorrow”将始终解析为明天的日期和 00:00:00 的时间。您必须使用规范化的表达式格式 YYYY-MM-DD HH:MM:SS
,此工具才能在日历模式下工作。尽管如此,systemd-analyze calendar
工具仍然可以帮助您了解 systemd 计时器使用的日历时间表达式的结构。我建议阅读 systemd.time(7)
手册页,以更好地理解可用于 systemd 计时器的时间格式。
为什么像 OnCalendar=tomorrow
这样的语句在计时器中使用时会失败?
时间戳
日历时间可用于匹配单个或多个时间点,而时间戳明确地表示单个时间点。例如,systemd 日志中的时间戳指的是每个记录事件发生的精确时刻
[student@testvm1 ~]$ journalctl -S today
Hint: You are currently not seeing messages from other users and the system.
Users in groups 'adm', 'systemd-journal', 'wheel' can see all messages.
Pass -q to turn off this notice.
-- Logs begin at Wed 2020-06-17 10:08:41 EDT, end at Wed 2020-06-17 10:13:55 EDT. --
Jun 17 10:08:41 testvm1.both.org systemd[1137785]: Started Mark boot as successful after the user session has run 2 minutes.
Jun 17 10:08:41 testvm1.both.org systemd[1137785]: Started Daily Cleanup of User's Temporary Directories.
Jun 17 10:08:41 testvm1.both.org systemd[1137785]: Reached target Paths.
Jun 17 10:08:41 testvm1.both.org systemd[1137785]: Reached target Timers.
<SNIP>
Jun 17 10:13:55 testvm1.both.org systemd[1137785]: systemd-tmpfiles-clean.service: Succeeded.
Jun 17 10:13:55 testvm1.both.org systemd[1137785]: Finished Cleanup of User's Temporary Files and Directories.
systemd-analyze timestamp
命令可用于分析时间戳表达式,就像它分析日历表达式一样。以下是来自日志数据流的示例
[student@testvm1 ~]$ systemd-analyze timestamp "Jun 17 10:08:41"
Failed to parse "Jun 17 10:08:41": Invalid argument
[student@testvm1 ~]$ systemd-analyze timestamp Jun 17 10:08:41
Failed to parse "Jun": Invalid argument
Failed to parse "17": Invalid argument
Hint: this expression is a valid timespan. Use 'systemd-analyze timespan "17"' instead?
Original form: 10:08:41
Normalized form: Wed 2020-06-17 10:08:41 EDT
(in UTC): Wed 2020-06-17 14:08:41 UTC
UNIX seconds: @1592402921
From now: 11min ago
为什么从日志复制的数据不被认为是有效的时间戳?我不知道。将日志的时间戳以工具设计用于分析时间戳的格式打印出来似乎很愚蠢。但请看指定为日志开始的时间
[student@testvm1 ~]$ systemd-analyze timestamp "Wed 2020-06-17 10:08:41"
Original form: Wed 2020-06-17 10:08:41
Normalized form: Wed 2020-06-17 10:08:41 EDT
(in UTC): Wed 2020-06-17 14:08:41 UTC
UNIX seconds: @1592402921
From now: 15min ago
[student@testvm1 ~]$
好的,所以那个时间是有效的时间戳。关键是 systemd-analyze
工具仅识别 DOW YYYY-MM-DD HH:MM:SS
格式。正如错误消息所述,它不识别月份名称或独立的月份日期,例如 17。它非常精确,因为确保计时器在期望的时间点或间隔触发事件的唯一方法是完全准确地指定它们的方式。
任何明确表达的时间,例如 2020-06-17 10:08:41
,都是时间戳,因为它只能发生一次。将来发生的时间戳也可以在 systemd 计时器中使用,并且该计时器只会触发一次其定义的动作。
以某种更模糊的方式表达的时间,例如 2025-*-* 22:15:00
,只能是计时器单元文件中 OnCalendar
语句中使用的日历时间。此表达式将在 2025 年的每一天 22:15:00(晚上 10:15:00)触发一个事件。
但是,为什么不在日志输出中使用有效的时间戳呢?我仍然不知道,但是通过一些命令行操作,您可以将默认时间显示转换为有效的时间戳。journalctl
命令工具有一些选项,可以以您可以轻松地与 systemd-analyze
工具一起使用的格式显示时间戳
[root@testvm1 ~]# journalctl -o short-full
<SNIP>
Fri 2020-06-26 12:51:36 EDT testvm1.both.org systemd[1]: Finished Update UTMP about System Runlevel Changes.
Fri 2020-06-26 12:51:36 EDT testvm1.both.org systemd[1]: Startup finished in 2.265s (kernel) + 4.883s (initrd) + 22.645s (userspace) = 29.793s.
Fri 2020-06-26 12:51:36 EDT testvm1.both.org audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=systemd-update-utmp-runlevel>
Fri 2020-06-26 12:51:36 EDT testvm1.both.org audit[1]: SERVICE_STOP pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=systemd-update-utmp-runlevel >
Fri 2020-06-26 12:51:36 EDT testvm1.both.org crond[959]: (CRON) INFO (running with inotify support)
Fri 2020-06-26 12:51:36 EDT testvm1.both.org ModemManager[759]: <info> Couldn't check support for device '/sys/devices/pci0000:00/0000:00:03.0': not >
Fri 2020-06-26 12:51:37 EDT testvm1.both.org VBoxService[804]: 16:51:37.196960 timesync vgsvcTimeSyncWorker: Radical guest time change: -14 388 436 32>
Fri 2020-06-26 12:51:39 EDT testvm1.both.org chronyd[827]: Selected source 192.168.0.52
Fri 2020-06-26 12:51:39 EDT testvm1.both.org chronyd[827]: System clock TAI offset set to 37 seconds
<SNIP>
您现在可以像这样使用时间戳
[root@testvm1 ~]# systemd-analyze timestamp "2020-06-26 12:51:36"
Original form: 2020-06-26 12:51:36
Normalized form: Fri 2020-06-26 12:51:36 EDT
(in UTC): Fri 2020-06-26 16:51:36 UTC
UNIX seconds: @1593190296
From now: 2h 37min ago
[root@testvm1 ~]#
您还可以以单调格式显示日志时间戳,该格式显示自启动以来的秒数
[root@testvm1 ~]# journalctl -o short-monotonic
[ 0.000000] testvm1.both.org kernel: Linux version 5.6.6-300.fc32.x86_64 (mockbuild@bkernel03.phx2.fedoraproject.org) (gcc version 10.0.1 20200328 >
[ 0.000000] testvm1.both.org kernel: Command line: BOOT_IMAGE=(hd0,msdos1)/vmlinuz-5.6.6-300.fc32.x86_64 root=/dev/mapper/VG01-root ro resume=/dev/>
[ 0.000000] testvm1.both.org kernel: x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'
[ 0.000000] testvm1.both.org kernel: x86/fpu: Supporting XSAVE feature 0x002: 'SSE registers'
[ 0.000000] testvm1.both.org kernel: x86/fpu: Supporting XSAVE feature 0x004: 'AVX registers'
[ 0.000000] testvm1.both.org kernel: x86/fpu: xstate_offset[2]: 576, xstate_sizes[2]: 256
[ 0.000000] testvm1.both.org kernel: x86/fpu: Enabled xstate features 0x7, context size is 832 bytes, using 'standard' format.
[ 0.000000] testvm1.both.org kernel: BIOS-provided physical RAM map:
[ 0.000000] testvm1.both.org kernel: BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
[ 0.000000] testvm1.both.org kernel: BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved
[ 0.000000] testvm1.both.org kernel: BIOS-e820: [mem 0x00000000000f0000-0x00000000000fffff] reserved
[ 0.000000] testvm1.both.org kernel: BIOS-e820: [mem 0x0000000000100000-0x00000000dffeffff] usable
[ 0.000000] testvm1.both.org kernel: BIOS-e820: [mem 0x00000000dfff0000-0x00000000dfffffff] ACPI data
[ 0.000000] testvm1.both.org kernel: BIOS-e820: [mem 0x00000000fec00000-0x00000000fec00fff] reserved
[ 0.000000] testvm1.both.org kernel: BIOS-e820: [mem 0x00000000fee00000-0x00000000fee00fff] reserved
[ 0.000000] testvm1.both.org kernel: BIOS-e820: [mem 0x00000000fffc0000-0x00000000ffffffff] reserved
[ 0.000000] testvm1.both.org kernel: BIOS-e820: [mem 0x0000000100000000-0x00000003373fffff] usable
[ 0.000000] testvm1.both.org kernel: NX (Execute Disable) protection: active
[ 0.000000] testvm1.both.org kernel: SMBIOS 2.5 present.
[ 0.000000] testvm1.both.org kernel: DMI: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006
[ 0.000000] testvm1.both.org kernel: Hypervisor detected: KVM
<SNIP>
[28829.016018] testvm1.both.org NetworkManager[760]: <info> [1593219095.3936] dhcp4 (enp0s3): option requested_time_offset => '1'
[28829.016062] testvm1.both.org NetworkManager[760]: <info> [1593219095.3936] dhcp4 (enp0s3): option requested_wpad => '1'
[28829.016106] testvm1.both.org NetworkManager[760]: <info> [1593219095.3936] dhcp4 (enp0s3): option routers => '192.168.0.254'
[28829.016155] testvm1.both.org NetworkManager[760]: <info> [1593219095.3936] dhcp4 (enp0s3): option subnet_mask => '255.255.255.0'
[28829.016201] testvm1.both.org NetworkManager[760]: <info> [1593219095.3936] dhcp4 (enp0s3): state changed extended -> extended
[28829.019332] testvm1.both.org systemd[1]: Starting Network Manager Script Dispatcher Service...
[28829.028205] testvm1.both.org audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=NetworkManager-dispatcher comm="systemd" >
[28829.028406] testvm1.both.org systemd[1]: Started Network Manager Script Dispatcher Service.
[28839.014035] testvm1.both.org systemd[1]: NetworkManager-dispatcher.service: Succeeded.
[28839.014496] testvm1.both.org audit[1]: SERVICE_STOP pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=NetworkManager-dispatcher comm="systemd" e>
[29336.998580] testvm1.both.org systemd[1]: Starting system activity accounting tool...
[29337.443114] testvm1.both.org audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=sysstat-collect comm="systemd" exe="/usr/>
[29337.443347] testvm1.both.org audit[1]: SERVICE_STOP pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=sysstat-collect comm="systemd" exe="/usr/l>
[29337.443404] testvm1.both.org systemd[1]: sysstat-collect.service: Succeeded.
[29337.443504] testvm1.both.org systemd[1]: Finished system activity accounting tool.
[29394.784506] testvm1.both.org CROND[2253]: (root) CMD (run-parts /etc/cron.hourly)
[29394.792113] testvm1.both.org run-parts[2256]: (/etc/cron.hourly) starting 0anacron
[29394.799468] testvm1.both.org run-parts[2262]: (/etc/cron.hourly) finished 0anacron
阅读 journalctl
手册页,了解时间戳格式选项的完整列表(以及其他内容)。
时间跨度
时间跨度主要用于 systemd 计时器中,以定义事件之间的特定时间跨度。这可用于触发事件,以便它们在系统启动后或同一事件的先前实例之后经过指定的时间量后发生。例如,以下是计时器单元文件中的示例表达式,用于在系统启动后 32 分钟触发事件
OnStartupSec=32m
触发 systemd 计时器的默认精度是从指定时间开始并持续一分钟的时间窗口。您可以通过在计时器单元文件的 Timer 部分添加如下语句来将触发时间跨度精度提高到微秒级
AccuracySec=1us
systemd-analyze timespan
命令可以帮助确保您在单元文件中使用有效的时间跨度。以下示例将帮助您入门
[student@testvm1 ~]$ systemd-analyze timespan 15days
Original: 15days
μs: 1296000000000
Human: 2w 1d
[student@testvm1 ~]$ systemd-analyze timespan "15days 6h 32m"
Original: 15days 6h 32m
μs: 1319520000000
Human: 2w 1d 6h 32min
尝试这些以及您自己的示例
"255days 6h 31m"(255 天 6 小时 31 分钟)
"255days 6h 31m 24.568ms"(255 天 6 小时 31 分钟 24.568 毫秒)
最终想法
时间跨度用于在定义的事件(例如启动)之后指定的间隔调度计时器事件。日历时间戳可用于在特定的日历日期和时间调度计时器事件,可以是一次性的或重复的。时间戳也用于 systemd 日志条目,尽管不是以可以直接在 systemd-analyze
等工具中使用的默认格式。
当我开始使用 systemd 计时器并创建日历和时间戳表达式来触发事件时,所有这些对我来说都不仅仅是有点困惑。部分原因是用于指定时间戳和日历事件触发时间的格式相似但不完全相同。我希望这有助于为您澄清所有这些。
在我的下一篇文章中,我将更详细地探讨 systemd 日志记录和相关工具
资源
互联网上有大量关于 systemd 的信息,但很多信息都很简洁、晦涩甚至具有误导性。除了本文中提到的资源外,以下网页还提供了关于 systemd 启动的更详细和可靠的信息。
- Fedora 项目有一个很好的、实用的指南 了解和管理 systemd。它几乎包含了您需要了解的所有内容,以便配置、管理和维护使用 systemd 的 Fedora 计算机。
- Fedora 项目还有一个很好的速查表,该速查表将旧的 SystemV 命令与可比较的 systemd 命令进行交叉引用。
- 有关 systemd 的详细技术信息以及创建它的原因,请查看 Freedesktop.org 的 systemd 描述。
- Linux.com 的“更多 systemd 乐趣”提供了更高级的 systemd 信息和技巧。
Lennart Poettering 是 systemd 的设计者和主要开发者,他还为 Linux 系统管理员撰写了一系列深入的技术文章。这些文章写于 2010 年 4 月至 2011 年 9 月之间,但它们现在与当时一样具有现实意义。关于 systemd 及其生态系统的大部分其他优秀文章都基于这些论文。
评论已关闭。