如何用JavaScript/ES6将5年日度数据聚合至每月最后一日?
嘿,这个需求其实挺常见的,用ES6的数组方法就能轻松搞定,我给你拆解一下思路和具体代码,保证你能看懂~
首先,核心思路其实很简单:把同一月份的日度数据归为一组,然后在每组里挑出日期最晚的那条(也就是当月最后一天的数据),最后整理成你要的格式就行。
接下来是具体实现,我会一步步给你讲清楚:
第一步:先处理日期解析(避免时区坑)
直接用new Date(dateStr)有时候会因为时区问题出bug,比如你在东八区的话,new Date('2018-01-31')可能会被解析成前一天的UTC时间,导致月份判断错误。所以我们写个辅助函数,用UTC来解析日期:
const parseUtcDate = (dateStr) => { const [year, month, day] = dateStr.split('-').map(Number); // 月份是0开始的,所以要减1,用Date.UTC确保是UTC时间 return new Date(Date.UTC(year, month - 1, day)); };
第二步:把日度数据按「年-月」分组
用Array.reduce()来分组,把同一月份的数据放在一起,这样后续处理更方便:
// 假设你的原始日度数据是这个格式 const dailyData = [ { x: '2018-01-01', y: 10 }, { x: '2018-01-31', y: 15 }, { x: '2018-02-28', y: 22 }, // ... 剩下的5年数据 ]; // 按年-月分组 const groupedByMonth = dailyData.reduce((groups, item) => { const date = parseUtcDate(item.x); const year = date.getUTCFullYear(); // 把月份转成两位数字(比如1月变成'01'),保证分组key统一 const month = String(date.getUTCMonth() + 1).padStart(2, '0'); const groupKey = `${year}-${month}`; // 如果这个分组还不存在,就创建一个空数组 if (!groups[groupKey]) { groups[groupKey] = []; } groups[groupKey].push(item); return groups; }, {});
第三步:从每个分组里提取当月最后一天的数据
每个分组里的日期不一定是按顺序排的,我们用reduce()高效找到日期最晚的那条数据:
const monthlyLastDayData = Object.values(groupedByMonth).map(group => { return group.reduce((latestItem, currentItem) => { const latestDate = parseUtcDate(latestItem.x); const currentDate = parseUtcDate(currentItem.x); // 比较两个日期,返回更晚的那个 return currentDate > latestDate ? currentItem : latestItem; }); });
第四步:给结果按时间排序(可选但推荐)
分组后的顺序可能不是严格的时间顺序,所以我们给结果排个序:
monthlyLastDayData.sort((a, b) => parseUtcDate(a.x) - parseUtcDate(b.x));
处理特殊情况:如果当月最后一天的数据缺失怎么办?
如果你的日度数据偶尔有缺失,比如某个月的最后一天没有数据,我们可以先计算出当月的最后一天,再去匹配数据,找不到的话就取当月最晚的那条:
先写个计算当月最后一天的函数:
const getLastDayOfMonth = (year, month) => { // month是1-12的数字,Date.UTC(year, month, 0)会返回当月最后一天的UTC时间 return new Date(Date.UTC(year, month, 0)).getUTCDate(); };
然后修改第三步的代码:
const monthlyLastDayData = Object.entries(groupedByMonth).map(([groupKey, group]) => { const [year, month] = groupKey.split('-').map(Number); const lastDay = getLastDayOfMonth(year, month); // 拼接成当月最后一天的日期字符串,比如'2018-02-28' const lastDayStr = `${year}-${month.toString().padStart(2, '0')}-${lastDay.toString().padStart(2, '0')}`; // 查找分组里有没有当天的数据 const targetItem = group.find(item => item.x === lastDayStr); // 如果找不到,就取分组里日期最晚的那条 return targetItem || group.reduce((latest, current) => parseUtcDate(current.x) > parseUtcDate(latest.x) ? current : latest); });
优化小技巧:减少重复解析日期
如果数据量很大,重复解析日期会有点浪费性能,我们可以先预处理数据,把每个item的日期提前解析好:
// 预处理数据,缓存解析后的日期 const processedDailyData = dailyData.map(item => ({ ...item, utcDate: parseUtcDate(item.x) })); // 后续分组和比较的时候直接用utcDate就行,不用再重复解析 const groupedByMonth = processedDailyData.reduce((groups, item) => { const year = item.utcDate.getUTCFullYear(); const month = String(item.utcDate.getUTCMonth() + 1).padStart(2, '0'); const groupKey = `${year}-${month}`; if (!groups[groupKey]) groups[groupKey] = []; groups[groupKey].push(item); return groups; }, {});
这样处理后,效率会更高一些~
现在你运行这些代码,就能得到你想要的[{ x: '2018-03-31', y: 27 }, ...]格式的数据啦!
内容的提问来源于stack exchange,提问作者Waggatron
相关产品推荐
相关产品推荐

