
本文介绍如何使用 go 的 time 包将毫秒级 unix 时间戳(如 mongodb 文档中的 timestamp)精准聚类为按自然月或周一至周日划分的周数组,提供可直接复用的分组逻辑与完整示例代码。
本文介绍如何使用 go 的 `time` 包将毫秒级 unix 时间戳(如 mongodb 文档中的 timestamp)精准聚类为按自然月或周一至周日划分的周数组,提供可直接复用的分组逻辑与完整示例代码。
在实际数据处理场景中(例如分析 MongoDB 返回的带毫秒级 UTC 时间戳的文档集合),常需将时间序列数据按业务周期聚合——如“同属 2024 年 5 月的所有文档归为一组”,或“周一(00:00)至周日(23:59:59.999)为一个自然周”。Go 标准库 time 提供了足够精确且时区安全的工具,但需注意:毫秒时间戳需转换为 time.Time,且周计算必须显式对齐到周一(Go 默认 Weekday() 返回值正确,但“属于哪一周”需基于起始日归一化)。
以下是一个生产就绪的聚类实现,支持按月(year-month)和按周(ISO 周,以周一为起点)两种模式:
package main import ( "fmt" "sort" "time" ) // Document 模拟 MongoDB 文档结构 type Document struct { ID String Timestamp int64 // UTC milliseconds since Unix epoch // 其他字段... } // MonthKey 返回形如 "2024-05" 的月标识符 func (d Document) MonthKey() string { t := time.Unix(0, d.Timestamp*int64(time.Millisecond)).UTC() return t.format("2006-01") } // WeekKey 返回该时间所属的周一零点对应的日期(ISO 周,周一为第 1 天) func (d Document) WeekKey() string { t := time.Unix(0, d.Timestamp*int64(time.Millisecond)).UTC() // 向前推算到最近的周一(含当天) y, m, day := t.date() w := t.Weekday() daysSinceMonday := int(w - time.Monday) if daysSinceMonday < 0 { daysSinceMonday += 7 } monday := time.Date(y, m, day-daysSinceMonday, 0, 0, 0, 0, t.Location()) return monday.Format("2006-01-02") // 如 "2024-04-22" } // ClusterByMonth 将文档切片按月分组 func ClusterByMonth(docs []Document) map[string][]Document { clusters := make(map[string][]Document) for _, doc := range docs { key := doc.MonthKey() clusters[key] = append(clusters[key], doc) } return clusters } // ClusterByWeek 将文档切片按周(周一至周日)分组 func ClusterByWeek(docs []Document) map[string][]Document { clusters := make(map[string][]Document) for _, doc := range docs { key := doc.WeekKey() clusters[key] = append(clusters[key], doc) } return clusters } func main() { // 示例数据:3 个不同时间戳的文档(毫秒级 UTC) docs := []Document{ {ID: "A", Timestamp: 1716796800000}, // 2024-05-27 00:00:00 UTC → May & week starting 2024-05-27 (Mon) {ID: "B", Timestamp: 1716883199999}, // 2024-05-27 23:59:59.999 UTC → same month & week {ID: "C", Timestamp: 1716969600000}, // 2024-05-28 00:00:00 UTC → same week (Mon), same month {ID: "D", Timestamp: 1714550400000}, // 2024-04-30 00:00:00 UTC → April & week starting 2024-04-29 (Mon) } // 按月聚类 byMonth := ClusterByMonth(docs) fmt.Println("=== By Month ===") for month, group := range byMonth { fmt.Printf("%s: %d docsn", month, len(group)) } // 按周聚类 byWeek := ClusterByWeek(docs) fmt.Println("n=== By Week (Mon–Sun) ===") for weekStart, group := range byWeek { fmt.Printf("%s: %d docsn", weekStart, len(group)) } }
✅ 关键要点说明:
- 毫秒转时间:务必使用 time.Unix(0, ms*int64(time.Millisecond)),而非 time.Unix(ms/1000, (ms%1000)*1e6),避免整数除法截断误差;
- 时区安全:调用 .UTC() 显式指定时区,避免本地时区干扰(MongoDB 存储 UTC,应始终以 UTC 计算);
- 周计算逻辑:Weekday() 仅返回星期几,真正“属于哪一周”需回溯至当周周一零点——本例采用 time.Date 构造周一时间点并格式化为 YYYY-MM-DD,确保跨月周(如 4 月 29 日周一至 5 月 5 日周日)被统一归入 “2024-04-29” 键;
- 键设计建议:使用 Format(“2006-01”) 和 Format(“2006-01-02”) 生成可排序、可读性强的字符串键,便于后续按时间顺序遍历或存入 map;
- 性能提示:若文档量极大(>10⁵),可预分配 map[string][]Document 的容量,或改用 sort.Slice + 二分查找优化,但对常规分析场景,上述实现已足够高效。
通过此方案,你可无缝集成到数据管道中,支撑日报、周报、月报等定时聚合任务,且完全兼容 Go 原生时间模型与 UTC 语义。