菜单

DateUtil.h说明

下载

DateUtil.h 详细使用手册

本文档依据当前工程中的以下文件编写:

  • src/util/DateUtil.h

文档以 .cpp 的实际实现为准,覆盖 DateUtil 的全部公开接口、时间单位、时区语义、真机与模拟器差异、错误边界和调用示例。

1. 使用前准备

1.1 引入头文件

cpp 复制代码
#include "util/DateUtil.h"
#include "util/StringUtil.h" // 使用本文中的格式化和校验示例时需要
#include "util/FileUtil.h"   // 使用导出耗时示例时需要

#include <chrono>
#include <cstdint>
#include <iostream>
#include <string>

using stt::util::DateUtil;
using stt::util::FileUtil;
using stt::util::StringUtil;

DateUtil 的方法全部是静态方法,不需要创建对象:

cpp 复制代码
int today = DateUtil::today();
uint64_t nowMs = DateUtil::getMsTimeStamp2();

不使用 using 时可以写完整名称:

cpp 复制代码
std::string now = stt::util::DateUtil::getTimeString();

1.2 时间单位必须先确认

本类同时使用秒和毫秒,调用错误不会产生编译错误,但结果可能相差 1000 倍。

接口 单位 起点/含义
getMsTimeStamp() 毫秒 从本次开机开始的单调时间
getMsTimeStamp2() 毫秒 从 1970-01-01 00:00:00 UTC 开始
getMsTimeStamp2(...) 毫秒 指定本地日期时间对应的 Unix 时间戳
getTimeStamp() 秒 从 1970-01-01 00:00:00 UTC 开始
getUnixTime(...) 秒 把给定字段按 UTC 解释后的 Unix 时间戳
toString(ms, ...) 输入为毫秒 转为本地日期时间字符串
formatDate 不在本类中 StringUtil::formatDate 的输入是秒,不是毫秒

常见换算:

cpp 复制代码
uint64_t nowMs = DateUtil::getMsTimeStamp2();
uint64_t nowSec = nowMs / 1000;

// StringUtil::formatDate 接收秒
std::string text = StringUtil::formatDate(nowMs / 1000);

1.3 本地时间、UTC 和时区

  • Unix 时间戳表示 UTC 时间线上的一个时刻,本身不带时区。
  • getTimeString、today、getDate、parse、toString 使用本地时区。
  • getUnixTime 使用 UTC 规则,不使用本地时区。
  • 指定字段版本的 getMsTimeStamp2 使用 mktime,把字段解释为本地时间。
  • 修改时区会改变同一时间戳格式化后的日期和小时,但不会改变时间戳本身。

1.4 真机与 Windows 模拟器差异

接口 ARM 真机 Windows 模拟器
setDateTime 尝试写 RTC 和系统时间 不执行设置,仍直接返回 true
setTimezone 写配置并修改当前进程的 TZ 空操作
getTimezone 读取当前本地时间偏移 读取 Windows Bias
getUnixTime 使用 timegm 使用 _mkgmtime

不能仅根据模拟器上的 true 返回值判断系统时钟已经设置成功。

1.5 当前实现中的重要风险

接口 当前实现行为 调用建议
getTimezone UTC+8 返回 -8,和头文件“东区为正”的说明相反 显示常规时区时取负值
getMonthMaxDay 非法月份也返回 30 调用前限制月份为 1..12
getMsTimeStamp2(string) 只检查长度为 19,不检查分隔符和日期是否合法 先严格校验格式与范围
parse 不检查 std::get_time 是否解析失败 不要用它验证外部输入
setDateTime ARM 的 tm 没有整体清零,且不检查 settimeofday 结果 设置后必须重新读取并核对时间
setDateTime/setTimezone Windows 模拟器不执行实际设置 系统设置功能必须上真机测试
convert2HMS 负秒数会产生异常文本 只传非负数
toString 依赖全局数据库,格式串直接拼入 SQL Runtime/DB 必须已初始化,格式不可来自不可信输入

2. 接口速查

