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(
|
private func envelopeToRecord(
|
||||||
_ envelope: FetchedEnvelope, accountId: String, mailboxId: String
|
_ envelope: FetchedEnvelope, accountId: String, mailboxId: String
|
||||||
) -> MessageRecord {
|
) -> MessageRecord {
|
||||||
let toJson = encodeAddresses(envelope.to)
|
// Decode RFC 2047 encoded words in all display names
|
||||||
let ccJson = encodeAddresses(envelope.cc)
|
let decodedTo = envelope.to.map { EmailAddress(name: $0.name.map { RFC2047Decoder.decode($0) }, address: $0.address) }
|
||||||
// Decode RFC 2047 encoded words in subject and sender name
|
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 decodedSubject = envelope.subject.map { RFC2047Decoder.decode($0) }
|
||||||
let decodedFromName = envelope.from?.name.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(
|
return MessageRecord(
|
||||||
id: UUID().uuidString,
|
id: UUID().uuidString,
|
||||||
accountId: accountId,
|
accountId: accountId,
|
||||||
@@ -293,7 +304,7 @@ public final class SyncCoordinator {
|
|||||||
fromName: decodedFromName,
|
fromName: decodedFromName,
|
||||||
toAddresses: toJson,
|
toAddresses: toJson,
|
||||||
ccAddresses: ccJson,
|
ccAddresses: ccJson,
|
||||||
date: envelope.date,
|
date: isoDate,
|
||||||
snippet: envelope.snippet,
|
snippet: envelope.snippet,
|
||||||
bodyText: envelope.bodyText,
|
bodyText: envelope.bodyText,
|
||||||
bodyHtml: envelope.bodyHtml,
|
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? {
|
private func encodeAddresses(_ addresses: [EmailAddress]) -> String? {
|
||||||
guard !addresses.isEmpty else { return nil }
|
guard !addresses.isEmpty else { return nil }
|
||||||
struct Addr: Codable { var name: String?; var address: String }
|
struct Addr: Codable { var name: String?; var address: String }
|
||||||
|
|||||||
Reference in New Issue
Block a user