CppCon 2015 学习:A C++14 Approach to Dates and Times
Big Picture — 日期库简介
- 扩展 标准库
这个库是对 C++ 标准库中<chrono>的自然延伸,专注于处理“日历”相关的功能(比如年月日、闰年、节假日等),而不仅仅是时间点和时长。 - 极简设计
它是**单头文件(header-only)**库,轻量且方便集成。 - 功能有限,但基础扎实
这个库不会帮你完成所有日期相关的复杂需求,但提供了高效且可靠的基础模块,你可以基于它自由构建自己的日期和日历功能。
换句话说,这个库不是“全功能的日期解决方案”,而是给程序员提供了“积木块”,用来灵活地实现各种自定义的日期处理。
这部分是描述日期时间库(如 date.h 和 tz.h)在整个时间处理系统中的位置。以下是详细理解:
图示结构(从底层到上层)
hardware↑
OS↑
<chrono> ← C++ 标准时间库(时长、时间点等)↑
"date.h" ← 扩展 <chrono> 的日期处理(如年/月/日)
"tz.h" ← 时区支持,使用 IANA tz 数据库↑
IANA tz database ← 提供全世界时区规则数据
NTP Server ← 用于同步当前精确时间
各部分说明
| 部件 | 作用 |
|---|---|
| hardware | 提供系统时钟的硬件层 |
| OS | 操作系统控制时间访问和管理 |
<chrono> | C++ 标准时间库,处理时间点/持续时间 |
date.h | 更高层的日期处理(年/月/日等),对 <chrono> 的扩展 |
tz.h | 基于 IANA 时区数据库的时区支持 |
| IANA tz database | 世界标准的时区定义数据库(例如夏令时规则) |
| NTP Server | 网络时间协议,用于获取当前标准时间 |
本讲内容集中在哪里?
- 重点在
date.h和tz.h:
如何在 C++ 中用现代、高效、跨平台的方式处理日期和时区问题
这为跨时区处理、日历计算、时间比较等功能提供了基础。
这一部分说明了这个日期库(如 date.h)在用户代码(client code)中的位置与使用方式。它表达的是这个库的架构理念和接口层次。理解如下:
分层结构
Client Code↑
Cute API ← 简洁易用的高级接口(例如:"2025-06-03")↑
Type-Safe Objects ← 严格类型封装的日期/时间对象↑
Date Algorithms ← 内部封装的日期计算逻辑
各层职责说明
| 层级 | 描述 |
|---|---|
| Client Code | 应用层开发者写的业务代码 |
| Cute API | 高层语法糖接口,简洁好读(如 "2025y/6/3") |
| Type-Safe Objects | 比如 year, month, day, year_month_day 等强类型对象,避免误用 |
| Date Algorithms | 底层封装的算法,比如判断闰年、月天数、日期加减等逻辑 |
核心理念
- 你可以选择用:
- 简洁、表达力强的 “Cute API” 来提高代码可读性
- 或直接操作 类型安全对象(type-safe objects) 获得精细控制
- 所有的算法实现都隐藏在类型安全对象里,比如
year_month_day内部处理所有的闰年/月份/天数验证等,不需要你操心
举例说明
#include "date/date.h"
using namespace date;
using namespace std::chrono;
year_month_day ymd = 2025_y/6/3; // Cute API
auto sys_days = sys_days(ymd); // 转为系统时间
auto ymd2 = year_month_day{sys_days}; // 类型安全地再转回来
你既可以用 "2025_y/6/3" 这样的简洁表达,也可以用 year, month, day 构造更底层对象,这两种方式之间是可互换的。
这段是对日期库中一个核心日期算法的解析:
constexpr int days_from_civil(int y, unsigned m, unsigned d) noexcept {y -= m <= 2;const Int era = (y >= 0 ? y : y - 399) / 400;const unsigned yoe = static_cast<unsigned>(y - era * 400);const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;return era * 146097 + static_cast<Int>(doe) - 719468;
}
将 {year, month, day} 转换成“距某一固定日期的天数”(序列化天数)。这个过程叫做**“civil date → days”** 转换。
理解核心函数:days_from_civil(...)
函数作用:
将公历日期 {年, 月, 日} 转换为一个整数(表示相对某个“起点”的总天数)。
这个“起点”通常是:1970-01-01(UNIX epoch) 或 0000-03-01 或其他定义。
参数说明:
constexpr int days_from_civil(int y, unsigned m, unsigned d)
y:年份m:月份(1-12)d:日(1-31)
主要计算过程(逐行解释):
y -= m <= 2;
- 如果是1月或2月,就把年份减1(因为算法以3月为起始月,有助于统一闰年处理)
const int era = (y >= 0 ? y : y - 399) / 400;
- 把年份分成以400年为周期的“纪元”,以处理格里历周期性(400年是格里历的一个周期)
const unsigned yoe = static_cast<unsigned>(y - era * 400);
yoe: year of era,即当前纪元内的年数(范围:0~399)
const unsigned doy = (153*(m + (m > 2 ? -3 : 9)) + 2)/5 + d - 1;
- 计算年内的天数 day-of-year (doy),基于3月为起点的公式
- 这样处理2月(闰年)的问题更简单、统一
const unsigned doe = yoe * 365 + yoe/4 - yoe/100 + doy;
doe: day-of-era,计算当前纪元开始以来的总天数- 包括平年天数
365 × yoe - 加上闰年天数
+ yoe/4 - yoe/100(每4年1闰,百年不闰)
- 包括平年天数
return era * 146097 + static_cast<Int>(doe) - 719468;
- 结合“纪元”和“纪元内天数”计算出总天数
146097是 400 年内总天数:365×400 + 97(闰年)719468是一个偏移量,用于将基准点转为 Unix epoch 或其他系统的起始点
应用场景
- 快速比较两个日期之间的天数差
- 实现
operator<或operator-之类的日期操作 - 是
std::chrono::sys_days等类型背后的基础
你现在看到的是上一函数的“反操作”版本,也就是将序列化天数(自某固定日期起算)转换为 {year, month, day} 三元组的算法。这个函数名为:
constexpr std::tuple<int, unsigned, unsigned> civil_from_days(int z) noexcept {z += 719468;const Int era = (z >= 0 ? z : z - 146096) / 146097;const unsigned doe = static_cast<unsigned>(z - era * 146097);const unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;const Int y = static_cast<Int>(yoe) + era * 400;const unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100);const unsigned mp = (5 * doy + 2) / 153;const unsigned d = doy - (153 * mp + 2) / 5 + 1;const unsigned m = mp + (mp < 10 ? 3 : -9);return std::tuple<Int, unsigned, unsigned>(y + (m <= 2), m, d);
}
civil_from_days(int z)
作用:
给定一个整数
z,表示自公历参考点(如 1970-01-01 或 0000-03-01)以来的天数,返回其对应的{年, 月, 日}。
这是 days_from_civil 的逆函数,两个函数可以互相还原。
参数:
z:序列化天数(整数)。例如,0可能表示1970-01-01(取决于基准)。
逐行解释:
z += 719468;
- 把天数偏移到公历系统的某个统一参考点(通常对应于
0000-03-01)
const Int era = (z >= 0 ? z : z - 146096) / 146097;
- 计算是哪一个 400 年纪元(每纪元是 146097 天 = 365×400 + 97 闰年天)
const unsigned doe = static_cast<unsigned>(z - era * 146097);
- 纪元内的天数
doe = day-of-era
const unsigned yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;
- 计算纪元内的年份
yoe = year-of-era(范围 0~399),包含闰年修正
这是“逆推”的关键计算:从天数恢复年份
const Int y = static_cast<Int>(yoe) + era * 400;
- 总年份
y是当前纪元的年份加上纪元前的年份
const unsigned doy = doe - (365*yoe + yoe/4 - yoe/100);
- 计算该年内的第几天(day-of-year)
const unsigned mp = (5*doy + 2)/153;
- 将 day-of-year 映射为月段索引
mp,这里仍以3月为起点(使2月变成年末)
const unsigned d = doy - (153*mp+2)/5 + 1;
- 从
mp反推具体的“日”
const unsigned m = mp + (mp < 10 ? 3 : -9);
- 从
mp反推具体的“月”(注意它是基于3月起算的)
return std::tuple<Int, unsigned, unsigned>(y + (m <= 2), m, d);
- 如果是1月或2月,年份要补回刚开始减掉的1年
- 返回
{year, month, day}
与 days_from_civil 配套使用:
int days = days_from_civil(2025, 6, 3);
auto [y, m, d] = civil_from_days(days); // 还原回 2025, 6, 3
应用场景:
- 构造日历
- 日期加减、间隔计算
std::chrono::sys_days → date显示格式转换
日期算法的鲁棒性和覆盖范围非常强:
- 所有提供的日期算法(如
days_from_civil()和civil_from_days()等)
都经过了严格的单元测试。 - 测试范围覆盖了从现在往前和往后各一百万年,即总共约 200 万年的日期。
为什么这很重要?
- 日历系统的规则(如闰年、纪元、历法转换)是复杂的;
- 即使在极端日期范围(比如公元前999,999年),算法也仍然工作正确;
- 表明这些算法非常可靠、健壮,甚至远超实际工程需求(实际我们只需几百年范围)。
实际意义:
- 你可以放心地使用这些算法,不必担心日期出错;
- 它们适用于历史研究、天文计算、未来预测等;
- 对于需要精度和长期一致性的系统(如日历服务、时间戳系统等),特别有价值。
这部分讲的是 “类型安全的日期对象(Type-Safe Objects)”,是现代 C++ 日期库(如 date.h)中提供的一种 安全、清晰、结构化表示日期的方式。
核心概念:类型安全的日期对象
这些类 不是简单的整数或字符串,而是具备语义的结构体,让日期处理更直观、安全、易于组合。
1. year_month_day
- 典型的日期结构,表示一个明确的年月日。
- 示例:
year_month_day ymd{2025y, June, 3d}; // 2025-06-03
2. year_month_day_last
- 表示某月的最后一天(无需手动判断是28/30/31日)。
- 示例:
year_month_day_last ymdl{2025y, month_day_last{June}};
3. year_month_weekday
- 表示类似“2025年6月的第1个星期一”。
- 示例:
year_month_weekday{2025y, June, weekday_indexed{Monday, 1}};
4. year_month_weekday_last
- 表示类似“2025年6月的最后一个星期五”。
- 示例:
year_month_weekday_last{2025y, June, weekday_last{Friday}};
5. weekday
- 表示一周中的哪一天(Monday、Tuesday 等)。
- 示例:
weekday wd{2025, 6, 3}; // 会计算出星期几
6. day_point(或 sys_days)
- 时间轴上的绝对日期点(通常是自1970年1月1日起的某天数)。
- 它是所有转换和运算的核心 —— 可以看作“中枢神经”。
- 示例:
sys_days dp = 2025y/6/3; // day_point,类似 time_point
总结:为什么这很重要?
- 比
int或string表示日期更安全(不易混淆年月日顺序) - 支持丰富的日期计算与转换
- 与
chrono完美集成,可参与时间运算 - 代码更易读、维护性强
选对数据结构,比写对算法更重要
引用 Stepanov(STL 设计者)的一句话:
“选择不合适的数据结构,几乎是导致性能问题的最常见原因。”
在这个日期库中的应用:
这个日期库提供了 类型安全的数据结构(Type-Safe Objects),用于更清晰、更高效地处理日期和时间。
year_month_day
- 表示一个具体的日历日期。
- 数据结构如下:
struct year_month_day {year y;month m;day d;
};
- 示例用法:
year_month_day ymd{2025y, June, 3d}; // 表示 2025-06-03
day_point(也叫 sys_days)
- 表示自某个固定日期(通常是1970-01-01)以来的天数。
- 是日期计算的“底层格式”,适合数学运算。
- 数据结构如下:
struct day_point {days count; // days 是 chrono 中的类型,表示“经过了多少天”
};
- 示例:
sys_days dp = 2025y/6/3; // 自动转换为 day_point(类似时间戳)
为什么要这样设计?
year_month_day:语义清晰,易于展示、读写day_point:高效、适合计算(如相减得出间隔天数)- 二者可以互相转换 → 灵活组合,性能优良
鼓励你自己构建数据结构
这个库提供了基础组件,比如 year、month、day,你也可以根据需要组合:
- 构建
academic_calendar_date - 支持农历、节假日标记等
- 构建用于金融系统的“交易日”结构
这部分讲的是 类型安全对象(Type-Safe Objects) 与日期算法(Date Algorithms)之间的关系,特别强调了:
类型转换 = 自动执行日期算法
内容总结:
“Type conversions execute date algorithms.”
—— 意思是说,在类型之间转换时,系统自动运行相应的日期算法,你不需要手动调用。
示例对象:year_month_day 和 day_point
year_month_day ymd{2025y, June, 3d}; // 日历格式
day_point dp = ymd; // 自动转换为 day_point(执行算法)
year_month_day back = dp; // 再转回来(再次执行算法)
背后发生了什么?
ymd -> day_point:调用days_from_civil(...)day_point -> ymd:调用civil_from_days(...)
所以关键点是:
- 这些转换 看似简单,但背后是精准的算法(已经过百万年范围的单元测试验证)
- C++ 的类型系统和重载机制 隐藏了算法的复杂性,给你一个“Cute API” —— 既好用又强大
Cute API 的好处
- 不需要用户理解底层算法
- 只需操作对象,就完成了日期推导与转换
- 代码表达力强,错误率低
小结
| 对象类型 | 说明 |
|---|---|
year_month_day | 人类友好格式,适合输入输出 |
day_point | 机器友好格式,适合运算存储 |
| 类型转换 | 自动执行算法,结果可靠精确 |
这部分内容是在介绍 “Cute API” 如何结合 类型安全对象(Type-Safe Objects) 与 重载运算符 实现简洁、直观的日期构造方式。
理解内容核心:
用
/运算符拼接字段(如 year、month、day)来构建完整日期对象。
这是一种 操作符重载(operator overloading) 技巧,让代码看起来更像人类写的日期,而不是传统结构体初始化。
具体结构
year / month / day --> year_month_day 类型
例如:
auto date = 2025y / June / 3d; // 创建一个 year_month_day 对象
2025y是year类型的对象June是枚举类型month3d是day类型的对象
上述表达式会被解释为:
year_month_day{year{2025}, month{6}, day{3}};
实现机制:
背后是对 / 运算符的重载,例如:
constexpr year_month operator/(const year& y, const month& m);
constexpr year_month_day operator/(const year_month& ym, const day& d);
所以 / 是组合构建器,逐步合并:
year / month→year_monthyear_month / day→year_month_day
为什么称它为 “Cute API”?
- 阅读性强:像写自然语言
- 函数式风格:无副作用,组合清晰
- 类型安全:编译器能检测非法组合(比如
year / day不合法)
示例完整代码:
#include <date/date.h>
#include <iostream>
using namespace date;
int main() {auto d = 2025y / June / 3d;std::cout << d << '\n'; // 输出: 2025-06-03
}
总结:
| 概念 | 说明 |
|---|---|
| Cute API | 用 / 运算符拼接字段创建日期对象 |
| 类型安全对象 | year, month, day, year_month_day 等 |
| 好处 | 清晰、直观、强类型、安全、易读 |
| 背后机制 | operator/ 重载实现类型组合和构建 |
这部分讲的是 Cute API 如何与 类型安全对象(Type-Safe Objects) 和 日期常量(如星期、月份、日) 一起工作,构建更丰富的日期表示,特别是像“某年3月的最后一个星期天”这样的复杂日期。
核心理解:
使用 运算符重载(
/) 和 字面量(如sun,last,mar,2015y),构造一个表示特殊日期结构的对象:
sun[last] / mar / 2015
→ year_month_weekday_last
关键概念解释:
1. 类型 year_month_weekday_last
用于表示 “某年某月的最后一个星期几”,比如:
- 2015年3月的最后一个星期天
对应结构:
year_month_weekday_last{year{2015},month{3},weekday_last{Sunday}
}
2. 表达式解释:
sun[last] / mar / 2015y
等价于:
year_month_weekday_last{year{2015},month{3},weekday_last{weekday{0}} // Sunday is typically 0
}
3. 运算符重载过程:
表达式由一连串 / 运算拼接组成:
| 步骤 | 表达式 | 结果类型 |
|---|---|---|
| 1 | sun[last] | weekday_last |
| 2 | weekday_last / mar | month_weekday_last |
| 3 | month_weekday_last / 2015y | year_month_weekday_last |
这些操作都依赖于重载的
/运算符,像链式拼接一样。
示例代码:
#include <date/date.h>
#include <iostream>
using namespace date;
int main() {auto special_date = sun[last] / March / 2015y;std::cout << special_date << '\n'; // 输出: 2015-Mar-last-Sun
}
应用场景:
- 日历系统中找“每月最后一个星期五”
- 排程与提醒(如 DST 调整日、支付日)
- 法律/金融文档中日期计算
总结:
| 项目 | 描述 |
|---|---|
| Cute API | 利用 / 运算符重载组合字段 |
| year_month_weekday_last 类型 | 表示“某年某月的最后一个星期几” |
字面量:sun, last, mar, 2015y | 用于构造类型安全、编译时常量的表达式 |
表达式:sun[last]/mar/2015y | 转换为 year_month_weekday_last |
这段内容对比了用 Cute API 和传统构造函数来创建日期对象的两种方式,重点说明了 Cute API 通过运算符重载使代码更简洁、易读,同时传统构造函数仍然可用。
核心理解:
1. Cute API(运算符重载组合字段)
auto d = sun[last] / mar / 2015;
- 通过重载的
/运算符,将各个部分(星期、月份、年份)组合成一个year_month_weekday_last类型。 - 看起来像自然语言,更直观。
- 这里的
sun[last]是“最后一个星期天”的表达。
2. 传统构造函数语法
year_month_weekday_last(year(2015),month(3),weekday_last(weekday(0)) // Sunday 是 weekday 0
);
- 直接用构造函数传入具体的类型和值。
- 更显式但写起来更啰嗦。
- 可用于不支持运算符重载的环境,或者需要更清晰的类型控制时。
3. 作用对比
| 方式 | 优点 | 备注 |
|---|---|---|
| Cute API | 代码简洁,语义清晰,易读 | 依赖运算符重载和字面量 |
| 传统构造函数 | 明确、标准,兼容性好 | 代码较繁琐 |
4. 总结
| 主要点 |
|---|
| Cute API 是对传统构造函数的“语法糖”,让代码更漂亮 |
两者最终生成的是相同的 year_month_weekday_last 类型实例 |
| 开发者可以根据偏好和场景选择使用哪种方式 |
这部分内容讲的是 year_month_day 这个类型,它是日期库里的一个字段类型(field type),用来表示具体的日期(年、月、日)。重点是展示了:
核心理解:
1. 构造方式(Cute API)
auto ymd = 2015_y / sep / 25;
- 利用运算符
/重载,把年(2015_y)、月(sep)、日(25)组合成一个year_month_day类型实例。 - 这种写法很自然,就像写日期一样。
2. 访问字段
assert(ymd.year() == 2015_y);
assert(ymd.month() == sep);
assert(ymd.day() == 25_d);
- 可以通过
year()、month()、day()方法分别访问对应的日期字段。 - 这里的
2015_y、sep、25_d是类型安全的字面量,保证类型和单位正确。
具体说明
| 元素 | 说明 |
|---|---|
year_month_day | 表示年-月-日的类型安全日期结构 |
2015_y | C++ 用户定义字面量,表示年份2015 |
sep | 表示九月(September),预定义的月份常量 |
25 | 整数日数 |
25_d | 25天,带有类型的“日”字面量 |
总结
year_month_day是日期的核心类型,代表具体的年月日。- Cute API 用
/运算符拼接字面量生成实例,代码直观易读。 - 可以通过
.year()、.month()、.day()方法访问各个部分。
这段话强调了 year_month_day 类型的几个重要特性:
理解要点
1. 构造
constexpr auto ymd = 2015_y / sep / 25;
- 使用了
constexpr,表示这个year_month_day对象在编译时就能被计算和确定。 - 用 Cute API 语法构造日期,语义清晰。
2. 访问字段(编译期检查)
static_assert(ymd.year() == 2015_y, "");
static_assert(ymd.month() == sep, "");
static_assert(ymd.day() == 25_d, "");
static_assert是编译时断言,保证ymd中的年、月、日字段值在编译时就正确。- 说明所有字段访问都支持
constexpr,即可以在编译期完成运算和验证。
3. 总结
year_month_day支持完全的编译期操作,提高效率且能在编译阶段捕获错误。- 这种设计对需要高性能和安全性的程序特别有用。
- 既能在编译期用作常量,也能在运行时使用,非常灵活。
这组内容详细介绍了 year_month_day 类型的用法和灵活性,重点讲了构造、算术运算、输入输出以及类型安全等。下面是详细的理解总结:
1. 构造 & 读写操作
constexpr auto ymd = 2015_y / sep / 25;
- 利用重载的
/运算符,可以轻松构造日期。 - 支持
constexpr,编译时就能确定日期值。
cout << ymd << '\n'; // 输出 2015-09-25
- 支持直接打印,格式化为常见的年月日形式。
2. 年月算术运算
auto next_month = ymd + months{1};
auto last_year = ymd - years{1};
- 支持用
months{}、years{}进行日期加减操作。 - 这种算术是基于年月日的“字段”加减。
- 如果要对天数进行操作,应该使用
day_point类型,专门处理天数的加减。
3. 字段的灵活输入输出(I/O)
- 每个字段(年、月、日)都能与整型互相转换,方便自定义输入输出。
- 例如:
int y, m, d;
cin >> y >> m >> d;
auto ymd = year(y) / month(m) / day(d);
- 只有第一个字段必须显式转换,后续字段可直接用整型值(比如
m、d):
auto ymd = year(y) / m / d;
4. 字段顺序灵活
- 可以用多种顺序构造日期:
auto ymd1 = day(d) / m / y;
auto ymd2 = month(m) / d / y;
- 只要第一个字段是明确的类型(
year、month或day),其余字段为整数,解析就无歧义。
5. 类型安全编译期检查
auto ymd = y / month(m) / d; // 编译错误!
- 不能混淆字段类型,比如整型
y不能直接与month类型做/运算。 - 这种错误会在编译阶段被捕获,保证类型安全。
总结
year_month_day提供了灵活、类型安全的日期表示和操作。- 方便用多种方式输入日期,同时编译器能帮你防止字段顺序或类型的错误。
- 支持方便的日期加减运算,支持打印输出。
- 设计上既保证了易用性,也保证了安全性和正确性。
这个部分讲的是日期加减时遇到“无效日期”的处理问题,以及这套库是如何设计的。总结如下:
1. 无效日期问题示例
- 比如计算:
auto ymd = 2015_y / jan / 31;
ymd += months{1}; // 2015-02-31 是无效日期
- 这是个无效日期,因为2015年2月没有31号。
- 有多种常见处理方案:
- 自动调整到有效日期(如2015-02-28)
- 抛出异常
- 返回下一个有效日期(如2015-03-03)
- 不同库、程序有不同的选择。
2. 本库的设计理念
- 库本身不做“自动修正”或“自动抛异常”,它允许你创建无效日期。
- 你必须自己选择何时检测和如何处理无效日期。
- 这带来更高性能,因为库不做额外检查,给你自由和灵活性。
- 你可以选择:
- 使用
assert()检测有效性。 - 使用
ok()函数检测日期是否有效。 - 自己抛异常或者做其他逻辑处理。
- 使用
3. 具体示例
断言检测:
ymd += months{1}; // ymd 变成了 2015-02-31(无效)
assert(ymd.ok()); // 如果无效,assert 会触发
抛异常示例:
auto result = ymd + months{1};
if (!result.ok()) {std::ostringstream os;os << ymd << " + " << months{1}.count() << " months results in " << result;throw std::domain_error(os.str());
}
自动调整到月底:
ymd += months{1};
if (!ymd.ok())ymd = ymd.year() / ymd.month() / last; // 修正到该月最后一天,例如 2015-02-28
转换为 day_point 处理(不自动修正):
ymd += months{1};
if (!ymd.ok())ymd = day_point{ymd}; // 转成 day_point(天数计数),保持原值
4. 总结
- 库只负责创建日期和提供
ok()判断函数,不自动纠错或抛异常。 - 这让你能写出更高效、可控的代码,决定在哪些地方要做有效性检测。
- 编译期会捕获一些无效字段顺序等错误,运行时的日期有效性由你自己检测和处理。
- 库本身不抛异常,但你的代码可以自由抛异常。
- 这种设计更适合需要底层高性能和灵活性的场景。
这部分讲的是日期类型中的特殊标记 last,总结理解如下:
1. 什么是 last?
last是一个特殊的“日期”标记,表示某个月或某年某月的最后一天。- 在任何可以写“日(day)”的地方,都可以写
last。
例如:
auto ymd1 = 2015_y / feb / last; // 2015年2月的最后一天,即2015-02-28
auto ymd2 = feb / last / 2015_y; // 顺序不同,但语义相同
auto ymd3 = last / feb / 2015_y; // 依然合法
2. 类型和API
- 表达式结果是
year_month_day_last类型,而不是普通的year_month_day。 year_month_day_last的API几乎与year_month_day相同。- 它也可以隐式转换成
year_month_day(即转成具体某一天的日期)。
3. 使用场景示例
当日期无效时,可以用 last 修正到当月最后一天:
ymd += months{1}; // 例如 ymd 是 2015-01-31,加一个月变成 2015-02-31(无效)
if (!ymd.ok()) {ymd = ymd.year() / ymd.month() / last; // 转成当月最后一天 2015-02-28
}
4. 总结
last是库提供的一个语法糖,用于方便表达“某月的最后一天”。- 它简化了对无效日期的处理(比如月份天数不同)。
- 让代码更直观,也方便避免无效日期错误。
这段内容介绍了“Indexed weekdays(带索引的星期几)”的用法,理解如下:
1. Indexed Weekdays 是什么?
- 在任何能写“日(day)”的地方,也可以写“带索引的星期几”(比如“第4个星期五”)。
- 语法形式是:
weekday[index],例如fri[4]表示“第4个星期五”。
2. 示例
auto ymwd1 = 2015_y / sep / fri[4]; // 2015年9月的第4个星期五
auto ymwd2 = sep / fri[4] / 2015_y; // 顺序不同,但含义相同
auto ymwd3 = fri[4] / sep / 2015_y; // 依然合法
- 这些表达式的类型是
year_month_weekday。
3. 转换为具体日期(year_month_day)
- 可以将
year_month_weekday转成普通日期year_month_day:
year_month_day ymd{ymwd1};
cout << ymwd1 << '\n'; // 输出类似:2015/Sep/Fri[4]
cout << ymd << '\n'; // 输出具体日期:2015-09-25
4. 带索引的“最后一个星期几”
- 也可以使用
last作为索引,表示“某月最后一个指定星期几”:
auto ymwd_last = 2015_y / sep / fri[last]; // 2015年9月最后一个星期五
- 类型为
year_month_weekday_last。 - 同样可以转换为普通日期:
year_month_day ymd{ymwd_last};
cout << ymwd_last << '\n'; // 输出 2015/Sep/Fri[last]
cout << ymd << '\n'; // 输出具体日期,比如 2015-09-25
5. 总结
- Indexed weekdays 让你能方便表达“某月第N个星期几”或“某月最后一个星期几”。
- 类型安全且语法灵活,支持多种字段顺序。
- 可以轻松转换成具体的日期。
day_point 概念
- day_point 是基于天的时间点(time_point),分辨率为“天”。
- 它是
std::chrono::time_point的一个类型别名,时间单位是天(days)。 - 具体定义是:
using day_point = std::chrono::time_point<std::chrono::system_clock, days>;
- 它表示自 Unix 纪元时间(1970-01-01)以来经过的天数。
示例说明
day_point dp = sep/25/2015; // 将日期转换成 day_point 类型
cout << dp.time_since_epoch().count() << '\n';
输出:
16703
- 表示自 1970-01-01 至 2015年9月25日,共经过了 16,703 天。
day_point 的优势
- 支持高效的“基于天”的日期算术:
dp += days{2}; // 加2天
cout << dp.time_since_epoch().count() << '\n'; // 变成 16705
- 这样操作比操作具体年月日字段更高效,也避免了日期边界问题。
总结
- day_point 是以天为单位的时间点类型,基于
<chrono>标准库。 - 它是这套日期库的核心,负责处理日期的底层计数和计算。
- 你可以用它做高效的日期加减等算术运算。
system_clock::time_point 转换为 day_point
day_point是一个时间点,时间单位是天,属于 coarse duration(粗粒度时间)。- 要将
system_clock::time_point转成day_point,可以用:
1. 使用 time_point_cast
day_point dp = std::chrono::time_point_cast<days>(std::chrono::system_clock::now());
- 缺点:
time_point_cast是向零舍入(truncate toward zero), - 这对 1970 年之前的日期(负时间点) 会产生意料之外的错误结果。
2. 使用 floor
day_point dp = floor<days>(std::chrono::system_clock::now());
floor类似time_point_cast,但是是向负无穷舍入(round down),- 对负时间点(1970 年之前的时间)表现更正确。
示例
day_point dp = floor<days>(std::chrono::system_clock::now());
std::cout << dp.time_since_epoch().count() << '\n';
输出:
16703
- 表示从 1970-01-01 到现在(示例日期)过去了 16703 天。
总结
- 转换时推荐用
floor<days>(),避免负时间点舍入错误。 - 这是标准库
<chrono>里的时间点转换好方法。
day_point 和时间点(time_point)结合的日期时间操作
day_point本质上是一个以天为单位的std::chrono::time_point。
示例说明
auto tp = day_point{jan/3/1970};
assert(tp.time_since_epoch() == days{2});
jan/3/1970转为day_point,是从1970-01-01起的第2天(因为1970-01-01是第0天)。
可以给 day_point 加上小时、分钟、秒
auto tp = day_point{jan/3/1970} + 7h;
assert(tp.time_since_epoch() == 55h);
auto tp2 = day_point{jan/3/1970} + 7h + 33min;
assert(tp2.time_since_epoch() == 3333min);
auto tp3 = day_point{jan/3/1970} + 7h + 33min + 20s;
assert(tp3.time_since_epoch() == 200000s);
- 这里时间单位变得更细(小时、分钟、秒),
time_since_epoch()会以相应单位表示。
从带有时分秒的 time_point 中恢复日期(day_point)
auto dp = floor<days>(tp3);
assert(dp.time_since_epoch() == days{2});
- 使用
floor<days>截断时分秒,得到对应的日期(只到天的精度)。
获取当天时间(时分秒)
auto s = tp3 - dp;
assert(s == 27200s); // 7h33m20s 转换成秒数
tp3减去日期部分dp,得到当天的时间段。
将秒数分解为时分秒字段
auto time = make_time(s);
assert(time.hours() == 7h);
assert(time.minutes() == 33min);
assert(time.seconds() == 20s);
- 使用
make_time将时间段转换成小时、分钟、秒。
将 day_point 转成年月日类型
auto ymd = year_month_day{dp};
assert(ymd.year() == 1970_y);
assert(ymd.month() == jan);
assert(ymd.day() == 3_d);
- 方便地得到具体日期信息。
总结
- 这个库完美衔接了 C++14
<chrono>的时间点和日历日期。 day_point既能做日期算术,也能做时间加减(时分秒),并且能方便地转换为年月日等类型。- 这让日期时间处理既高效又直观。
性能成本分析 — 这个库的开销到底有多大?
背景
- 作者没有跑性能测试,而是直接对比了用“date.h”中复杂类型生成代码的汇编,与简单的C结构体写法生成的汇编代码。
- 结果显示,这些高级封装并没有增加任何额外的运行时成本!
示例1:构造年月日结构体
date.h版本(year_month_day)
date::year_month_day make_year_month_day(int y, int m, int d) {using namespace date;return year{y}/m/d;
}
对应的汇编大致是位操作组合三个字段。
传统C-like版本
struct YMD_4 {std::int16_t year;std::uint8_t month;std::uint8_t day;
};
YMD_4 make_YMD_4(int y, int m, int d) {return {static_cast<std::int16_t>(y),static_cast<std::uint8_t>(m),static_cast<std::uint8_t>(d)};
}
生成的汇编与上面几乎相同。
结论1:
- “date.h”的“花哨”API在编译后生成的代码和传统C结构体构造函数几乎一样快。
- 即“Cute API”零空间和时间开销!
示例2:时间点偏移(将epoch从2000-01-01切换到1970-01-01)
date.h版本
using time_point = std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
time_point shift_epoch(time_point t) {using namespace date;return t + (day_point{jan/1/2000} - day_point{jan/1/1970});
}
C-like版本
long shift_epoch(long t) {return t + 946684800; // 秒数常量
}
对应汇编几乎完全一样:
leaq 946684800(%rdi), %rax
表示将常数偏移加到传入参数。
结论2:
- 用“date.h”封装的高层时间点算术,不会导致额外运行时负担。
- 编译器优化后,与裸指针加常数一样高效。
总结
- 该日期库设计巧妙,利用C++的类型系统和表达能力,完全没有牺牲性能。
- 你写的代码看起来简洁、类型安全、功能丰富,底层机器码同样简洁高效。
- 不用担心抽象导致运行慢,放心用!
“date.h” 的时间点偏移函数性能解析
代码示例对比
// date.h 版本,使用类型安全的时间点和日期
using time_point = std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
time_point shift_epoch(time_point t) {using namespace date;return t + (day_point{jan/1/2000} - day_point{jan/1/1970});
}
vs
// 传统 C-like 版本,直接用整数秒数
long shift_epoch(long t) {return t + 946684800;
}
关键点
day_point{jan/1/2000}和day_point{jan/1/1970}其实是两个日期的序列号(自1970年1月1日起的天数)。- 这两个日期相差 10,957 天(2000年1月1日距离1970年1月1日的天数)。
- 将天数转成秒数时,等于 10,957 × 86400 = 946,684,800 秒。
- 这些计算全部在编译期完成(
constexpr),运行时只做简单加法。
运行时效果
- 编译器生成的机器码与直接加一个常量的C-like代码完全一样高效。
- 这保证了使用“date.h”库的类型安全和表达力,不会带来任何运行时开销。
总结
- 库利用了现代C++的编译期计算(
constexpr)机制。 - 高层抽象代码在底层被优化成极简机器码。
- 类型安全和性能兼得,使用该库既安全又高效。
这段内容在对比几个C++日期时间库处理“每月第5个星期五”这类较复杂日期计算时的易用性、性能和代码规模,我帮你总结和拆解一下:
Inter-library Comparison — 不同库处理“每月第5个星期五”活动
任务背景:
- 要找到**每年中所有“有第5个星期五的月份”**的具体日期。
- 这类情况每年发生4~5次。
- 目标:
- 代码实现的简易性
- 运行时的性能消耗(执行时间)
- 库本身的代码规模
统一的接口设计(测试基准)
struct ymd {std::int16_t y;std::uint8_t m;std::uint8_t d;
};
std::pair<std::array<ymd, 5>, std::uint32_t> fifth_friday(int y);
- 返回值包含最多5个日期和实际找到的数量。
测试环境
- 编译开启
-O3优化。 - 测试机器:4核MacBook Pro。
- 测量:平均多次运行时间(微秒级)。
- 输出示例:
2015-1-30
2015-5-29
2015-7-31
2015-10-30
<运行时间纳秒>
Bloomberg bdlt
- 通过静态查表来快速得出结果。
- 查表大小固定,有14条预定义模式。
- 速度快(平均几微秒内完成)。
- 代码大小中等(几十KB范围)。
- 方案本质上是查表代替计算,非常高效。
Boost date_time(传统版本)
- 通过循环12个月,调用库函数计算每月第5个星期五。
- 计算量中等,且调用外部函数。
- 代码相对较大(更多模板和函数调用)。
- 速度比Bloomberg略慢,但仍在微秒范围。
Boost date_time v2(新版)
- 类似Boost date_time,接口稍改进。
- 功能同样是按月循环调用计算。
- 性能和代码规模接近传统Boost。
Howard Hinnant’s date.h 库(你提到的date)
- 使用表达式式的语法,结合类型安全日期和时间点。
- 允许生成无效日期(例如不存在的第5个星期五),然后用
.ok()检测。 - 代码最简洁,调用表达式清晰。
- 代码规模很小(几十KB以下)。
- 性能在所有库中表现优秀,甚至更快一点。
- 灵活性好,允许无效日期操作,简化实现。
重要对比点
| 库 | 代码规模 | 执行速度 | 易用性 | 特点 |
|---|---|---|---|---|
| Bloomberg bdlt | 中等 (~几十KB) | 极快 (<5μs) | 需要查表维护 | 静态查表,极致优化 |
| Boost date_time | 较大 (~百KB) | 快 (~5-8μs) | 复杂,依赖Boost | 计算驱动,代码量大 |
| Boost date_time v2 | 较大 | 类似传统 | 类似传统 | 新接口,未来可期 |
| date.h (Howard) | 小 (~几KB) | 非常快 | 简洁易用 | 类型安全,灵活,支持无效日期 |
总结
- date.h 兼具简洁易用和高性能,且代码体积小。
- 它允许“无效日期”先生成,后验证,有利于写更自然的代码,减少分支判断。
- Bloomberg 的查表方案性能最好,但灵活性较低,需要维护表数据。
- Boost 方案更传统,但代码庞大且运行时成本稍高。
- 这体现了现代C++库设计趋向于类型安全和表达力强,同时保持高效。
1)用C标准库 <time.h> 判断2001年7月4日是星期几
#include <stdio.h>
#include <time.h>
static const char *const wday[] = {"星期日", "星期一", "星期二", "星期三","星期四", "星期五", "星期六", "-未知-"
};
int main() {struct tm time_str = {0};time_str.tm_year = 2001 - 1900; // 年份从1900算起time_str.tm_mon = 7 - 1; // 月份0开始计数time_str.tm_mday = 4;time_str.tm_isdst = -1; // 让系统自动判断夏令时if (mktime(&time_str) == (time_t)(-1)) {time_str.tm_wday = 7; // 计算失败,标记为未知}printf("%s\n", wday[time_str.tm_wday]);return 0;
}
运行后输出:
星期三
- 这里用
mktime把日期转换成时间戳,会自动填充tm_wday字段,代表星期几(0是星期日,6是星期六)。
2)用现代C++库date.h(Howard Hinnant写的)
#include "date.h"
#include <iostream>
int main() {using namespace date;std::cout << weekday{2001_y/jul/4} << '\n'; // 输出 Wed
}
- 代码更简洁、易读。
- 输出同样是星期三(Wed)。
3)编译期判断(date.h支持)
#include "date.h"
int main() {using namespace date;static_assert(weekday{2001_y/jul/4} == wed);
}
- 这是在编译阶段就判断2001年7月4日确实是星期三,编译不通过就报错。
- 很适合做编译期的日期校验。
4)时区处理 tz.h
默认之前的方法都在UTC时区下计算,实际工作中经常需要用本地时间:
#include "date/tz.h"
#include <iostream>
int main() {using namespace date;using namespace std::chrono;auto zone = locate_zone("America/Los_Angeles"); // 选择时区auto now = floor<milliseconds>(system_clock::now());auto local = zone->to_local(now);std::cout << now << " UTC\n";std::cout << local.first << " " << local.second << "\n"; // 本地时间和时区缩写
}
示例输出:
2015-09-25 16:50:06.123 UTC
2015-09-25 09:50:06.123 PDT
- 支持完整的IANA时区数据库,包括历史变动和夏令时切换。
- 还支持闰秒计算。
总结表格
| 方法 | 结果(2001年7月4日星期几) | 特点 |
|---|---|---|
| C标准库 | 星期三 | 运行时判断,跨平台 |
date.h库 | 星期三 | 现代C++,代码简洁 |
date.h 编译期 | 星期三 | 编译期校验,安全 |
tz.h + date.h | 本地时区时间 | 支持完整时区和夏令时处理 |
相关文章:
CppCon 2015 学习:A C++14 Approach to Dates and Times
Big Picture — 日期库简介 扩展 标准库 这个库是对 C 标准库中 <chrono> 的自然延伸,专注于处理“日历”相关的功能(比如年月日、闰年、节假日等),而不仅仅是时间点和时长。极简设计 它是**单头文件(header-on…...
基于CNN的OFDM-IM信号检测系统设计与实现
基于CNN的OFDM-IM信号检测系统设计与实现 摘要 本文详细研究了基于卷积神经网络(CNN)的正交频分复用索引调制(OFDM-IM)信号检测方法。通过在不同信噪比(SNR)和信道条件下进行系统仿真,对比分析了CNN检测器与传统最大似然(ML)检测器的误码率(BER)性能和计算复杂度。实验结果表…...
macos常见且应该避免被覆盖的系统环境变量(避免用 USERNAME 作为你的自定义变量名)
文章目录 macos避免用 USERNAME 作为你的自定义变量名macos常见且应该避免被覆盖的系统环境变量 macos避免用 USERNAME 作为你的自定义变量名 问题: 你执行了:export USERNAME“admin” 然后执行:echo ${USERNAME} 输出却是:xxx …...
2024年认证杯SPSSPRO杯数学建模D题(第二阶段)AI绘画带来的挑战解题全过程文档及程序
2024年认证杯SPSSPRO杯数学建模 D题 AI绘画带来的挑战 原题再现: 2023 年开年,ChatGPT 作为一款聊天型AI工具,成为了超越疫情的热门词条;而在AI的另一个分支——绘图领域,一款名为Midjourney(MJÿ…...
深入理解CSS常规流布局
引言 在网页设计中,理解元素如何排列和相互作用至关重要。CSS提供了三种主要的布局方式:常规流、浮动和定位。本文将重点探讨最基础也是最常用的常规流布局(Normal Flow),帮助开发者掌握页面布局的核心机制。 什么是…...
DOCKER使用记录
1、拉取镜像 直接使用docker pull <image>,大概率会出现下面的报错信息: (base) jetsonyahboom:~$ docker pull ubuntu:18.04 Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while …...
MYSQL中常见的函数和使用
字符串函数 CONCAT(str1,str2,...,strN) :用于将多个字符串连接成一个字符串。例如,SELECT CONCAT(SQL, , 函数) ,结果为 “SQL 函数”。 LOWER(str) :将字符串中的所有字母转换为小写。例如,SELECT LOWER(MySQL Fun…...
【深度学习相关安装及配环境】Anaconda搭建虚拟环境并安装CUDA、cuDVV和对应版本的Pytorch,并在jupyter notebook上部署
目录 1. 查看自己电脑的cuda版本2.安装cuda关于环境变量的配置测试一下,安装完成 3.安装cuDVV环境变量的配置测试一下,安装完成 4.创建虚拟环境先安装镜像源下载3.11版本py 5.在虚拟环境下,下载pytorch6.验证是否安装成功7.在jupyter noteboo…...
web3-区块链基础:从区块添加机制到哈希加密与默克尔树结构
区块链基础:从区块添加机制到哈希加密与默克尔树结构 什么是区块链 抽象的回答: 区块链提供了一种让多个参与方在没有一个唯一可信方的情况下达成合作 若有可信第三方 > 不需要区块链 [金融系统中常常没有可信的参与方] 像股票市场,或者一个国家的…...
TCP小结
1. 核心特性 面向连接:通过三次握手建立连接,四次挥手终止连接,确保通信双方状态同步。 TCP连接建立的3次握手 抓包: client发出连接请求; server回应client请求,并且同步发送syn连接; clien…...
django ssh登录 并执行命令
在Django开发环境中,通常不推荐直接通过SSH登录到服务器并执行命令,因为这违背了Django的架构设计原则,即前端与后端分离。Django主要负责处理Web请求、逻辑处理和数据库交互,而不直接执行系统级命令。然而,在某些情况…...
unix/linux,sudo,其高级使用
掌握了sudo的基石,现在是时候向更高阶的技巧和应用进发了!sudo的强大远不止于简单的sudo <command>。它的高级用法能让你在复杂的系统管理和安全场景中游刃有余,如同经验丰富的物理学家巧妙运用各种定律解决棘手问题。 sudo 的高级使用技巧与场景 精细化命令控制与参…...
Python 打包指南:setup.py 与 pyproject.toml 的全面对比与实战
在 Python 开发中,创建可安装的包是分享代码的重要方式。本文将深入解析两种主流打包方法——setup.py 和 pyproject.toml,并通过一个实际项目示例,展示如何使用现代的 pyproject.toml 方法构建、测试和发布 Python 包。 一、setup.py 与 pyp…...
计算机视觉与深度学习 | 基于OpenCV的实时睡意检测系统
基于OpenCV的实时睡意检测系统 下面是一个完整的基于OpenCV的睡意检测系统实现,该系统使用眼睛纵横比(EAR)算法检测用户是否疲劳或瞌睡。 import cv2 import numpy as np import dlib from scipy.spatial import distance as dist import pygame import time# 初始化pygame用…...
python打卡day44@浙大疏锦行
知识点回顾: 预训练的概念常见的分类预训练模型图像预训练模型的发展史预训练的策略预训练代码实战:resnet18 作业: 尝试在cifar10对比如下其他的预训练模型,观察差异,尽可能和他人选择的不同尝试通过ctrl进入resnet的…...
性能优化 - 案例篇:缓存_Guava#LoadingCache设计
文章目录 Pre引言1. 缓存基本概念2. Guava 的 LoadingCache2.1 引入依赖与初始化2.2 手动 put 与自动加载(CacheLoader)2.2.1 示例代码 2.3 缓存移除与监听(invalidate removalListener) 3. 缓存回收策略3.1 基于容量的回收&…...
NiceGUI 是一个基于 Python 的现代 Web 应用框架
NiceGUI 是一个基于 Python 的现代 Web 应用框架,它允许开发者直接使用 Python 构建交互式 Web 界面,而无需编写前端代码。以下是 NiceGUI 的主要功能和特点: 核心功能 1.简单易用的 UI 组件 提供按钮、文本框、下拉菜单、滑块、图表等常见…...
生动形象理解CNN
好的!我们把卷积神经网络(CNN)想象成一个专门识别图像的“侦探小队”,用破案过程来生动解释它的工作原理: 🕵️♂️ 案件:识别一张“猫片” 侦探小队(CNN)的破案流程&am…...
python入门(1)
第一章 第一个python程序 1.1 print函数 print方法的作用 : 把想要输出的内容打印在屏幕上 print("Hello World") 1.2 输出中文 在Python 2.x版本中,默认的编码方式是ASCII编码方式,如果程序中用到了中文,直接输出结果很可能会…...
【PDF提取表格】如何提取发票内容文字并导出到Excel表格,并将发票用发票号改名,基于pdf电子发票的应用实现
应用场景 该应用主要用于企业财务部门或个人处理大量电子发票,实现以下功能: 自动从 PDF 电子发票中提取关键信息(如发票号码、日期、金额、销售方等)将提取的信息整理并导出到 Excel 表格,方便进行财务统计和报销使…...
Hugging Face 最新开源 SmolVLA 小模型入门教程(一)
系列文章目录 目录 系列文章目录 前言 一、引言 二、认识 SmolVLA! 三、如何使用SmolVLA? 3.1 安装 3.2 微调预训练模型 3.3 从头开始训练 四、方法 五、主要架构 5.1 视觉语言模型(VLM) 5.2 动作专家:流匹…...
封闭内网安装配置VSCode Anconda3 并配置 PyQt5开发
封闭内网安装配置VSCode Anconda3 并配置 PyQt5开发 零一 vscode1.1 下载 vscode1.2 下载插件1.3 安装 二 anaconda 32.1 下载2.2 新建虚拟环境1 新建快捷方式,启动base2 新建虚拟环境 3 配置Qt designer3.1 designer.exe和uic.exe3.2 设置插件,3.4 ui文件转为py文件 4使用4.1 …...
大话软工笔记—组合要素2之逻辑
1. 逻辑的概念 逻辑,指的是思维的规律和规则,是对思维过程的抽象。 结合逻辑的一般定义以及信息系统的设计方法,对逻辑的概念进行抽提、定义为三个核心内涵,即:规律、顺序、规则。 (1)规律&a…...
浅谈边缘计算
(꒪ꇴ꒪ ),Hello我是祐言QAQ我的博客主页:C/C语言,数据结构,Linux基础,ARM开发板,网络编程等领域UP🌍快上🚘,一起学习,让我们成为一个强大的攻城狮࿰…...
宝塔专属清理区域,宝塔清理MySQL日志(高效释放空间)
1. 删除超过 365 天的积分变更记录 宝塔面板 → 数据库 → 选择数据库 → 点击 管理 进入 phpMyAdmin 后: 选择在用的数据库名 看到顶部的 SQL 点击 输入命令 然后点击执行 DELETE FROM pre_common_credit_log WHERE dateline < UNIX_TIMESTAMP(DATE_SUB(NO…...
7.Demo Js执行同步任务,微任务,宏任务的顺序(3)
一个包含 同步任务、微任务(Promise)、宏任务(setTimeout) 的例子,JS 是怎么调度这些任务的。 🎯 例子代码(建议复制到浏览器控制台运行) console.log(‘同步任务 1’); setTimeo…...
边缘计算网关赋能沸石转轮运行故障智能诊断的配置实例
一、项目背景 在环保行业,随着国家对大气污染治理要求的不断提高,VOCs废气处理成为了众多企业的重要任务。沸石转轮作为一种高效的VOCs治理设备,被广泛应用于石油化工、汽车制造、印刷包装等主流行业。这些行业生产规模大、废气排放量多&…...
机器学习之深入理解机器学习常见算法:原理、公式与应用
机器学习之深入理解机器学习常见算法:原理、公式与应用 机器学习是一门让计算机自动从数据中学习规律的技术体系。常见的机器学习算法可以分为监督学习、无监督学习和深度学习三大类。本文将系统介绍每类中具有代表性的算法,并深入剖析其核心原理与数学基础。 一、监督学习(…...
Python实例题: Python 的简单电影信息
目录 Python实例题 题目 代码实现 实现原理 网页请求: 内容解析: 数据存储: 反爬策略: 关键代码解析 1. 网页请求处理 2. 电影列表解析 3. 电影详情解析 4. 爬虫主逻辑 使用说明 安装依赖: 修改配置&a…...
MyBatis 的动态 SQL
1. 动态 SQL 的定义 动态 SQL 是 MyBatis 的核心特性之一,它允许开发者根据运行时条件动态生成 SQL 语句。通过特殊的 XML 标签或注解语法,实现 SQL 的灵活拼接,避免在 Java 代码中手动拼接 SQL 字符串的复杂性和安全风险。 2. 核心作用 条…...
