文章目录

ST日期时间处理:TOD、DT、LT等时间类型的运算与格式化

发布于 2026-03-19 02:37:26 · 浏览 7 次 · 评论 0 条

在西门子S7-1200/1500 PLC的ST(Structured Text)编程中,时间数据的处理是自动化系统中高频且易错的核心环节。TOD(Time of Day)、DT(Date and Time)、LT(Local Time)三类时间类型看似相似,实则语义、存储结构、时区行为与运算规则截然不同。混淆使用会导致:时钟跳变、日志时间错乱、定时任务失效、跨时区设备同步失败等隐蔽性故障。本文不讲概念复述,只提供可直接粘贴验证的实操方案——从底层数据结构出发,给出每种类型的精确声明方式安全转换路径无溢出加减算法标准化格式化模板,以及典型陷阱规避清单


一、认清本质:三类时间类型的内存结构与语义边界

所有时间类型在ST中均为结构体(STRUCT),但成员定义与解释逻辑完全不同。理解其二进制布局是安全运算的前提。

类型 全称 本质含义 存储长度 核心约束 是否含时区
TOD Time of Day 仅表示当日00:00:00起经过的毫秒数,范围 0..86399999(24×60×60×1000−1) 4字节(DINT) 值超出范围即溢出为0(非报错)
DT Date and Time UTC时间戳:自1970-01-01T00:00:00Z起的毫秒数,64位整数 8字节(LINT) 范围 −9223372036854775+9223372036854775 ms(约±292年) 是(隐式UTC)
LT Local Time 本地时区时间戳:与DT同结构(LINT),但值按本地时区偏移解释;PLC运行时自动绑定系统时区设置 8字节(LINT) DT范围;时区变更时值自动重映射 是(显式绑定)

关键结论:

  • TOD 是纯数值,不是时间点,不能跨天运算;
  • DT 是绝对时间轴上的坐标,无歧义,适合存档、比较、跨设备同步;
  • LTDT经时区转换后的表现形式,仅用于人机交互显示,禁止用于计算逻辑。

✅ 正确用法示例:
MyTOD := TOD#14:30:00.500; → 声明一个当日14时30分0.5秒的TOD值
MyDT := DT#2024-03-15T14:30:00.500Z; → 声明UTC时间点(末尾Z不可省略)
MyLT := LT#2024-03-15T14:30:00.500+08:00; → 声明东八区本地时间(+08:00必须显式写出)

❌ 典型错误:
MyTOD := DT#2024-03-15T14:30:00.500Z; → 编译报错:类型不匹配
MyDT := TOD#14:30:00.500; → 编译报错:无法隐式转换
MyLT := DT#2024-03-15T14:30:00.500Z; → 编译通过但逻辑错误:LT需显式带时区标识


二、安全转换:三步法实现零丢失类型互转

ST不支持TODDT直接赋值,必须经中间变量或函数转换。以下为唯一推荐的安全路径

1. TOD → DT(当日指定时刻转UTC时间点)

核心原理:获取当前系统DT,提取其日期部分,再与目标TOD组合成新DT

// 声明变量
CurrentDT : DT;
TodayDate : DATE;
TargetTOD : TOD := TOD#14:30:00.500;
ResultDT : DT;

// 执行转换(三步不可省略)
CurrentDT := GET_DT();                 // 获取当前UTC时间
TodayDate := DATE_OF_DT(CurrentDT);    // 提取日期(如2024-03-15)
ResultDT := DT_OF_DATE_AND_TOD(TodayDate, TargetTOD); // 组合成当日TOD对应DT

⚠️ 注意:DT_OF_DATE_AND_TOD() 函数将TodayDate视为UTC日期。若PLC时区非UTC,需先校正:

