
本文介绍如何使用 go 的 time 包将毫秒级 unix 时间戳解析为标准时间,进而按自然月或 iso 周(周一至周日)对文档数组进行聚类分组,生成嵌套的二维切片结构。
本文介绍如何使用 go 的 time 包将毫秒级 unix 时间戳解析为标准时间,进而按自然月或 iso 周(周一至周日)对文档数组进行聚类分组,生成嵌套的二维切片结构。
在实际数据处理场景中(如从 MongoDB 查询出带毫秒级 UTC 时间戳的文档集合),常需按时间维度(如月、周)聚合数据。Go 语言本身不提供直接的“时间聚类”函数,但可通过 time.Unix() 将毫秒时间戳安全转换为 time.Time,再利用其方法提取归一化的时间标识(如年+月、ISO 周编号),从而实现高效分组。
以下是一个完整的、生产就绪的分组示例,支持按自然月(YYYY-MM)和按ISO 周(周一为每周起始,对应 time.ISOWeek())两种模式:
package main
import (
"fmt"
"sort"
"time"
)
// Document 模拟带有毫秒时间戳的文档结构
type Document struct {
ID string
Timestamp int64 // UTC milliseconds since Unix epoch
}
// groupByMonth 按年月(格式:2024-03)对文档分组
func groupByMonth(docs []Document) map[string][]Document {
groups := make(map[string][]Document)
for _, doc := range docs {
t := time.Unix(0, doc.Timestamp*int64(time.Millisecond))
key := t.Format("2006-01") // 格式化为"YYYY-MM"
groups[key] = append(groups[key], doc)
}
return groups
}
// groupByWeek 按 ISO 周(年+周序号,如"2024-W12")分组,周起始为周一
func groupByWeek(docs []Document) map[string][]Document {
groups := make(map[string][]Document)
for _, doc := range docs {
t := time.Unix(0, doc.Timestamp*int64(time.Millisecond))
year, week := t.ISOWeek()
key := fmt.Sprintf("%d-W%02d", year, week)
groups[key] = append(groups[key], doc)
}
return groups
}
// toNestedSlice 将 map[string][]Document 转为 [][]Document(保持时间顺序)
func toNestedSlice(groups map[string][]Document) [][]Document {
var keys []string
for k := range groups {
keys = append(keys, k)
}
sort.Strings(keys) // 按字符串排序可保证 YYYY-MM 或 YYYY-Wxx 的自然时序
result := make([][]Document, 0, len(keys))
for _, k := range keys {
result = append(result, groups[k])
}
return result
}
func main() {
// 示例数据:3个不同时间戳的文档(毫秒级 UTC)
docs := []Document{
{ID: "A", Timestamp: 1709222400000}, // 2024-03-01 00:00:00 UTC → March
{ID: "B", Timestamp: 1709827200000}, // 2024-03-08 00:00:00 UTC → March & Week 10
{ID: "C", Timestamp: 1710259200000}, // 2024-03-12 00:00:00 UTC → March & Week 11
}
// 按月分组
byMonth := groupByMonth(docs)
fmt.Println("By Month:")
for month, group := range byMonth {
fmt.Printf(" %s: %v\n", month, len(group))
}
// 转为嵌套切片([][]Document)
nestedByMonth := toNestedSlice(byMonth)
fmt.Printf("Result as [][]Document (len=%d)\n", len(nestedByMonth))
// 按 ISO 周分组
byWeek := groupByWeek(docs)
fmt.Println("\nBy ISO Week:")
for week, group := range byWeek {
fmt.Printf(" %s: %v\n", week, len(group))
}
}✅ 关键要点说明:
- 使用 time.Unix(0, ms*int64(time.Millisecond)) 正确将毫秒时间戳转为 time.Time —— 注意单位换算(纳秒 = 毫秒 × 1,000,000),time.Millisecond 是纳秒常量,无需手动乘 1e6;
- t.Format("2006-01") 是 Go 时间格式化的固定布局(参考 Go time layout doc),确保年月标准化;
- t.ISOWeek() 返回符合 ISO 8601 的年份与周序号,天然支持“周一为周始、周日为周终”的语义;
- 分组后若需返回 [][]Document(即题目要求的“数组的数组”),应显式排序键(如字符串升序),避免 map 遍历无序导致结果不稳定;
- 实际集成到 MongoDB 场景时,可将 []Document 替换为你的结构体(如含 CreatedAt int64 字段),逻辑完全复用。
⚠️ 注意事项:
- 所有时间操作默认基于本地时区;若需严格 UTC,请在解析后调用 .UTC()(例如 t := time.Unix(...).UTC());
- 对超大数组(>10⁵ 文档),建议预分配 groups map 容量或改用 sync.Map(并发安全);
- 若需支持自定义周起始(如周日),需手动计算 Weekday() 并调整偏移,而非依赖 ISOWeek()。
通过以上方式,你可在 Go 中稳健、清晰地完成毫秒级时间戳到月/周粒度的聚类任务,兼顾可读性、可维护性与生产可用性。