分类 方法 主要用途
当前日期时间 getDate、today、getTimeString 读取本地日期时间
时间戳 getMsTimeStamp、getMsTimeStamp2、getTimeStamp 计时或记录绝对时刻
日期构造与运算 getUnixTime、addDays、prevDay、nextDay 构造 UTC 时间或按日运算
日历信息 getWeekDay、getMonthMaxDay 星期和月天数
系统设置 setDateTime、getTimezone、setTimezone 真机时间与时区设置
时长显示 convert2HMS 秒数转时分秒
解析与差值 parse、diffInSeconds 本地时间字符串解析和秒差
时间戳格式化 toString 毫秒时间戳转本地字符串

3. 获取当前日期字段

3.1 getDate

cpp 复制代码
static int getDate(string f);

读取系统当前本地时间的一个字段。

有效参数:

f 返回内容 典型范围
"Y" 年 例如 2026
"M" 月 1..12
"D" 日 1..31
"hh" 小时 0..23
"mm" 分钟 0..59
"ss" 秒 0..60,极少数平台可能出现闰秒

示例:

cpp 复制代码
int year = DateUtil::getDate("Y");
int month = DateUtil::getDate("M");
int day = DateUtil::getDate("D");
int hour = DateUtil::getDate("hh");
int minute = DateUtil::getDate("mm");
int second = DateUtil::getDate("ss");

构造 yyyyMMdd 文本:

cpp 复制代码
std::string ymd = StringUtil::snprintf(
    "%04d%02d%02d",
    DateUtil::getDate("Y"),
    DateUtil::getDate("M"),
    DateUtil::getDate("D"));

注意事项:

  • 参数区分大小写,"y"、"HH"、"month" 都是无效值。
  • 无效参数返回 0,接口没有错误状态。
  • 连续调用多次时系统时间可能刚好跨秒或跨日,字段可能不属于同一时刻。需要完整时间时优先调用一次 getTimeString,需要日期整数时使用 today()。
  • ARM 实现使用 localtime 的静态缓冲区,不适合多个线程并发调用。

3.2 today

cpp 复制代码
static int today();

返回当前本地日期的 yyyyMMdd 整数:

cpp 复制代码
int date = DateUtil::today();
// 例如 20261020

int year = date / 10000;
int month = date / 100 % 100;
int day = date % 100;

适合数据库分表、按日查询和文件名:

cpp 复制代码
std::string fileName = std::to_string(DateUtil::today()) + ".csv";

注意事项:

  • 返回的是日期编码,不是 Unix 时间戳,不能直接参与秒或毫秒计算。
  • 返回值使用设备当前本地时区。设备时区不正确时,日期也会不正确。

4. 获取当前时间字符串

4.1 getTimeString

cpp 复制代码
static string getTimeString(
    const std::string& format = "%Y-%m-%d %H:%M:%S");

使用本地时间和 strftime 格式生成字符串。

常用示例:

cpp 复制代码
std::string full = DateUtil::getTimeString();
// 例如 "2026-10-20 14:30:05"

std::string date = DateUtil::getTimeString("%Y-%m-%d");
// "2026-10-20"

std::string compactDate = DateUtil::getTimeString("%Y%m%d");
// "20261020"

std::string compactTime = DateUtil::getTimeString("%H%M%S");
// "143005"

std::string fileName =
    DateUtil::getTimeString("export_%Y%m%d_%H%M%S.csv");

常用格式符:

格式符 含义 示例
%Y 四位年份 2026
%y 两位年份 26
%m 两位月份 10
%d 两位日期 20
%H 24 小时制小时 14
%I 12 小时制小时 02
%M 分钟 30
%S 秒 05
%F 年-月-日 2026-10-20
%T 时:分:秒 14:30:05
%w 星期日为 0 的星期编号 0..6
%u 星期一为 1 的星期编号 1..7
%j 一年中的第几天 001..366
%z 数字时区偏移 例如 +0800
%Z 时区名称 取决于系统
%% 百分号 %

注意事项:

  • %M 是分钟,月份是小写 %m。
  • %H 是 24 小时制;头文件旧注释中的 %I 是 12 小时制。
  • %a、%A、%b、%B、%c 等结果受系统 Locale 影响,真机上不一定输出中文。
  • 内部缓冲区固定为 128 字节。格式化结果过长时 strftime 返回 0,本方法返回空字符串。
  • 空格式也会返回空字符串,无法仅凭空结果区分空格式和格式化失败。

客户按日导出示例:

cpp 复制代码
std::string todayStr = DateUtil::getTimeString("%Y%m%d");
std::string todaySql = DateUtil::getTimeString("%Y-%m-%d");

