commit 0b27a49e32a89a0dcd594ca16ae8add832159e7c
parent 58b4d57f32c81fc5433de58668246ef6ffcb05d4
Author: Terry Yiu <963907+tyiu@users.noreply.github.com>
Date: Fri, 30 Dec 2022 23:32:33 -0500
Internationalize time ago since string
Switches to using standard date component formatting abbreviations and
enables it to be localized to non-English locales
Closes: #194
Changelog-Changed: Internationalize relative dates
Diffstat:
3 files changed, 57 insertions(+), 10 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -11,6 +11,7 @@
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; };
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; };
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; };
+ 3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */; };
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670028FC7C5900038D2A /* RelayView.swift */; };
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4C06670328FC7EC500038D2A /* Kingfisher */; };
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670528FCB08600038D2A /* ImageCarousel.swift */; };
@@ -162,6 +163,7 @@
3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTimelineView.swift; sourceTree = "<group>"; };
3169CAEC294FCCFC00EE4006 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = damus/Util/Constants.swift; sourceTree = SOURCE_ROOT; };
31D2E846295218AF006D67F8 /* Shimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shimmer.swift; sourceTree = "<group>"; };
+ 3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeAgoTests.swift; sourceTree = "<group>"; };
4C06670028FC7C5900038D2A /* RelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayView.swift; sourceTree = "<group>"; };
4C06670528FCB08600038D2A /* ImageCarousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarousel.swift; sourceTree = "<group>"; };
4C06670828FDE64700038D2A /* damus-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "damus-Bridging-Header.h"; sourceTree = "<group>"; };
@@ -608,6 +610,7 @@
4C363A9D2828A822006E126D /* ReplyTests.swift */,
4CE6DEF727F7A08200C66700 /* damusTests.swift */,
4C3EA67A28FF7B3900C48A62 /* InvoiceTests.swift */,
+ 3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */,
);
path = damusTests;
sourceTree = "<group>";
@@ -898,6 +901,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */,
4C3EA67B28FF7B3900C48A62 /* InvoiceTests.swift in Sources */,
4C363A9E2828A822006E126D /* ReplyTests.swift in Sources */,
4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */,
diff --git a/damus/Util/TimeAgo.swift b/damus/Util/TimeAgo.swift
@@ -11,36 +11,45 @@ public func time_ago_since(_ date: Date) -> String {
let calendar = Calendar.current
let now = Date()
- let unitFlags: NSCalendar.Unit = [.second, .minute, .hour, .day, .weekOfYear, .month, .year]
+ let unitFlags: NSCalendar.Unit = [.second, .minute, .hour, .day, .weekOfMonth, .month, .year]
+
let components = (calendar as NSCalendar).components(unitFlags, from: date, to: now, options: [])
+ let formatter = DateComponentsFormatter()
+ formatter.unitsStyle = .abbreviated
+ formatter.maximumUnitCount = 1
+ formatter.allowedUnits = unitFlags
+
+ // Manually format date component from only the most significant time unit because
+ // DateComponentsFormatter rounds up by default.
+
if let year = components.year, year >= 1 {
- return "\(year)yr"
+ return formatter.string(from: DateComponents(calendar: calendar, year: year))!
}
if let month = components.month, month >= 1 {
- return "\(month)mth"
+ return formatter.string(from: DateComponents(calendar: calendar, month: month))!
}
- if let week = components.weekOfYear, week >= 1 {
- return "\(week)wk"
+ if let week = components.weekOfMonth, week >= 1 {
+ return formatter.string(from: DateComponents(calendar: calendar, weekOfMonth: week))!
}
if let day = components.day, day >= 1 {
- return "\(day)d"
+ return formatter.string(from: DateComponents(calendar: calendar, day: day))!
}
if let hour = components.hour, hour >= 1 {
- return "\(hour)h"
+ return formatter.string(from: DateComponents(calendar: calendar, hour: hour))!
}
if let minute = components.minute, minute >= 1 {
- return "\(minute)m"
+ return formatter.string(from: DateComponents(calendar: calendar, minute: minute))!
}
if let second = components.second, second >= 3 {
- return "\(second)s"
+ return formatter.string(from: DateComponents(calendar: calendar, second: second))!
}
- return "now"
+ return NSLocalizedString("now", comment: "String indicating that a given timestamp just occurred")
}
diff --git a/damusTests/TimeAgoTests.swift b/damusTests/TimeAgoTests.swift
@@ -0,0 +1,34 @@
+//
+// TimeAgoTests.swift
+// damusTests
+//
+// Created by Terry Yiu on 12/30/22.
+//
+
+import XCTest
+@testable import damus
+
+final class TimeAgoTests: XCTestCase {
+
+ func testTimeAgoSince() {
+ XCTAssertEqual(time_ago_since(Date.now), "now")
+ XCTAssertEqual(time_ago_since(Date.now.addingTimeInterval(-2)), "now")
+ XCTAssertEqual(time_ago_since(Date.now.addingTimeInterval(-3)), "3s")
+ XCTAssertEqual(time_ago_since(Date.now.addingTimeInterval(-59)), "59s")
+ XCTAssertEqual(time_ago_since(Date.now.addingTimeInterval(-60)), "1m")
+ XCTAssertEqual(time_ago_since(Date.now.addingTimeInterval(-3599)), "59m")
+ XCTAssertEqual(time_ago_since(Date.now.addingTimeInterval(-3600)), "1h")
+ XCTAssertEqual(time_ago_since(Date.now.addingTimeInterval(-86399)), "23h")
+ XCTAssertEqual(time_ago_since(Date.now.addingTimeInterval(-86400)), "1d")
+ XCTAssertEqual(time_ago_since(Calendar.current.date(byAdding: .weekOfMonth, value: -1, to: Date.now)!.addingTimeInterval(1)), "6d")
+ XCTAssertEqual(time_ago_since(Calendar.current.date(byAdding: .weekOfMonth, value: -1, to: Date.now)!), "1w")
+ XCTAssertEqual(time_ago_since(Calendar.current.date(byAdding: .weekOfMonth, value: -2, to: Date.now)!), "2w")
+ XCTAssertEqual(time_ago_since(Calendar.current.date(byAdding: .weekOfMonth, value: -3, to: Date.now)!), "3w")
+ // Not testing the 4-5 week boundary since how it is formatted depends on which month and year it is currently when this test executes.
+ XCTAssertEqual(time_ago_since(Calendar.current.date(byAdding: .month, value: -1, to: Date.now)!), "1mo")
+ XCTAssertEqual(time_ago_since(Calendar.current.date(byAdding: .year, value: -1, to: Date.now)!.addingTimeInterval(1)), "11mo")
+ XCTAssertEqual(time_ago_since(Calendar.current.date(byAdding: .year, value: -1, to: Date.now)!), "1y")
+ XCTAssertEqual(time_ago_since(Calendar.current.date(byAdding: .year, value: -1000, to: Date.now)!), "1,000y")
+ }
+
+}