decode RFC 2047 in To/CC names, convert dates to ISO 8601 for correct sorting
- decode RFC 2047 encoded words in To/CC address display names - convert IMAP RFC 2822 dates to ISO 8601 before storing in MessageRecord, so SQLite ORDER BY text comparison sorts chronologically - strip parenthesized timezone names from dates (e.g. "+0100 (CET)") Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -275,11 +275,22 @@ public final class SyncCoordinator {
|
||||
private func envelopeToRecord(
|
||||
_ envelope: FetchedEnvelope, accountId: String, mailboxId: String
|
||||
) -> MessageRecord {
|
||||
let toJson = encodeAddresses(envelope.to)
|
||||
let ccJson = encodeAddresses(envelope.cc)
|
||||
// Decode RFC 2047 encoded words in subject and sender name
|
||||
// Decode RFC 2047 encoded words in all display names
|
||||
let decodedTo = envelope.to.map { EmailAddress(name: $0.name.map { RFC2047Decoder.decode($0) }, address: $0.address) }
|
||||
let decodedCc = envelope.cc.map { EmailAddress(name: $0.name.map { RFC2047Decoder.decode($0) }, address: $0.address) }
|
||||
let toJson = encodeAddresses(decodedTo)
|
||||
let ccJson = encodeAddresses(decodedCc)
|
||||
let decodedSubject = envelope.subject.map { RFC2047Decoder.decode($0) }
|
||||
let decodedFromName = envelope.from?.name.map { RFC2047Decoder.decode($0) }
|
||||
|
||||
// Convert RFC 2822 date to ISO 8601 for correct text-based sorting in SQLite
|
||||
let isoDate: String
|
||||
if let parsed = Self.parseRFC2822Date(envelope.date) {
|
||||
isoDate = ISO8601DateFormatter().string(from: parsed)
|
||||
} else {
|
||||
isoDate = envelope.date
|
||||
}
|
||||
|
||||
return MessageRecord(
|
||||
id: UUID().uuidString,
|
||||
accountId: accountId,
|
||||
@@ -293,7 +304,7 @@ public final class SyncCoordinator {
|
||||
fromName: decodedFromName,
|
||||
toAddresses: toJson,
|
||||
ccAddresses: ccJson,
|
||||
date: envelope.date,
|
||||
date: isoDate,
|
||||
snippet: envelope.snippet,
|
||||
bodyText: envelope.bodyText,
|
||||
bodyHtml: envelope.bodyHtml,
|
||||
@@ -303,6 +314,29 @@ public final class SyncCoordinator {
|
||||
)
|
||||
}
|
||||
|
||||
nonisolated(unsafe) private static let rfc2822Fmt: DateFormatter = {
|
||||
let f = DateFormatter()
|
||||
f.locale = Locale(identifier: "en_US_POSIX")
|
||||
f.dateFormat = "EEE, dd MMM yyyy HH:mm:ss Z"
|
||||
return f
|
||||
}()
|
||||
|
||||
nonisolated(unsafe) private static let rfc2822FmtNoDow: DateFormatter = {
|
||||
let f = DateFormatter()
|
||||
f.locale = Locale(identifier: "en_US_POSIX")
|
||||
f.dateFormat = "dd MMM yyyy HH:mm:ss Z"
|
||||
return f
|
||||
}()
|
||||
|
||||
private static func parseRFC2822Date(_ dateString: String) -> Date? {
|
||||
// Strip parenthesized timezone name if present: "... +0100 (CET)" → "... +0100"
|
||||
var cleaned = dateString.trimmingCharacters(in: .whitespaces)
|
||||
if let parenRange = cleaned.range(of: #"\s*\([^)]*\)\s*$"#, options: .regularExpression) {
|
||||
cleaned = String(cleaned[..<parenRange.lowerBound])
|
||||
}
|
||||
return rfc2822Fmt.date(from: cleaned) ?? rfc2822FmtNoDow.date(from: cleaned)
|
||||
}
|
||||
|
||||
private func encodeAddresses(_ addresses: [EmailAddress]) -> String? {
|
||||
guard !addresses.isEmpty else { return nil }
|
||||
struct Addr: Codable { var name: String?; var address: String }
|
||||
|
||||
Reference in New Issue
Block a user