echarts 实现一个夏令时转冬令时的图表

117 阅读4分钟

image.png 最近的项目中,突然增加一个时区的需求,而且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
};