📹 第七章 ONVIF协议原理解析
📄 一句话概括

GetSystemDateAndTime 返回三个时间:UTC、本地、夏令时标志,只有 UTC 是全球一致的。 「录像时间对不上」这个经典故障,根源几乎都是「一边存 UTC、一边显示本地」。

📝 本节和前面几节的关系

前五节讲的是「怎么找到设备、它是什么」,这一节进入「怎么管它」。 设备管理是 device 这组 service 的地盘, 也是接一台陌生设备时问得最多的一组(07.01.03 那张表里它占 5 个操作)。

一、三个时间,一个可靠

字段 本例的值 说明
DateTimeType Manual 手动设的;NTP 表示对过时
DaylightSavings false 夏令时是否生效
UTCDateTime 2026-10-24T09:47:26 UTC 时间,唯一可靠的

第三行是重点:UTCDateTime 是唯一「全球一致」的字段, 本地时间和夏令时都是「给人看的」。 所以代码里做时间比较,一律用这个字段,别碰本地时间。 第一行那个 DateTimeType 值得一看: Manual 说明这台设备的时间是人手动设的——手动设的时间会漂,一个月能差几分钟。 NTP 才是对过时的。 所以「录像时间戳不准」有时不是时区,是设备压根没对时, 这两个原因的排查方法完全不同:一个改配置,一个查 NTP 服务器。

二、实测:设备报 9:47,本机报 17:47

Demo 把设备报的 UTC 和本机本地时间摆在一起,差 8 小时:

来源 时刻 时区
设备 UTCDateTime 09:47:26 UTC
本机本地时间 17:47:26 UTC+8

这个差不是「设备时间错了」,是时区—— 设备报的是 UTC,本机报的是本地时间,两者本来就该差 8 小时。 「录像时间对不上」的故障就是这么来的: NVR 按 UTC 时间戳存,回放界面按本地时间显示,中间差了几个小时,找录像就找不到。 所以排查这类问题的第一件事,是把两边都换成 UTC 再比。 这个「先把口径统一,再比数值」的习惯,比任何工具都管用。 我在这门课里已经踩过好几次口径的坑(04 章的时间基、05 章的存储容量), 每次都是「数没错,口径错了」。

三、夏令时什么时候必须管

场景 只看 UTC 够不够 要不要结合夏令时
比较两个时间戳的先后 够 不用管
按「本地 8 点」触发录像 不够 要算时区偏移
给用户显示「录于几点」 不够 要按用户所在时区换算

第二行的坑在于:「本地 8 点」在夏令时切换那天可能不存在,也可能出现两次。 严格的做法是三条:存 UTC、显示时换算、触发用 UTC 绝对时刻。 一旦把「本地时间」当存储格式,夏令时就一定会咬人。 国内不用夏令时,所以很多人觉得这条无所谓——但设备是卖给全世界的, 固件里那句 DaylightSavings 就是为出口准备的。 写客户端时如果只考虑国内,到了有夏令时的地区就会出错,而这种错一年只出现两次,最难复现。

四、设备管理这一组还有什么

device 这组 service 的操作不止时间一个,常用的还有这些:

操作 干什么 什么时候用
GetDeviceInformation 厂商、型号、固件、序列号 接设备第一件事
GetSystemDateAndTime 三个时间 算摘要、对时
GetNetworkInterfaces 网口配置、IP、网关 查「为什么连不上」
SystemReboot 重启设备 远程维护的最后手段
GetHostname 设备主机名 多设备时区分

第一行那个信息在「报故障」时最有用:厂家第一句一定问「固件版本多少」, 没有它,后面所有沟通都要重来一遍。 所以接设备的第一步就该把它存下来,别等出问题再问。 第四行的 SystemReboot 要慎用:它没有「优雅关闭」的概念,重启期间录像会断。 工程上的做法是「先停录像任务、再重启、再恢复」,顺序错了就是一段丢失的录像。

五、动手 Demo:设备管理里的时间,为什么总是不对

Demo 问一次 GetSystemDateAndTime,把三个字段都打出来, 再和本机时间比一比,算出那个差。 最后把本机时间也换成 UTC,验证「换成同一口径后差为零」。

_a, b, _n = S.call(port, "device", "GetSystemDateAndTime",
                   "<tds:GetSystemDateAndTime/>")
r = ET.fromstring(b)
ty = r.find(".//{%s}DateTimeType" % S.NS_TT)
ds = r.find(".//{%s}DaylightSavings" % S.NS_TT)
ut = r.find(".//{%s}UTCDateTime" % S.NS_TT)
print(ty.text, ds.text)

# UTCDateTime 是复合结构,要拼出时分秒
h = ut.find("{%s}Hour" % S.NS_TT).text
mi = ut.find("{%s}Minute" % S.NS_TT).text
sc = ut.find("{%s}Second" % S.NS_TT).text
print("设备 UTC %s:%s:%s" % (h, mi, sc))