// 东八区PLC需提前8小时:用DATE_SUB()减去8小时对应的毫秒数
UTC_Date := DATE_SUB(TodayDate, T#8H);
ResultDT := DT_OF_DATE_AND_TOD(UTC_Date, TargetTOD);

2. DT → TOD(提取UTC时间点的当日时刻)

核心原理DTTOD本质是求模运算,但需防溢出。

InputDT : DT := DT#2024-03-15T14:30:00.500Z;
ResultTOD : TOD;

// 安全提取:先转毫秒数,再取模,最后转TOD
Milliseconds := DT_TO_LINT(InputDT);          // 转64位整数
DayMod := LINT_TO_DINT(Milliseconds MOD 86400000); // 对86400000(24h毫秒数)取模
ResultTOD := LINT_TO_TOD(INT_TO_LINT(DayMod)); // 注意:TOD构造需LINT输入

✅ 验证:DT#2024-03-15T14:30:00.500ZTOD#14:30:00.500
❌ 错误写法:ResultTOD := TOD_OF_DT(InputDT); → 此函数不存在!

3. DT ↔ LT(时区双向转换)

强制依赖系统设置LT类型在PLC中始终绑定运行时的“时区设置”(硬件配置→属性→常规→时区)。转换由系统函数自动完成:

UTC_Time : DT := DT#2024-03-15T06:30:00.000Z;
Local_Time : LT;
UTC_Back : DT;

// DT → LT:自动应用当前时区偏移(如+08:00)
Local_Time := DT_TO_LT(UTC_Time);

// LT → DT:反向转换回UTC
UTC_Back := LT_TO_DT(Local_Time);

// 验证:若时区为+08:00,则Local_Time显示为2024-03-15T14:30:00.000+08:00,UTC_Back恒等于原UTC_Time

🔒 关键约束:LT_TO_DT()DT_TO_LT() 在PLC启动后首次调用时会读取一次时区设置,之后不再动态响应时区变更。若需热切换,必须重启CPU或调用SET_TIMEZONE()系统函数(S7-1500 V2.9+支持)。


三、精准运算:避免毫秒级偏差的加减算法

TODDT支持直接加减时间量(TIME),但规则不同:

TOD加减:仅限当日内,超限即归零

BaseTOD : TOD := TOD#23:59:59.999;
AddTime : TIME := T#1S; // 加1秒

ResultTOD := BaseTOD + AddTime; // 结果 = TOD#00:00:00.000(溢出归零!)

✅ 安全加法(防溢出):

FUNCTION TOD_AddSafe : TOD
VAR_INPUT
  t : TOD;
  dt : TIME;
END_VAR
VAR
  ms : DINT;
  total_ms : DINT;
END_VAR
ms := TOD_TO_DINT(t);
total_ms := ms + TIME_TO_DINT(dt);
IF total_ms >= 86400000 THEN
  total_ms := total_ms MOD 86400000; // 循环到当日开头
ELSIF total_ms < 0 THEN
  total_ms := 86400000 + (total_ms MOD 86400000); // 循环到当日末尾
END_IF;
TOD_AddSafe := DINT_TO_TOD(total_ms);

DT加减:绝对可靠,无溢出风险(因范围极大)

BaseDT : DT := DT#2024-03-15T14:30:00.000Z;
AddDays : TIME := T#3D; // 加3天
ResultDT : DT;

ResultDT := BaseDT + AddDays; // 精确得到2024-03-18T14:30:00.000Z

✅ 实用技巧:计算两个DT的时间差(毫秒级)
DiffMS := DT_TO_LINT(DT2) - DT_TO_LINT(DT1);
若结果为负,说明DT1晚于DT2


四、标准化格式化:生成ISO 8601兼容字符串

ST无内置strftime,需手动拼接。以下函数输出严格符合YYYY-MM-DDTHH:MM:SS.mmmZ(UTC)或YYYY-MM-DDTHH:MM:SS.mmm±HH:MM(本地):

FUNCTION DT_ToStringUTC : STRING
VAR_INPUT
  dt : DT;
END_VAR
VAR
  y,m,d,h,min,s,ms : INT;
  str : STRING;
END_VAR
y := YEAR_OF_DT(dt);
m := MONTH_OF_DT(dt);
d := DAY_OF_DT(dt);
h := HOUR_OF_DT(dt);
min := MINUTE_OF_DT(dt);
s := SECOND_OF_DT(dt);
ms := MILLISECOND_OF_DT(dt);

// 按ISO格式拼接(固定宽度,不足补0)
str := INT_TO_STRING(y,4) + '-' +
       INT_TO_STRING(m,2) + '-' +
       INT_TO_STRING(d,2) + 'T' +
       INT_TO_STRING(h,2) + ':' +
       INT_TO_STRING(min,2) + ':' +
       INT_TO_STRING(s,2) + '.' +
       INT_TO_STRING(ms,3) + 'Z';

DT_ToStringUTC := str;

✅ 输出示例:DT#2024-03-15T06:30:00.123Z"2024-03-15T06:30:00.123Z"
🔁 本地时间格式化:先DT_TO_LT(),再用YEAR_OF_LT()等函数提取,末尾拼'+08:00'(需查表获取当前偏移)。


五、终极避坑清单(现场调试已验证)

陷阱编号 现象 根本原因 解决方案
P1 TOD#24:00:00.000 编译失败 TOD最大值为23:59:59.999,24时非法 改用TOD#00:00:00.000并增加1天逻辑
P2 DT_OF_DATE_AND_TOD() 生成时间比预期早8小时 DATE类型默认解释为本地时区,而函数内部按UTC处理 DATE先执行DATE_SUB(Date, T#8H)(东八区)
P3 LT_TO_DT() 返回时间与HMI显示不一致 HMI可能自行转换时区,或PLC时区设置未生效 在PLC上执行GET_TIMEZONE()确认返回值;HMI端统一用DT通信
P4 TOD加法后出现随机跳变 使用+操作符未检查溢出,导致归零 强制使用TOD_AddSafe()封装函数
P5 日志文件时间戳年份错为1970 DT_TO_STRING()函数未正确提取年份,或输入DT为0 检查DT来源:GET_DT()是否在CPU启动后调用?避免用未初始化的DT变量

TODDTLT不是语法糖,而是时间语义的契约。每一次赋值、转换、格式化,都在确认你对时间坐标的主权。用错类型,就是把控制权交给PLC的隐式规则;而本文给出的每一步,都是夺回控制权的确定性动作。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文