最近的项目中,突然增加一个时区的需求,而且echarts图表 要支持 夏令时、冬令时的转换。
拿到需求,我们先了解一下相关概念:
时区的概念
时区是全球范围内,为统一时间标准而划分的区域,其核心逻辑是基于经度差异,将地球划分为若干个时间同步的区域,每个区域采用同一“区时”作为标准时间。
- 全球通用性:24个时区覆盖全球,每个时区内的区域采用同一区时,避免时间混乱。
- 国家/地区实践:部分国家(如中国)虽跨多个时区(中国跨东五区—东九区),但为统一管理,统一采用东八区的“北京时间” 作为全国标准时间。
夏令时、冬令时的概念
一、夏令时(Daylight Saving Time,DST)
- 定义:夏季到来前,人为将时钟拨快1小时,新的时间称为“夏令时间”。
- 目的:充分利用夏季白天的自然光照,减少夜间照明用电(如夏季天亮早、天黑晚,提前1小时作息可延长白天活动时间,降低照明需求)。
- 实施周期:通常从每年3月最后一个星期日或4月初开始,持续至10月最后一个星期日(不同国家/地区规则略有差异)。
二、冬令时(Standard Time/Winter Time)
- 定义:冬季到来前,人为将时钟拨慢1小时,恢复到“标准时间”(即未调整的常规时间)。
- 目的:充分利用冬季有限的日照时间,节省夜间电力(如冬季天亮晚、天黑早,推迟1小时作息可延长夜晚灯光使用时间,降低照明成本)。
- 实施周期:通常从每年11月左右开始,持续至次年3月(与夏令时的“拨快”周期形成季节性互补)。
与夏令时转冬令时的区别:
| 特性 | 冬令时转夏令时 | 夏令时转冬令时 |
|---|---|---|
| 时间变化 | 01:59 → 03:00(跳过1小时) | 01:59 → 01:00(重复1小时) |
| 数据点 | 缺失1小时数据 | 重复1小时数据 |
| 视觉表现 | 线条断开或跳跃 | 线条连续但有重叠 |
| 标记重点 | 显示缺失时间段 | 显示重复时间段 |
关键结论
夏令时转冬令时会多一小时
冬令时转夏令时会少一小时
不同国家/地区夏令时/冬令时的起止规则略有差异(如美国夏令时始于3月第二个周日凌晨2点,冬令时始于11月第一个周日凌晨2点),但“拨快/拨慢1小时”的核心逻辑一致。
前端实现方案
所以,冬令时转夏令时,其实很好实现。无非就是少了一个小的数据、x轴补充时间点连贯起来即可。
难点是,夏令时转冬令时比较麻烦,会多出一个小时。如何来实现呢?
问题:
在ECharts中,x轴通常用于表示连续的数据,比如时间序列。但是,如果你有重复的时间段,比如冬令时和夏令时转换时出现的一个小时重叠,那么直接使用时间轴可能会导致问题,因为ECharts的时间轴默认是连续的,不允许重复。
解决方案: 还好在ECharts中,xAxis使用category轴,将时间点作为字符串标签显示可以不用关心重复的时间点。
同时,我们需要用markLine来标注 夏令时结束、冬令时开始的点。但是要标注到2个一样的时间值点,也会有问题。
这里为了区分,我们给重叠时间点的值增加一个*号 ,比如: '00:00', '00:30', '01:00', '01:30', '01:59', '01:00*', '01:30*', '02:00'
然后xAxis、tooltip展示的时候移除掉*即可。
具体配置如下:
option = {
tooltip: {
trigger: "axis",
confine: false,
appendToBody: true,
formatter: function(params) {
// 处理x轴的值,去掉*
let xValue = params[0].axisValue;
if (xValue.includes('*')) {
xValue = xValue.replace('*', '');
}
// 构建tooltip内容
let result = xValue + '<br/>';
params.forEach(function(item) {
result += item.marker + item.seriesName + ': ' + item.value + '<br/>';
});
return result;
}
},
xAxis: {
type: 'category',
data: [
'00:00', '00:30', '01:00', '01:30', '01:59',
'01:00*', '01:30*', '02:00'
],
axisLabel: {
formatter: function(value) {
if (value.includes('*')) {
return value.replace('*', '');
}
return value;
}
}
},
yAxis: {
type: 'value'
},
series: [{
name: '数据', // 添加series名称,方便tooltip显示
data: [1, 2, 3, 4, 5, 3, 2, 5],
type: 'line',
lineStyle: {
color: '#1890ff',
width: 2
},
markLine: {
silent: true,
symbol: 'none',
lineStyle: {
type: 'dashed',
width: 1
},
data: [
{
name: '夏令时结束\nUTC+7',
xAxis: '01:59',
label: {
formatter: '{b}',
backgroundColor: 'rgba(255,150,0,0.3)',
padding: [2, 4],
borderColor: '#ff9600',
borderWidth: 1
},
lineStyle: {
color: '#ff9600'
}
},
{
name: '冬令时开始\nUTC+8',
xAxis: '01:00*',
label: {
formatter: '{b}',
backgroundColor: 'rgba(0,102,204,0.3)',
padding: [2, 4],
borderColor: '#0066cc',
borderWidth: 1
},
lineStyle: {
color: '#0066cc'
}
}
]
}
}],
animation: false
};