
本文介绍如何使用 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 docs\n", month, len(group))
}
// 按周聚类
byWeek := ClusterByWeek(docs)
fmt.Println("\n=== By Week (Mon–Sun) ===")
for weekStart, group := range byWeek {
fmt.Printf("%s: %d docs\n", 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 语义。