std::string csvPath = exportDir + "/" + todayStr + ".csv";
std::string sql =
    "SELECT * FROM SAMPLE_4_V WHERE STIME >= '" +
    todaySql +
    " 00:00:00'";

如果表中的 STIME 是毫秒整数,应改用本手册第 6 节的时间戳范围,不能拿格式化字符串直接比较。

5. 单调计时和当前时间戳

5.1 getMsTimeStamp

cpp 复制代码
static uint64_t getMsTimeStamp();

返回从设备本次开机开始累计的单调毫秒数,底层使用 CLOCK_MONOTONIC。

它适合计算耗时和超时:

cpp 复制代码
uint64_t started = DateUtil::getMsTimeStamp();

doWork();

uint64_t elapsed = DateUtil::getMsTimeStamp() - started;
std::cout << "耗时 " << elapsed << " ms" << std::endl;

轮询超时:

cpp 复制代码
uint64_t started = DateUtil::getMsTimeStamp();
const uint64_t timeoutMs = 2000;

while (!isReady()) {
    if (DateUtil::getMsTimeStamp() - started >= timeoutMs) {
        break;
    }
}

注意事项:

  • 设备重启后重新从较小值开始,不能保存到数据库作为绝对时间。
  • 修改系统时间或 NTP 校时不会使它跳变,因此比 getMsTimeStamp2 更适合超时判断。
  • 返回的是毫秒,但实际精度由操作系统时钟决定。
  • 不要把它传给 toString;它不是 1970 Epoch 时间戳。

5.2 getMsTimeStamp2()

cpp 复制代码
static uint64_t getMsTimeStamp2();

返回当前 Unix Epoch 毫秒时间戳,底层使用 CLOCK_REALTIME:

cpp 复制代码
uint64_t nowMs = DateUtil::getMsTimeStamp2();
// 例如 1792487405123

适合数据库记录、事件时间、采样时间和跨进程交换:

cpp 复制代码
uint64_t eventTime = DateUtil::getMsTimeStamp2();
table.setValue(row, "STIME", std::to_string(eventTime));

注意事项:

  • 系统时间被手工修改、RTC 校正或 NTP 同步时,该值可能向前或向后跳变。
  • 不适合测量超时;耗时测量使用 getMsTimeStamp()。
  • 时区变化不会改变 Unix 时间戳,但会改变它格式化后的本地时间。

5.3 getTimeStamp

cpp 复制代码
static uint64_t getTimeStamp();

返回当前 Unix Epoch 秒时间戳:

cpp 复制代码
uint64_t nowSec = DateUtil::getTimeStamp();
uint64_t nowMsApprox = nowSec * 1000;

它不包含当前秒内的毫秒部分。需要毫秒精度时直接使用 getMsTimeStamp2()。

5.4 三种当前时钟的选择

需求 推荐接口
测量函数执行耗时 getMsTimeStamp()
实现 2 秒超时 getMsTimeStamp()
保存采样记录时间 getMsTimeStamp2()
生成带秒的协议字段 getTimeStamp()
生成用户可读时间 getTimeString()

6. 构造指定日期的时间戳

6.1 getUnixTime

cpp 复制代码
static time_t getUnixTime(
    int year,
    int month,
    int day,
    int hour = 0,
    int minute = 0,
    int second = 0);

把传入字段按 UTC 时间解释,并返回 Unix 秒时间戳。ARM 使用 timegm,Windows 使用 _mkgmtime。

cpp 复制代码
time_t utcMidnight = DateUtil::getUnixTime(2026, 10, 20);
time_t utcTime = DateUtil::getUnixTime(2026, 10, 20, 12, 30, 0);

如果输入表示本地时间,不应直接使用这个接口。以 UTC+8 的本地时间 2026-10-20 08:00:00 为例:

cpp 复制代码
// 方法一:使用本地时间接口,推荐
uint64_t localMs = DateUtil::getMsTimeStamp2(
    20261020, 8, 0, 0, 0);

// 方法二:调用 getUnixTime 前自行换算为 UTC 00:00:00
time_t sameMoment = DateUtil::getUnixTime(
    2026, 10, 20, 0, 0, 0);

注意事项:

  • 输入范围没有验证。底层时间函数可能自动归一化,例如把 10 月 32 日转换到 11 月。
  • 返回 time_t,失败值可能是 -1,调用方需要自行判断。
  • 该接口适合 UTC 计算和 addDays 的内部日历运算,不是常规本地时间构造接口。

