summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2020-05-15 14:48:35 +0200
committerAndrej Mihajlov <and@mullvad.net>2020-05-15 14:48:35 +0200
commit98c62cd60e88926b73168042a9bb46a95bcff92e (patch)
treec5b74e5c86f5c2df8620117a318c04262256cfc5
parentffa654e0c0bb7e8be53adf30a4edf40274d6ff60 (diff)
parent83aefa4348960d1b74e14ad14bb6ee1a9f2a81cb (diff)
downloadmullvadvpn-98c62cd60e88926b73168042a9bb46a95bcff92e.tar.xz
mullvadvpn-98c62cd60e88926b73168042a9bb46a95bcff92e.zip
Merge branch 'fix-date-interval-formatting'
-rw-r--r--ios/CHANGELOG.md2
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj10
-rw-r--r--ios/MullvadVPN/AccountExpiry.swift15
-rw-r--r--ios/MullvadVPN/CustomDateComponentsFormatting.swift55
-rw-r--r--ios/MullvadVPN/MullvadRpc.swift1
-rw-r--r--ios/MullvadVPN/WireguardKeysViewController.swift27
-rw-r--r--ios/MullvadVPNTests/CustomDateComponentsFormattingTests.swift59
7 files changed, 137 insertions, 32 deletions
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index b045099ed2..50627f748a 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -31,6 +31,8 @@ Line wrap the file at 100 chars. Th
parameter.
- Fix defect when manually regenerating the private key from Settings would automatically connect
the tunnel.
+- Properly format date intervals close to 1 day or less than 1 minute. Enforce intervals between 1
+ and 90 days to always be displayed in days quantity.
## [2020.2] - 2020-04-16
### Fixed
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index d76b758975..c97cc5cc50 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -77,6 +77,9 @@
5896AE80246ACE79005B36CB /* KeychainClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FAEE0024533A9C00CB0F5B /* KeychainClass.swift */; };
5896AE81246ACE81005B36CB /* KeychainMatchLimit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FAEDFC24533A5500CB0F5B /* KeychainMatchLimit.swift */; };
5896AE82246ACE84005B36CB /* KeychainReturn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FAEDFE24533A7000CB0F5B /* KeychainReturn.swift */; };
+ 5896AE84246D5889005B36CB /* CustomDateComponentsFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5896AE83246D5889005B36CB /* CustomDateComponentsFormatting.swift */; };
+ 5896AE86246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5896AE85246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift */; };
+ 5896AE88246D7FAF005B36CB /* CustomDateComponentsFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5896AE83246D5889005B36CB /* CustomDateComponentsFormatting.swift */; };
589AB4F7227B64450039131E /* BasicTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 589AB4F6227B64450039131E /* BasicTableViewCell.swift */; };
58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */; };
58A8BE81239FBE62006B74AC /* IPEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58561C98239A5D1500BD6B5E /* IPEndpoint.swift */; };
@@ -246,6 +249,8 @@
588AE72E2362001F009F9F2E /* MutuallyExclusive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutuallyExclusive.swift; sourceTree = "<group>"; };
58906DDF2445C7A5002F0673 /* NEProviderStopReason+Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEProviderStopReason+Debug.swift"; sourceTree = "<group>"; };
5894E725236B2801008A2793 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
+ 5896AE83246D5889005B36CB /* CustomDateComponentsFormatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDateComponentsFormatting.swift; sourceTree = "<group>"; };
+ 5896AE85246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDateComponentsFormattingTests.swift; sourceTree = "<group>"; };
589AB4F6227B64450039131E /* BasicTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicTableViewCell.swift; sourceTree = "<group>"; };
58A1AA8623F43901009F7EA6 /* Location.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Location.swift; sourceTree = "<group>"; };
58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionPanelView.swift; sourceTree = "<group>"; };
@@ -362,6 +367,7 @@
58B0A2A4238EE67E00BC001D /* Info.plist */,
584B26F3237434D00073B10E /* RelaySelectorTests.swift */,
5807E2C1243203D000F5FF30 /* StringTests.swift */,
+ 5896AE85246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift */,
);
path = MullvadVPNTests;
sourceTree = "<group>";
@@ -482,6 +488,7 @@
58B8743122B25A7600015324 /* WireguardAssociatedAddresses.swift */,
5877152F23981F7B001F8237 /* WireguardKeysViewController.swift */,
58C6B35322BB87C4003C19AD /* WireguardPrivateKey.swift */,
+ 5896AE83246D5889005B36CB /* CustomDateComponentsFormatting.swift */,
);
path = MullvadVPN;
sourceTree = "<group>";
@@ -764,6 +771,7 @@
5896AE80246ACE79005B36CB /* KeychainClass.swift in Sources */,
582AE3132440CA2700E6733A /* AccountTokenInput.swift in Sources */,
58B0A2AA238EE6A900BC001D /* RelaySelector.swift in Sources */,
+ 5896AE86246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift in Sources */,
5807E2C3243203E700F5FF30 /* String+Split.swift in Sources */,
5896AE82246ACE84005B36CB /* KeychainReturn.swift in Sources */,
58B0A2A8238EE68200BC001D /* RelaySelectorTests.swift in Sources */,
@@ -772,6 +780,7 @@
58B0A2AB238EE6BF00BC001D /* RelayList.swift in Sources */,
58B0A2AD238EE6EC00BC001D /* MullvadEndpoint.swift in Sources */,
58FAEDF4245088B300CB0F5B /* KeychainError.swift in Sources */,
+ 5896AE88246D7FAF005B36CB /* CustomDateComponentsFormatting.swift in Sources */,
582AE3122440CA0D00E6733A /* AccountTokenInputTests.swift in Sources */,
5896AE7E246ACE65005B36CB /* KeychainAttributes.swift in Sources */,
58B0A2A9238EE6A100BC001D /* RelayConstraints.swift in Sources */,
@@ -854,6 +863,7 @@
58A8BE8323A0F362006B74AC /* UIAlertController+Error.swift in Sources */,
58F840AF2464382C0044E708 /* KeychainItemRevision.swift in Sources */,
587425C12299833500CA2045 /* RootContainerViewController.swift in Sources */,
+ 5896AE84246D5889005B36CB /* CustomDateComponentsFormatting.swift in Sources */,
588AE72F2362001F009F9F2E /* MutuallyExclusive.swift in Sources */,
5888AD89227B18C40051EB06 /* RelayList.swift in Sources */,
587AD7C623421D7000E93A53 /* TunnelConfiguration.swift in Sources */,
diff --git a/ios/MullvadVPN/AccountExpiry.swift b/ios/MullvadVPN/AccountExpiry.swift
index 64f85eb0d2..4618e736ae 100644
--- a/ios/MullvadVPN/AccountExpiry.swift
+++ b/ios/MullvadVPN/AccountExpiry.swift
@@ -11,15 +11,6 @@ import Foundation
class AccountExpiry {
let date: Date
- private lazy var relativeFormatter: DateComponentsFormatter = {
- let formatter = DateComponentsFormatter()
- formatter.unitsStyle = .full
- formatter.allowedUnits = [.minute, .hour, .day, .month, .year]
- formatter.maximumUnitCount = 1
-
- return formatter
- }()
-
init(date: Date) {
self.date = date
}
@@ -29,7 +20,11 @@ class AccountExpiry {
}
var formattedRemainingTime: String? {
- return relativeFormatter.string(from: Date(), to: date)
+ return CustomDateComponentsFormatting.localizedString(
+ from: Date(),
+ to: date,
+ unitsStyle: .full
+ )
}
var formattedDate: String {
diff --git a/ios/MullvadVPN/CustomDateComponentsFormatting.swift b/ios/MullvadVPN/CustomDateComponentsFormatting.swift
new file mode 100644
index 0000000000..7ef731a999
--- /dev/null
+++ b/ios/MullvadVPN/CustomDateComponentsFormatting.swift
@@ -0,0 +1,55 @@
+//
+// CustomDateComponentsFormatting.swift
+// MullvadVPN
+//
+// Created by pronebird on 14/05/2020.
+// Copyright © 2020 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+enum CustomDateComponentsFormatting {}
+
+extension CustomDateComponentsFormatting {
+
+ /// Format a duration between the given dates returning a string that only contains one unit.
+ ///
+ /// The behaviour of that method differs from `DateComponentsFormatter`:
+ ///
+ /// 1. Intervals between 23h 30m - 23h 59m are rounded to 1 day to fix the iOS SDK bug which
+ /// results in the wrong output ("0 months").
+ /// 2. Intervals between 26 and 90 days are formatted in days quantity.
+ /// 3. Produce "Less than a minute" message for intervals below 1 minute.
+ ///
+ static func localizedString(
+ from start: Date,
+ to end: Date,
+ calendar: Calendar = Calendar.current,
+ unitsStyle: DateComponentsFormatter.UnitsStyle) -> String?
+ {
+ let formatter = DateComponentsFormatter()
+ formatter.calendar = calendar
+ formatter.unitsStyle = unitsStyle
+ formatter.allowedUnits = [.minute, .hour, .day, .month, .year]
+ formatter.maximumUnitCount = 1
+
+ let dateComponents = calendar
+ .dateComponents([.day, .hour, .minute, .second], from: start, to: end)
+
+ let days = dateComponents.day ?? 0
+ let hours = dateComponents.hour ?? 0
+ let minutes = dateComponents.minute ?? 0
+ let seconds = dateComponents.second ?? 0
+
+ if days == 0 && hours == 0 && minutes == 0 && seconds < 60 {
+ return NSLocalizedString("Less than a minute", comment: "")
+ } else if days == 0 && hours == 23 && minutes >= 30 {
+ return formatter.string(from: DateComponents(calendar: calendar, day: 1))
+ } else if days >= 1 && days <= 90 {
+ formatter.allowedUnits = [.day]
+ return formatter.string(from: dateComponents)
+ } else {
+ return formatter.string(from: start, to: end)
+ }
+ }
+}
diff --git a/ios/MullvadVPN/MullvadRpc.swift b/ios/MullvadVPN/MullvadRpc.swift
index 3db2c25bf6..8f07c6d8cb 100644
--- a/ios/MullvadVPN/MullvadRpc.swift
+++ b/ios/MullvadVPN/MullvadRpc.swift
@@ -26,7 +26,6 @@ struct SendAppStoreReceiptResponse: Codable {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.day, .hour]
formatter.unitsStyle = .full
- formatter.maximumUnitCount = 1
return formatter.string(from: timeAdded)
}
diff --git a/ios/MullvadVPN/WireguardKeysViewController.swift b/ios/MullvadVPN/WireguardKeysViewController.swift
index 513e6097db..5b01dd541c 100644
--- a/ios/MullvadVPN/WireguardKeysViewController.swift
+++ b/ios/MullvadVPN/WireguardKeysViewController.swift
@@ -75,15 +75,6 @@ class WireguardKeysViewController: UIViewController {
}
}
- private lazy var relativeFormatter: DateComponentsFormatter = {
- let formatter = DateComponentsFormatter()
- formatter.unitsStyle = .full
- formatter.allowedUnits = [.minute, .hour, .day, .month, .year]
- formatter.maximumUnitCount = 1
-
- return formatter
- }()
-
override func viewDidLoad() {
super.viewDidLoad()
@@ -153,18 +144,12 @@ class WireguardKeysViewController: UIViewController {
// MARK: - Private
private func formatKeyGenerationElapsedTime(with creationDate: Date) -> String? {
- let elapsedTime = Date().timeIntervalSince(creationDate)
-
- if elapsedTime >= 60 {
- if let formattedInterval = relativeFormatter.string(from: elapsedTime) {
- return String.localizedStringWithFormat(
- NSLocalizedString("%@ ago", comment: ""),
- formattedInterval)
- } else {
- return nil
- }
- } else {
- return NSLocalizedString("Less than a minute ago", comment: "")
+ return CustomDateComponentsFormatting.localizedString(
+ from: creationDate,
+ to: Date(),
+ unitsStyle: .full
+ ).map { (formattedInterval) -> String in
+ return String(format: NSLocalizedString("%@ ago", comment: ""), formattedInterval)
}
}
diff --git a/ios/MullvadVPNTests/CustomDateComponentsFormattingTests.swift b/ios/MullvadVPNTests/CustomDateComponentsFormattingTests.swift
new file mode 100644
index 0000000000..f2fdf623d0
--- /dev/null
+++ b/ios/MullvadVPNTests/CustomDateComponentsFormattingTests.swift
@@ -0,0 +1,59 @@
+//
+// CustomDateComponentsFormattingTests.swift
+// MullvadVPNTests
+//
+// Created by pronebird on 14/05/2020.
+// Copyright © 2020 Mullvad VPN AB. All rights reserved.
+//
+
+import XCTest
+
+class CustomDateComponentsFormattingTests: XCTestCase {
+
+ func testCloseToOneDayFormatting() throws {
+ var dateComponents = DateComponents()
+ dateComponents.hour = 23
+ dateComponents.minute = 30
+
+ let (startDate, endDate) = makeDateRange(addingComponents: dateComponents)
+
+ let result = CustomDateComponentsFormatting.localizedString(
+ from: startDate,
+ to: endDate,
+ calendar: self.calendar,
+ unitsStyle: .full
+ )
+
+ XCTAssertEqual(result, "1 day")
+ }
+
+ func testLessThanOneMinuteFormatting() throws {
+ var dateComponents = DateComponents()
+ dateComponents.second = 59
+
+ let (startDate, endDate) = makeDateRange(addingComponents: dateComponents)
+
+ let result = CustomDateComponentsFormatting.localizedString(
+ from: startDate,
+ to: endDate,
+ calendar: self.calendar,
+ unitsStyle: .full
+ )
+
+ XCTAssertEqual(result, "Less than a minute")
+ }
+
+ private func makeDateRange(addingComponents dateComponents: DateComponents) -> (Date, Date) {
+ let startDate = Date()
+ let endDate = Calendar.current.date(byAdding: dateComponents, to: startDate)!
+
+ return (startDate, endDate)
+ }
+
+ private var calendar: Calendar {
+ var calendar = Calendar(identifier: .gregorian)
+ calendar.locale = Locale(identifier: "en_US_POSIX")
+ return calendar
+ }
+
+}