diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2020-05-15 14:48:35 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2020-05-15 14:48:35 +0200 |
| commit | 98c62cd60e88926b73168042a9bb46a95bcff92e (patch) | |
| tree | c5b74e5c86f5c2df8620117a318c04262256cfc5 | |
| parent | ffa654e0c0bb7e8be53adf30a4edf40274d6ff60 (diff) | |
| parent | 83aefa4348960d1b74e14ad14bb6ee1a9f2a81cb (diff) | |
| download | mullvadvpn-98c62cd60e88926b73168042a9bb46a95bcff92e.tar.xz mullvadvpn-98c62cd60e88926b73168042a9bb46a95bcff92e.zip | |
Merge branch 'fix-date-interval-formatting'
| -rw-r--r-- | ios/CHANGELOG.md | 2 | ||||
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 10 | ||||
| -rw-r--r-- | ios/MullvadVPN/AccountExpiry.swift | 15 | ||||
| -rw-r--r-- | ios/MullvadVPN/CustomDateComponentsFormatting.swift | 55 | ||||
| -rw-r--r-- | ios/MullvadVPN/MullvadRpc.swift | 1 | ||||
| -rw-r--r-- | ios/MullvadVPN/WireguardKeysViewController.swift | 27 | ||||
| -rw-r--r-- | ios/MullvadVPNTests/CustomDateComponentsFormattingTests.swift | 59 |
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 + } + +} |