6.2 getMsTimeStamp2(ymd, hour, minute, second, ms)

cpp 复制代码
static uint64_t getMsTimeStamp2(
    int ymd,
    int hour,
    int minute,
    int second,
    int ms);

把给定字段解释为当前时区的本地时间,再转换为 Unix 毫秒时间戳。

cpp 复制代码
uint64_t start = DateUtil::getMsTimeStamp2(
    20261020, 0, 0, 0, 0);

uint64_t end = DateUtil::getMsTimeStamp2(
    20261020, 23, 59, 59, 999);

推荐使用“左闭右开”的整日范围,避免手工拼 23:59:59.999:

cpp 复制代码
int day = 20261020;
int next = DateUtil::nextDay(day);

uint64_t beginMs = DateUtil::getMsTimeStamp2(day, 0, 0, 0, 0);
uint64_t endMs = DateUtil::getMsTimeStamp2(next, 0, 0, 0, 0);

std::string sql =
    "SELECT * FROM SAMPLE_4_V WHERE STIME >= " +
    std::to_string(beginMs) +
    " AND STIME < " +
    std::to_string(endMs);

注意事项:

  • ymd 必须按 yyyyMMdd 传入,例如 20261020。
  • 方法使用 mktime,会根据当前本地时区转换,并设置 tm_isdst=-1 自动判断夏令时。
  • 年、月、日、时、分、秒和毫秒都没有范围检查,非法值可能被自动归一化。
  • ms 应限制为 0..999。传负数或大于 999 会跨秒或发生无符号计算异常。
  • mktime 失败返回 -1;当前实现把它转为 uint64_t,会得到非常大的值,接口本身不报告失败。
  • 1970 年以前的本地日期不适合通过这个无符号接口表示。

6.3 getMsTimeStamp2(string, isstart)

cpp 复制代码
static uint64_t getMsTimeStamp2(
    string strdate,
    bool isstart = true);

接收一个长度为 19 的日期时间字符串,并转换为本地 Unix 毫秒时间戳:

cpp 复制代码
uint64_t begin = DateUtil::getMsTimeStamp2(
    "2026-10-20 12:30:45",
    true);
// 毫秒部分为 000

uint64_t end = DateUtil::getMsTimeStamp2(
    "2026-10-20 12:30:45",
    false);
// 毫秒部分为 999

isstart 只控制同一秒内的毫秒部分:

  • true:追加 000 ms。
  • false:追加 999 ms。

它不会自动把时刻改为一天的开始或结束。

当前实现只检查 strdate.length() == 19,然后固定截取以下位置:

text 复制代码
yyyy-MM-dd HH:mm:ss
0123456789012345678

因此必须在调用前自行校验:

cpp 复制代码
const std::string text = "2026-10-20 12:30:45";
const std::string pattern =
    "^[0-9]{4}-[0-9]{2}-[0-9]{2} "
    "[0-9]{2}:[0-9]{2}:[0-9]{2}$";

if (!StringUtil::match(pattern, text)) {
    // 格式错误
    return;
}

uint64_t value = DateUtil::getMsTimeStamp2(text, true);

仅通过正则仍不能发现 2026-02-31 之类的非法日期;月份、天数和时间范围还应结合 getMonthMaxDay 进一步检查。

长度不等于 19 时直接返回 0。0 同时也是合法的 Unix Epoch 时刻,不能单凭返回值判断输入是否有效。

7. 日期加减

7.1 addDays

cpp 复制代码
static void addDays(
    int& year,
    int& month,
    int& day,
    int num);

在给定日历日期上增加或减少整天,并通过引用修改年月日。

cpp 复制代码
int year = 2026;
int month = 10;
int day = 20;

DateUtil::addDays(year, month, day, 5);
// 2026-10-25

DateUtil::addDays(year, month, day, -10);
// 2026-10-15

跨月和闰年:

cpp 复制代码
int y = 2024;
int m = 2;
int d = 28;

DateUtil::addDays(y, m, d, 1);
// 2024-02-29

实现按 UTC 日历执行 num * 24 * 60 * 60 秒运算,因此不受本地夏令时一天为 23 或 25 小时的影响,适合“日期加一天”。

