Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

1. 关键词

C++ 时间处理 从字符串中解析日期时间 跨平台

2. 问题

C++如何将字符串的日期时间解析成对应的时间戳?

3. 解决思路

  • 可以用正则表达式将字符串解析成 struct tm 类型的对象。
  • mktime()函数可以将 struct tm 类型的时间转换成时间戳。

4. 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157

#include <cstdint>
#include <string>
#include <vector>
#include <regex>
#include <time.h>

using time_regex_type = std::pair<std::string, std::regex>;
using time_regex_vec_type = std::vector<time_regex_type>;

std::string supported_time_formats(const time_regex_vec_type &fmtlist)
{
std::string time_fmts;
for (size_t i = 0; i < fmtlist.size(); i++)
{
time_fmts += fmtlist[i].first + "\n";
}
return time_fmts;
}

bool datetime::verify_time(const struct tm &time)
{
// 校验年
if (time.tm_year < 1900)
{
CUTL_ERROR("the year should be >= 1900");
return false;
}
// 校验月
if (time.tm_mon < 1 || time.tm_mon > 12)
{
CUTL_ERROR("the month should be between 1 and 12");
return false;
}
// 校验日
std::vector<int> large_month = {1, 3, 5, 7, 8, 10, 12};
if (std::find(large_month.begin(), large_month.end(), time.tm_mon) != large_month.end() && (time.tm_mday < 1 || time.tm_mday > 31))
{
CUTL_ERROR("the day should be between 1 and 31 for " + std::to_string(time.tm_mon) + " month");
return false;
}
std::vector<int> small_month = {4, 6, 9, 11};
if (std::find(small_month.begin(), small_month.end(), time.tm_mon) != small_month.end() && (time.tm_mday < 1 || time.tm_mday > 30))
{
CUTL_ERROR("the day should be between 1 and 30 for " + std::to_string(time.tm_mon) + " month");
return false;
}
if (time.tm_mon == 2)
{
auto is_leap_year = [](int year)
{ return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); };
if (is_leap_year(time.tm_year) && (time.tm_mday < 1 || time.tm_mday > 29))
{
CUTL_ERROR("the day should be between 1 and 29 for " + std::to_string(time.tm_year) + "-" + fmt_uint(time.tm_mon, 2));
return false;
}
if (!is_leap_year(time.tm_year) && (time.tm_mday < 1 || time.tm_mday > 28))
{
CUTL_ERROR("the day should be between 1 and 28 for " + std::to_string(time.tm_year) + "-" + fmt_uint(time.tm_mon, 2));
return false;
}
}

// 校验时分秒
if (time.tm_hour < 0 || time.tm_hour > 23)
{
CUTL_ERROR("the hour should be between 0 and 23");
return false;
}
if (time.tm_min < 0 || time.tm_min > 59)
{
CUTL_ERROR("the minute should be between 0 and 59");
return false;
}
if (time.tm_sec < 0 || time.tm_sec > 59)
{
CUTL_ERROR("the second should be between 0 and 59");
return false;
}

return true;
}


