diff --git a/ScienceJournal/Extensions/DateComponentsFormatter+ScienceJournal.swift b/ScienceJournal/Extensions/DateComponentsFormatter+ScienceJournal.swift index 3ee35a0..041241b 100644 --- a/ScienceJournal/Extensions/DateComponentsFormatter+ScienceJournal.swift +++ b/ScienceJournal/Extensions/DateComponentsFormatter+ScienceJournal.swift @@ -27,4 +27,23 @@ extension DateComponentsFormatter { return formatter } + /// Returns an accessible string based on the given time interval, appending any available + /// fractional seconds as milliseconds using a NSCalendar.Unit.spellOut unitsStyle. + /// + /// - Parameters: + /// - ti: The time interval, measured in seconds. The value must be a finite number. + /// Negative numbers are treated as positive numbers when creating the string. + /// - fractional: The fractional seconds available on the time interval. + /// If this equals 0, milliseconds will not be appended to the string. + /// - Returns: A formatted string representing the specified time interval. + func string(from ti: TimeInterval, appending fractional: Int64) -> String { + guard let duration = TimeInterval.accessibleIntervalFormatter.string(from: ti) else { + return "" + } + if fractional > 0 { + return duration + " \(fractional) " + String.timeIntervalMilliseconds + } + return duration + } + } diff --git a/ScienceJournal/Extensions/TimeInterval+ScienceJournal.swift b/ScienceJournal/Extensions/TimeInterval+ScienceJournal.swift index e002dda..f8c784d 100644 --- a/ScienceJournal/Extensions/TimeInterval+ScienceJournal.swift +++ b/ScienceJournal/Extensions/TimeInterval+ScienceJournal.swift @@ -35,10 +35,19 @@ extension TimeInterval { } /// Returns the time interval as an accessible string in component format with hours, minutes and - /// seconds without abbreviation. - /// Example: "One hour 46 minutes 11 seconds" + /// seconds without abbreviation. When there are fractional seconds present on the time interval, + /// this will include them as milliseconds. + /// Example: "One hour 46 minutes 11 seconds" and "One second 250 milliseconds". var accessibleDurationString: String { - return TimeInterval.accessibleIntervalFormatter.string(from: self) ?? "" + return TimeInterval.accessibleIntervalFormatter.string(from: self, appending: fractional) + } + + /// The fractional seconds available on the time interval. + /// Example: 250 when the time interval equals 1.250, and 0 when the time interval equals 3.0. + private var fractional: Int64 { + let remainder = truncatingRemainder(dividingBy: 1) + guard remainder > 0.0 else { return 0 } + return Int64(remainder * 1000) } } diff --git a/ScienceJournal/Resources/Strings.bundle/en.lproj/Localizable.strings b/ScienceJournal/Resources/Strings.bundle/en.lproj/Localizable.strings index ff8fbcb..f8fa2fd 100644 --- a/ScienceJournal/Resources/Strings.bundle/en.lproj/Localizable.strings +++ b/ScienceJournal/Resources/Strings.bundle/en.lproj/Localizable.strings @@ -1381,6 +1381,9 @@ /* Accessibility hint for a graphic representing an in-progress recording, telling a user that interacting with it will show the observe tab [CHAR_LIMIT=NONE] */ "throbber_content_details" = "Double tap to switch to the observe tab in the tool drawer"; +/* Name for milliseconds in a TimeInterval object */ +"time_interval_milliseconds" = "milliseconds"; + /* Title for activity that handles sensor settings [CHAR_LIMIT=40] */ "title_activity_sensor_settings" = "Sensor settings"; diff --git a/ScienceJournal/Strings/ScienceJournalStrings.swift b/ScienceJournal/Strings/ScienceJournalStrings.swift index 9967768..66e73ce 100644 --- a/ScienceJournal/Strings/ScienceJournalStrings.swift +++ b/ScienceJournal/Strings/ScienceJournalStrings.swift @@ -485,6 +485,7 @@ extension String { static public var textNoteAddedContentDescription: String { return "text_note_added_content_description".localized } static public var throbberContentDescription: String { return "throbber_content_description".localized } static public var throbberContentDetails: String { return "throbber_content_details".localized } + static public var timeIntervalMilliseconds: String { return "time_interval_milliseconds".localized } static public var titleActivitySensorSettings: String { return "title_activity_sensor_settings".localized } static public var toExperimentContentDescription: String { return "to_experiment_content_description".localized } static public var toRunContentDescription: String { return "to_run_content_description".localized } diff --git a/ScienceJournalTests/Extensions/TimeInterval+ScienceJournalTest.swift b/ScienceJournalTests/Extensions/TimeInterval+ScienceJournalTest.swift index c25254b..c3c0826 100644 --- a/ScienceJournalTests/Extensions/TimeInterval+ScienceJournalTest.swift +++ b/ScienceJournalTests/Extensions/TimeInterval+ScienceJournalTest.swift @@ -30,4 +30,9 @@ class TimeInterval_ScienceJournalTest: XCTestCase { XCTAssertEqual(interval.accessibleDurationString, "one hour, three minutes, twelve seconds") } + func testAccessibleDurationStringWithFractionalSeconds() { + let interval: TimeInterval = 1.250 + XCTAssertEqual(interval.accessibleDurationString, "one second 250 milliseconds") + } + }