注意事项:

  • 三个日期参数都会被修改。
  • num == 0 时立即返回。
  • 输入日期不校验,底层可能自动归一化非法日期。
  • 极大的 num 会在 int 秒数乘法阶段溢出,不适合一次增加数万天。
  • 内部 gmtime 的返回值未检查,超出平台时间范围时有风险。

7.2 prevDay

cpp 复制代码
static int prevDay(int yyyymmdd);

返回前一天的 yyyyMMdd 整数:

cpp 复制代码
int previous = DateUtil::prevDay(20260301); // 20260228
int leapPrevious = DateUtil::prevDay(20240301); // 20240229

7.3 nextDay

cpp 复制代码
static int nextDay(int yyyymmdd);

返回后一天的 yyyyMMdd 整数:

cpp 复制代码
int next = DateUtil::nextDay(20261231); // 20270101

prevDay 和 nextDay 都不验证输入是否是合法 yyyyMMdd。外部输入应先拆分年月日并校验。

8. 星期和月份天数

8.1 getWeekDay

cpp 复制代码
static uint8_t getWeekDay(
    uint32_t y,
    uint8_t m,
    uint8_t d);

根据日期计算星期编号:

返回值 星期
0 星期日
1 星期一
2 星期二
3 星期三
4 星期四
5 星期五
6 星期六
cpp 复制代码
uint8_t week = DateUtil::getWeekDay(2026, 10, 20);
// 2,星期二

显示中文星期:

cpp 复制代码
const char* names[] = {
    "星期日", "星期一", "星期二", "星期三",
    "星期四", "星期五", "星期六"
};

std::string weekName = names[DateUtil::getWeekDay(2026, 10, 20)];

注意事项:

  • 接口不检查月份和日期范围,非法日期仍可能返回 0..6 中的某个值。
  • 年份为 0 且月份为 1 或 2 时会发生无符号减一,不应使用。
  • 星期编号和 strftime("%w") 一致,都是星期日为 0。

8.2 getMonthMaxDay

cpp 复制代码
static uint8_t getMonthMaxDay(
    uint16_t y,
    uint8_t m);

返回指定月份的天数,包含公历闰年规则:

cpp 复制代码
uint8_t a = DateUtil::getMonthMaxDay(2024, 2); // 29
uint8_t b = DateUtil::getMonthMaxDay(2026, 2); // 28
uint8_t c = DateUtil::getMonthMaxDay(2026, 10); // 31

日期合法性检查示例:

cpp 复制代码
bool isValidDate(int year, int month, int day) {
    if (year < 1970 || month < 1 || month > 12) {
        return false;
    }

    int maxDay = DateUtil::getMonthMaxDay(
        static_cast<uint16_t>(year),
        static_cast<uint8_t>(month));

    return day >= 1 && day <= maxDay;
}

注意事项:非法月份会进入 default 分支并返回 30,所以必须先检查 m 是否在 1..12。

9. 设置系统日期时间

9.1 setDateTime

cpp 复制代码
static bool setDateTime(
    int year,
    int month,
    int day,
    int hour,
    int min,
    int sec);

ARM 真机上尝试执行以下操作:

  1. 按当前本地时区通过 mktime 生成时间。
  2. 打开 /dev/rtc0;代码还包含 /dev/misc/rtc 的备用尝试。
  3. 通过 RTC_SET_TIME 写硬件 RTC。
  4. 通过 settimeofday 修改系统时间。
  5. 如果时间向后调整,调用 SttLooper::notifyAll() 唤醒相关等待。

基本示例:

cpp 复制代码
bool ok = DateUtil::setDateTime(
    2026, 10, 20,
    14, 30, 0);

if (!ok) {
    vm->setChar("提示", "系统时间设置失败");
    return;
}

// 必须重新读取确认,不能只信返回值
std::string actual = DateUtil::getTimeString();
vm->setChar("当前时间", actual);