# 和本机比:先都换成 UTC
now_local = datetime.datetime.now()
now_utc = datetime.datetime.utcnow()
print("本机本地", now_local.strftime("%H:%M:%S"))
print("本机 UTC ", now_utc.strftime("%H:%M:%S"))
print("时区差 %.0f 小时" % ((now_local - now_utc).total_seconds() / 3600))

实跑输出:

--------------------------------------------------------------------
设备管理里的时间,为什么总是不对
--------------------------------------------------------------------
**`GetSystemDateAndTime` 是所有 ONVIF 操作里最容易踩时区坑的一个。**
  它返回三个时间:**UTC 时间、本地时间、夏令时标志**。

**响应里的字段:**

  字段                  值                        说明
  ----------------------------------------------------------------------------------------------
  `DateTimeType`        Manual                    **`Manual` 表示手动设的,`NTP` 表示对过时**
  `DaylightSavings`     false                     **夏令时是否生效**
  `UTCDateTime`         2026-10-24 9:47:26        **UTC 时间,这是唯一可靠的**

  **第三行是重点**:  **`UTCDateTime` 是唯一「全球一致」的字段**,
  **本地时间和夏令时都是「给人看的」。**
  **所以代码里做时间比较,一律用这个字段,别碰本地时间。**

**实测这台设备和本机的时间差:**

  **设备报的 UTC 时刻**:09:47:26
  **本机本地时刻**:17:47:26
  **两者差 +8 小时**(本机在 UTC+8)

  **这个差不是「设备时间错了」,是时区**——  **设备报的是 UTC,本机报的是本地时间。**
  **很多「录像时间对不上」的故障就是这么来的**:
  **NVR 按 UTC 时间戳存,回放界面按本地时间显示,  中间差了几个小时,找录像就找不到。**
  **所以排查这类问题时,第一件事是把两边都换成 UTC 再比。**

**夏令时那个字段为什么不能忽略:**

  场景                        只看 UTC              要结合夏令时
  --------------------------------------------------------------------------------
  比较两个时间戳的先后        **够**                不用管
  按「本地 8 点」触发录像     不够                  **要算时区偏移**
  给用户显示「录于几点」      不够                  **要按用户所在时区换算**

  **第二行的坑在于**:  **「本地 8 点」在夏令时切换那天可能不存在,也可能出现两次。**
  **严格的做法是:存 UTC,显示时换算,触发用 UTC 绝对时刻。**
  **一旦把「本地时间」当存储格式,夏令时就一定会咬人。**

  **ONVIF 里还有个细节**:  **`GetSystemDateAndTime` 允许匿名调用**(07.01.06 讲过),
  **所以「先问时间再鉴权」是可行的**——
  **这正是 WS-Security 摘要算法需要的**(它要算时间戳的摘要)。

UTCDateTime 不是一个字符串,是一个复合结构: 里面分 Hour/Minute/Second 三个子元素,所以要一个个取再拼。 这一点和很多协议不一样(它们直接给 ISO 8601 字符串)——ONVIF 这里用的是结构化的时间, 别想当然地把 .text 当整串用。 最后那个「时区差」是本机算的,不是设备给的: datetime.now() 和 utcnow() 相减就是本机的时区偏移。 本例是 +8 小时,所以设备报的 09:47 和本机的 17:47 是「同一个时刻的两种写法」。 这个 Demo 里我一次都没碰「本地时间」这个字段, 只用了 UTC 和本机自己的时区—— 这是刻意的:设备给的本地时间不可靠(取决于设备上设的时区),而 UTC 和本机时区都是确定的。

30 秒实验

  1. 把 utcnow() 换成 now(timezone.utc),看打印出来的时刻变不变。
  2. 算一下「设备时间和本机时间差几秒」——本例应该只差秒级,如果差很多,说明设备没对时。
  3. 把三个时间字段都打印出来(含 LocalDateTime),看它和 UTC 差多少。

常见误区

  1. 以为设备时间错了。 多半是时区:设备报 UTC,本机显示本地时间,差 8 小时是正常的。
  2. 以为 UTCDateTime 是个字符串。 它是复合结构,要分别取 Hour/Minute/Second。
  3. 以为国内不用夏令时就不用管它。 设备是出口的, DaylightSavings 那一行在别的地区会咬人,一年只错两次最难查。
自测三问(点开对答案) 1. `GetSystemDateAndTime` 返回哪三个时间相关的字段?哪个可靠? —— **`UTCDateTime`、本地时间、`DaylightSavings`。** **只有 UTC 是全球一致的**,比较时间一律用它。 2. 「录像时间对不上」最常见的原因是什么? —— **一边存 UTC、一边显示本地时间。** **排查第一步是把两边都换成 UTC 再比。** 3. `Manual` 和 `NTP` 的区别有什么实际影响? —— **`Manual` 是人手动设的,会漂**;`NTP` 是对过时的。 **前者要查配置,后者要查 NTP 服务器**,排查方向完全不同。

下一节讲设备的输入输出:视频以外还有继电器和音频,而继电器在亮化工程里能直接控灯。

相关笔记