uint64_t parse_datetime(const std::string &time_text, int isdst)
{
std::smatch matchRes;
bool result = false;
static time_regex_vec_type fmt_list = {
// 0/1, 2/3, 4/5, 6/7的顺序不能反,因为不含毫秒数的时间会被优先匹配到
std::make_pair("YYYY-MM-DD HH:MM:SS.sss", std::regex(R"((\d{4})-(\d{2})-(\d{2})[ ](\d{2}):(\d{2}):(\d{2}).(\d{3}))")),
std::make_pair("YYYY-MM-DD HH:MM:SS", std::regex(R"((\d{4})-(\d{2})-(\d{2})[ ](\d{2}):(\d{2}):(\d{2}))")),
std::make_pair("YYYY.MM.DD HH:MM:SS.sss", std::regex(R"((\d{4}).(\d{2}).(\d{2})[ ](\d{2}):(\d{2}):(\d{2}).(\d{3}))")),
std::make_pair("YYYY.MM.DD HH:MM:SS", std::regex(R"((\d{4}).(\d{2}).(\d{2})[ ](\d{2}):(\d{2}):(\d{2}))")),
std::make_pair("YYYY/MM/DD HH:MM:SS.sss", std::regex(R"((\d{4})/(\d{2})/(\d{2})[ ](\d{2}):(\d{2}):(\d{2}).(\d{3}))")),
std::make_pair("YYYY/MM/DD HH:MM:SS", std::regex(R"((\d{4})/(\d{2})/(\d{2})[ ](\d{2}):(\d{2}):(\d{2}))")),
std::make_pair("YYYYMMDD HH:MM:SS.sss", std::regex(R"((\d{4})(\d{2})(\d{2})[ ](\d{2}):(\d{2}):(\d{2}).(\d{3}))")),
std::make_pair("YYYYMMDD HH:MM:SS", std::regex(R"((\d{4})(\d{2})(\d{2})[ ](\d{2}):(\d{2}):(\d{2}))")),
};
for (size_t i = 0; i < fmt_list.size(); i++)
{
auto &fmt_text = fmt_list[i].first;
auto &fmt_pattern = fmt_list[i].second;
result = std::regex_search(time_text, matchRes, fmt_pattern);
if (result)
{
CUTL_DEBUG("matched regex: " + fmt_text);
break;
}
}

if (!result || matchRes.size() < 7)
{
auto time_fmts = supported_time_formats(fmt_list);
CUTL_ERROR("Only the following time formats are supported:\n" + time_fmts);
return 0;
}

CUTL_DEBUG("matchRes size:" + std::to_string(matchRes.size()) + ", res:" + matchRes[0].str());
// 解析毫秒值
int ms = 0;
if (matchRes.size() == 8)
{
ms = std::stoi(matchRes[7].str());
}
// 解析tm结构的时间
struct tm time = {};
if (matchRes.size() >= 7)
{
for (size_t i = 1; i < 7; i++)
{
time.tm_year = std::stoi(matchRes[1]);
time.tm_mon = std::stoi(matchRes[2]);
time.tm_mday = std::stoi(matchRes[3]);
time.tm_hour = std::stoi(matchRes[4]);
time.tm_min = std::stoi(matchRes[5]);
time.tm_sec = std::stoi(matchRes[6]);
time.tm_isdst = isdst;
}
}
if (!verify_time(time))
{
return 0;
}

// 转换为时间戳
time.tm_year -= 1900;
time.tm_mon -= 1;
auto ret = mktime(&time);
if (ret == -1)
{
CUTL_ERROR("mktime() failed");
return 0;
}
auto s = static_cast<uint64_t>(ret);
return s;
}

5. 测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#pragma once

#include <iostream>
#include "timecount.h"
#include "common.hpp"

void TestDatetimeParseString()
{
// 字符串解析成时间
PrintSubTitle("TestDatetimeParseString");

auto dt0 = parse_datetime(" 2024-03-02 14:18:44 ");
std::cout << "dt0: " << dt0 << std::endl;
auto dt2 = parse_datetime(" 2024.03.12 14:18:44");
std::cout << "dt2: " << dt2 << std::endl;
}

6. 运行结果

1
2
3
--------------------------------------TestDatetimeParseString---------------------------------------
dt0: 2024-03-02 14:18:44.000
dt2: 2024-03-12 14:18:44.000

7. 源码地址

更多详细代码,请查看本人写的C++ 通用工具库: common_util, 本项目已开源,代码简洁,且有详细的文档和Demo。

推荐阅读
C++ 时间处理7-日期时间类 C++ 时间处理7-日期时间类 C++数据格式化4 - 格式化时间戳 C++数据格式化4 - 格式化时间戳 C++时间处理3-格式化时间戳 C++时间处理3-格式化时间戳

评论