注意事项:

  • Windows 模拟器不做任何设置,仍返回 true。
  • 真机进程需要访问 RTC 和设置系统时钟的权限。
  • 输入没有范围验证,mktime 可能自动归一化非法日期。
  • 当前 ARM 实现的 struct tm tptr 没有整体清零,tm_isdst 等字段值不确定,夏令时地区可能得到错误结果。
  • /dev/rtc0 打不开时,代码虽然尝试打开 /dev/misc/rtc,但由于当前控制流会在外层分支中无条件 return false,即使备用设备打开成功也不会继续设置,并可能遗留该文件描述符。
  • RTC 写入失败返回 false;但 settimeofday 的返回值没有检查,RTC 成功后即使系统时钟设置失败也可能返回 true。
  • 设置后应再次调用 getTimeString 或 getTimeStamp 核对实际结果。
  • 系统时间向后调整会使 getMsTimeStamp2 变小,但不会影响 getMsTimeStamp 的单调递增。

建议在调用前做完整范围校验:

cpp 复制代码
bool valid =
    year >= 1970 &&
    month >= 1 && month <= 12 &&
    day >= 1 && day <= DateUtil::getMonthMaxDay(year, month) &&
    hour >= 0 && hour <= 23 &&
    min >= 0 && min <= 59 &&
    sec >= 0 && sec <= 59;

if (valid) {
    DateUtil::setDateTime(year, month, day, hour, min, sec);
}

10. 获取和设置时区

10.1 getTimezone

cpp 复制代码
static int getTimezone();

当前实现返回的是“UTC 到本地时间所需的 Bias”符号,而不是头文件所述的常规东正西负符号:

实际本地时区 当前返回值
UTC+8(中国) -8
UTC+1 -1
UTC 0
UTC-5 +5

如果界面需要显示常见的 UTC+8 数值,应取负号:

cpp 复制代码
int displayTimezone = -DateUtil::getTimezone();
// 中国时区通常得到 8

注意事项:

  • ARM 使用当前时刻的 tm_gmtoff,可能包含夏令时影响。
  • Windows 返回 TIME_ZONE_INFORMATION::Bias / 60,没有组合标准/夏令时附加 Bias。
  • 实现虽然计算了分钟偏移,但最终只返回整小时。UTC+5:30 会被截断为 -5。
  • 头文件写的范围是 -12..+12,实现没有主动钳位。

10.2 setTimezone

cpp 复制代码
static void setTimezone(int tz);

此方法的输入采用常规符号:东区为正,西区为负。

cpp 复制代码
DateUtil::setTimezone(8);  // 设置为 UTC+8
DateUtil::setTimezone(-5); // 设置为 UTC-5
DateUtil::setTimezone(0);  // 设置为 UTC

ARM 上 setTimezone(8) 会写入 UTC-8。这是 POSIX TZ 字符串的反向符号规则,不是写错。

ARM 行为:

  • 通过 EnvVar::writeVar("timezone", value) 写配置。
  • 通过 setenv("TZ", value, 1) 修改当前进程环境。
  • 注释明确说明 Telnet 等已经启动的其他进程仍可能保持原时区环境。

注意事项:

  • Windows 模拟器为空操作,没有返回值提示是否生效。
  • 输入不校验;调用方应限制为产品支持的范围。
  • 只支持整小时,无法表达 UTC+5:30、UTC+5:45 等时区。
  • setTimezone(8) 后,getTimezone() 当前实现通常返回 -8,两个接口的符号语义不对称。
  • 修改后应调用 getTimeString("%Y-%m-%d %H:%M:%S %z") 检查当前进程显示。

11. 秒数转时分秒

11.1 convert2HMS

cpp 复制代码
static string convert2HMS(
    int seconds,
    bool pad0 = true,
    bool mod24 = false);

把非负秒数转换为时、分、秒文本。

示例:

cpp 复制代码
DateUtil::convert2HMS(5);          // "00:05"
DateUtil::convert2HMS(65);         // "01:05"
DateUtil::convert2HMS(3661);       // "1:01:01"
DateUtil::convert2HMS(3661, false); // "1:1:1"

pad0 只控制分钟和秒钟的两位补零,小时不会补零。

mod24=true 时小时对 24 取模:

cpp 复制代码
DateUtil::convert2HMS(25 * 3600, true, true);
// "1:00:00"

DateUtil::convert2HMS(24 * 3600, true, true);
// "00:00",小时变成 0 后不会显示小时字段

注意事项:

  • 小于 1 小时时结果只有 mm:ss,不是固定的 hh:mm:ss。
  • 负数没有专门处理,可能输出 00:0-1 等异常文本。
  • seconds 是 int,最大只能表达约 68 年;它适合界面时长,不适合超长周期。
  • 需要固定三段输出时,应自行使用格式化:
