Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion internal/sync/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,11 @@ func (s *Syncer) processBatch(ctx context.Context, sourceID int64, listResp *gma
// Track oldest message date for progress display
// Gmail returns messages newest-to-oldest, so oldest shows where we've reached
if raw.InternalDate > 0 {
msgDate := time.UnixMilli(raw.InternalDate)
// .UTC() to match how InternalDate is normalized everywhere
// else (the stored message date and parsed.Date both use
// .UTC()); without it oldestDate carries the local zone and
// callers reading its calendar day are off by one east of UTC.
msgDate := time.UnixMilli(raw.InternalDate).UTC()
if result.oldestDate.IsZero() || msgDate.Before(result.oldestDate) {
result.oldestDate = msgDate
}
Expand Down
2 changes: 1 addition & 1 deletion internal/whatsapp/importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ func (imp *Importer) Import(ctx context.Context, waDBPath string, opts ImportOpt
reactorID = selfParticipantID
}

createdAt := time.Unix(r.Timestamp/1000, 0)
createdAt := time.Unix(r.Timestamp/1000, 0).UTC()
if err := imp.store.UpsertReaction(messageID, reactorID, reactionTypeEmoji, reactionValue, createdAt); err != nil {
summary.Errors++
imp.progress.OnError(fmt.Errorf("upsert reaction: %w", err))
Expand Down
6 changes: 5 additions & 1 deletion internal/whatsapp/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ func mapMessage(msg waMessage, conversationID, sourceID int64, senderID sql.Null
sentAt := sql.NullTime{}
if msg.Timestamp > 0 {
// WhatsApp timestamps are in milliseconds since epoch.
// .UTC() to match how every other importer normalizes stored
// message dates; without it SentAt carries the local zone and
// Parquet year-partitioning / calendar-day reads are off by one
// near boundaries east of UTC.
sentAt = sql.NullTime{
Time: time.Unix(msg.Timestamp/1000, (msg.Timestamp%1000)*1e6),
Time: time.Unix(msg.Timestamp/1000, (msg.Timestamp%1000)*1e6).UTC(),
Valid: true,
}
}
Expand Down
17 changes: 17 additions & 0 deletions internal/whatsapp/mapping_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,20 @@ func TestChatTitle(t *testing.T) {
}
assertpkg.Equal(t, "+447700900000", chatTitle(direct), "chatTitle(direct)")
}

func TestMapMessageSentAtIsUTC(t *testing.T) {
assert := assertpkg.New(t)

// 2024-01-10T12:00:00Z in milliseconds. On a machine east of UTC
// (e.g. NZ) a non-UTC SentAt would read back as Jan 11 and carry the
// local zone — wrong for Parquet year-partitioning and date display.
msg := waMessage{Timestamp: 1704888000000}
got := mapMessage(msg, 1, 1, sql.NullInt64{})

requirepkg.True(t, got.SentAt.Valid, "SentAt should be set")
assert.Equal("UTC", got.SentAt.Time.Location().String(), "SentAt must be normalized to UTC")
y, m, d := got.SentAt.Time.Date()
assert.Equal(2024, y, "year")
assert.Equal(1, int(m), "month")
assert.Equal(10, d, "day")
}