cpp 复制代码
int total = 65;
int h = total / 3600;
int m = total / 60 % 60;
int s = total % 60;

std::string fixed = StringUtil::snprintf("%02d:%02d:%02d", h, m, s);
// "00:01:05"

12. 解析日期时间

12.1 parse

cpp 复制代码
static system_clock::time_point parse(
    const string& timeStr,
    const string& format = "%Y-%m-%d %H:%M:%S");

通过 std::get_time 解析字段,再用 mktime 按本地时区生成 system_clock::time_point。

cpp 复制代码
std::chrono::system_clock::time_point point = DateUtil::parse(
    "2026-10-20 14:30:00");

time_t seconds = std::chrono::system_clock::to_time_t(point);

自定义格式:

cpp 复制代码
auto point = DateUtil::parse("20/10/2026", "%d/%m/%Y");

注意事项:

  • 输入按当前本地时区解释,不是 UTC。
  • 实现没有检查流的 fail() 状态,格式错误时仍会把零值或部分值交给 mktime,返回某个归一化时间点。
  • 因此无法通过返回值判断解析是否成功,不适合直接解析用户输入或协议数据。
  • tm 被清零后 tm_isdst 为 0,表示强制“非夏令时”,而不是自动判断。夏令时地区可能相差一小时。
  • mktime 还会归一化超范围日期,例如某些平台会把 2 月 31 日转换到 3 月。

外部输入应先检查固定格式、数值范围和月份天数,再调用 parse。

13. 日期时间差值

13.1 diffInSeconds

cpp 复制代码
static int diffInSeconds(
    const string& time1,
    const string& time2);

计算 time1 - time2,单位为秒。两个字符串都固定按 %Y-%m-%d %H:%M:%S 解析。

cpp 复制代码
int seconds = DateUtil::diffInSeconds(
    "2026-10-20 15:00:03",
    "2026-10-20 15:00:00");
// 3

int negative = DateUtil::diffInSeconds(
    "2026-10-20 14:59:00",
    "2026-10-20 15:00:00");
// -60

跨日示例:

cpp 复制代码
int seconds = DateUtil::diffInSeconds(
    "2026-10-21 00:00:00",
    "2026-10-20 00:00:00");
// 通常为 86400;夏令时切换地区可能不是 86400

注意事项:

  • 继承 parse 的全部限制:不报告解析失败、按本地时区处理、夏令时标志固定为非夏令时。
  • 返回类型是 int,约 68 年以上的差值可能溢出。
  • 只支持秒精度,不解析毫秒。
  • 比较数据库毫秒时间戳时,直接做 uint64_t/int64_t 差值更明确。

14. 毫秒时间戳转字符串

14.1 toString

cpp 复制代码
static string toString(
    uint64_t ms,
    const string& format = "");

通过工程默认 SQLite 数据库把 Unix 毫秒时间戳转换为本地时间文本。

默认格式:

cpp 复制代码
uint64_t nowMs = DateUtil::getMsTimeStamp2();
std::string text = DateUtil::toString(nowMs);
// 形如 "2026-10-20 14:30:05"

自定义 SQLite strftime 格式:

cpp 复制代码
std::string date = DateUtil::toString(nowMs, "%Y-%m-%d");
std::string compact = DateUtil::toString(nowMs, "%Y%m%d_%H%M%S");

内部执行的 SQL 逻辑等价于:

sql 复制代码
SELECT datetime(ms / 1000, 'unixepoch', 'localtime');

或者:

sql 复制代码
SELECT strftime(format, ms / 1000, 'unixepoch', 'localtime');

注意事项:

  • 该方法依赖 DBMgr::getdb() 和 TTable,默认数据库必须已经初始化;它不是纯字符串函数。
  • ms / 1000 会丢弃毫秒部分,结果只能显示到秒。
  • format 使用 SQLite strftime 规则,不完全等同于 C/C++ strftime,支持项取决于设备上的 SQLite 版本。
  • 格式字符串直接放进 SQL 单引号中,没有调用 sqlEsc。不要把用户输入或网络输入直接作为格式串。
  • SQLite 的 'localtime' 使用当前环境时区,改变 TZ 后结果会变化。
  • 无效格式、数据库查询失败或时间超出 SQLite 支持范围时可能返回空字符串,接口没有独立错误参数。

如果只需格式化当前时间,优先使用不依赖数据库的 getTimeString。如果需要格式化任意时间戳且允许使用 HMI Runtime,也可把毫秒除以 1000 后调用 StringUtil::formatDate。

15. 推荐的业务用法

15.1 生成当天 CSV 文件名

cpp 复制代码
std::string fileName =
    DateUtil::getTimeString("%Y%m%d") + ".csv";

std::string csvPath = exportDir + "/" + fileName;

15.2 按整数毫秒时间戳查询当天记录

cpp 复制代码
int day = DateUtil::today();
int tomorrow = DateUtil::nextDay(day);

uint64_t beginMs = DateUtil::getMsTimeStamp2(day, 0, 0, 0, 0);
uint64_t endMs = DateUtil::getMsTimeStamp2(tomorrow, 0, 0, 0, 0);

std::string sql =
    "SELECT * FROM SAMPLE_4_V "
    "WHERE STIME >= " + std::to_string(beginMs) +
    " AND STIME < " + std::to_string(endMs) +
    " ORDER BY STIME";

左闭右开范围能覆盖当天所有毫秒,同时避免下一天 00:00:00.000 被重复计入。

15.3 按文本日期字段查询当天记录

cpp 复制代码
std::string date = DateUtil::getTimeString("%Y-%m-%d");
std::string nextDate;

int tomorrow = DateUtil::nextDay(DateUtil::today());
nextDate = StringUtil::snprintf(
    "%04d-%02d-%02d",
    tomorrow / 10000,
    tomorrow / 100 % 100,
    tomorrow % 100);

std::string sql =
    "SELECT * FROM SAMPLE_4_V "
    "WHERE STIME >= '" + date + " 00:00:00' "
    "AND STIME < '" + nextDate + " 00:00:00' "
    "ORDER BY STIME";

这要求数据库中的文本日期固定为可按字典序排序的 yyyy-MM-dd HH:mm:ss。

15.4 可靠测量导出耗时

cpp 复制代码
uint64_t started = DateUtil::getMsTimeStamp();

std::string err;
bool ok = FileUtil::exportDB2CSV(
    dbPath,
    tableName,
    sql,
    csvPath,
    err);

uint64_t elapsed = DateUtil::getMsTimeStamp() - started;

std::string message = ok
    ? "导出完成,耗时 " + std::to_string(elapsed) + " ms"
    : "导出失败: " + err;

vm->setChar("导出提示", message);

不要用 getMsTimeStamp2() 测耗时,因为导出过程中如果发生校时,结果可能跳变。

15.5 设置时区和时间后核验

cpp 复制代码
DateUtil::setTimezone(8);

bool started = DateUtil::setDateTime(
    2026, 10, 20,
    14, 30, 0);

std::string actual = DateUtil::getTimeString(
    "%Y-%m-%d %H:%M:%S %z");

if (!started) {
    vm->setChar("提示", "RTC 设置失败");
} else {
    vm->setChar("提示", "当前系统时间: " + actual);
}

在 Windows 模拟器上,这个示例只能验证界面流程,不能验证系统设置是否生效。

16. 使用建议总结

  • 测量耗时和超时统一使用 getMsTimeStamp()。
  • 数据库存储绝对时间统一使用 getMsTimeStamp2(),并明确字段单位为毫秒。
  • 用户可读的当前时间使用 getTimeString()。
  • 需要本地指定时刻的时间戳时使用 getMsTimeStamp2(ymd, ...);需要 UTC 字段转换时才使用 getUnixTime()。
  • 按日查询优先使用 [当天 00:00, 下一天 00:00) 左闭右开范围。
  • 所有外部日期输入都应先校验格式、月份、月份最大天数和时分秒范围。
  • 不要通过 parse 或 getMsTimeStamp2(string) 的返回值判断输入是否合法。
  • getTimezone() 当前返回值与常见显示符号相反,中国时区通常返回 -8。
  • setDateTime 和 setTimezone 必须在 ARM 真机验证;模拟器不会真正设置。
  • 调用 setDateTime 后重新读取时间核对,不要仅依赖布尔返回值。
  • toString 依赖默认数据库,普通当前时间格式化优先使用 getTimeString。
上一个
StringUtil.h说明
下一个
DataSample.h 使用手册
最近修改: 2026-10-20Powered by