Adding Time Interval. e.g hours, minutes and seconds - swift

I am getting time from the response in hours, minutes and seconds like "01:32:34" of multiple objects. I have saved it in a custom date object, where i am saving the date value and the string value, there are a large number of records so i am saving it in my local db and upon retrieving i get the date value in the format 1999-12-31 19:01:04 +0000 whereas my string value which i am getting from response as well is 19:01:04. now i would like to add all of these values to return a string e.g 1:15:16, 00:15:02, 00:45:27 should return 2 hours 15 minutes 45 seconds. I have explored Calendar.Components.byAdding but the methods there only let me add one single component, it doesn't receive an array and return Date. Is there any swifty way to achieve this, I want to achieve this via an elegant and proper method, i could think of a few fixes but they don't seem appropriate.

I’m going to assume that “01:32:34” represents an elapsed time of 5,554 seconds, not 1:32am. So, I’d convert it to a TimeInterval, not a Date:
func timeInterval(from string: String) -> TimeInterval? {
let components = string.components(separatedBy: ":").map { Double($0) }
guard
components.count == 3,
let hours = components[0],
let minutes = components[1],
let seconds = components[2]
else { return nil }
return ((hours * 60) + minutes) * 60 + seconds
}
You can chose to store either the original “01:32:34” string or this value, 5,554.0.
Anyway, then adding these numeric time intervals is trivial addition. And to display a resulting TimeInterval, you’d use a DateComponentsFormatter, e.g.
let timeIntervalFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.hour, .minute, .second]
return formatter
}()
func totalElapsed(_ strings: [String]) -> String? {
let total = strings.reduce(TimeInterval.zero) { sum, string in
sum + (timeInterval(from: string) ?? 0)
}
return timeIntervalFormatter.string(from: total)
}
let strings = ["1:15:16", "00:15:02", "00:45:27"]
let result = totalElapsed(strings)
02:15:45
Or if you want more of a (localized) natural language representation, use unitsStyle of .full:
2 hours, 15 minutes, 45 seconds
This approach (using TimeInterval, not Date) has the virtue that it also can represent intervals that exceed 24 hours.

Related

Swift Coding With Milliseconds - How To Calculate?

Just a question in regards to the times in Swift. I have a timestamp, which I want to validate whether it is 24 hours past the current time at the moment. I tried a few options and this is where my code is at the moment however I am stuck. Any tips will be highly appreciated!
let timeStampsOfUser = timeStampsOfUser.value as Any as! Double
print(timeStampsOfUser)
let timeInMiliSecDate = Date()
let timeInMiliSec = Double (timeInMiliSecDate.timeIntervalSince1970 * 1000)
print(timeInMiliSec)
if timeInMiliSec - timeStampsOfUser > 86400000 {
// how do I get the total count of timestamps which have been more than 24 hours and vice versa?
}
Update: However, how do I found the count? As in, how many of the timestamps are more and less than 24 hours? Thanks
let timeStampsOfUser = timeStampsOfUser.value as Any as! Double
print(timeStampsOfUser)
let timeInMiliSecDate = Date()
let timeInMiliSec = Double (timeInMiliSecDate.timeIntervalSince1970 * 1000)
print(timeInMiliSec)
let timeDone = timeInMiliSec - timeStampsOfUser > 86400000
if timeDone == true {
print("more than 24 hours")
} else {
print("less than 24 hours")
}
86400000 is the wrong approach. Never use 86400-math. Days could have 23 or 25 hours.
Create an extension of Date with 2 functions, an init method which takes a timestamp in milliseconds and a function to check if a given date is in the last 24 hours (actually the difference is less than one day). Calendar can do this with DateComponents pretty reliably.
extension Date {
init(timestampInMilliseconds: Double) {
self.init(timeIntervalSince1970: timestampInMilliseconds / 1000.0)
}
func isDateInLast24Hours() -> Bool {
Calendar.current.dateComponents([.day], from: self, to: .now).day! == 0
}
}
And use it
let timeStamp = 1665584000000.0 // 12. Oct 2022, 16:13
let date = Date(timestampInMilliseconds: timeStamp)
let isInLast24Hours = date.isDateInLast24Hours()
To check multiple timestamps use a loop or map

Find difference between just the time of two dates in seconds

I have 2 dates. I don't care about the date portion, just the time.
How can I compare 2 dates and get the timeinterval between 2 dates?
Should I set the dates to 01-01-2000 and leave the time alone to compare?
Use DateComponents and get the hour, minute, and second of the two dates. At this point you have to assume a full 24 hour, 86400 seconds per day. There's no need to worry about daylight saving or leap seconds or anything since you are doing date independent calculations.
Convert the hours, minutes, and seconds into total seconds of the day for the two dates. Then simply subtract the two totals and you have the difference.
Here's a helpful Date extension:
extension Date {
func secondsSinceMidnight() -> TimeInterval {
let comps = Calendar.current.dateComponents([.hour,.minute,.second], from: self)
return TimeInterval(comps.hour! * 3600 + comps.minute! * 60 + comps.second!)
}
func timeDifference(to date: Date) -> TimeInterval {
return date.secondsSinceMidnight() - self.secondsSinceMidnight()
}
}
Call timeDifference(to:) using your two dates and you will get the difference in seconds ignoring the date portion of the dates.
A negative result means that the to date is closer to midnight.
This is an alternative to rmaddy's solution completely based on DateComponents
extension Date {
func timeComponents() -> DateComponents {
return Calendar.current.dateComponents([.hour,.minute,.second], from: self)
}
func timeDifference(to date: Date) -> Int {
return Calendar.current.dateComponents([.second], from: date.timeComponents(), to: self.timeComponents()).second!
}
}
If you have two dates you can use the method timeIntervalSince(Date).
For instance:
func calculateElapsedTime(from someTime: Date) -> TimeInterval {
let currentTime = Date()
var elapsedTime = currentTime.timeIntervalSince(someTime)
return elapsedTime
}
If you only want to consider the time difference between the two dates, you first have to normalize the date. This can be done in the following cumbersome way:
let currentDate = Date()
let anotherDate = Date(timeInterval: 60, since: currentDate)
let formatter = DateFormatter()
formatter.timeStyle = .short
let currentTime = formatter.string(from: currentDate)
let anotherTime = formatter.string(from: anotherDate)
let currentIntervalTime = formatter.date(from: currentTime)
let anotherIntervalTime = formatter.date(from: anotherTime)
let elapsedTime = anotherIntervalTime?.timeIntervalSince(currentIntervalTime!)

Round time to nearest thirty seconds

I have an app that presents data that expires every 30 seconds (precisely, at h/m/s 11:30:00, 11:30:30, 11:31:00, etc).
I can get the current time, but I am unsure on how to calculate the time between now and the nearest thirty seconds.
Anything I've found is in Objective-C, and I've been unable to convert it.
Here's what I tried:
func nearestThirtySeconds() -> Date? {
var components = NSCalendar.current.dateComponents([.second], from: self)
let second = components.second ?? 30
components.second = second >= 30 ? 60 - second : -second
return Calendar.current.date(byAdding: components, to: self)
}
But this returns the nearest minute (I think, it always returns a definite minute)
Any ideas?
You can round the seconds to the nearest multiple of 30,
and then add the difference between the rounded and the original
value to the date:
extension Date {
func nearestThirtySeconds() -> Date {
let cal = Calendar.current
let seconds = cal.component(.second, from: self)
// Compute nearest multiple of 30:
let roundedSeconds = lrint(Double(seconds) / 30) * 30
return cal.date(byAdding: .second, value: roundedSeconds - seconds, to: self)!
}
}
That should be good enough to display the rounded time, however it
is not exact: A Date includes also fractional seconds, so
for example "11:30:10.123" would become "11:30:00.123" and not "11:30:00.000". Here is another approach which solves that problem:
extension Date {
func nearestThirtySeconds() -> Date {
let cal = Calendar.current
let startOfMinute = cal.dateInterval(of: .minute, for: self)!.start
var seconds = self.timeIntervalSince(startOfMinute)
seconds = (seconds / 30).rounded() * 30
return startOfMinute.addingTimeInterval(seconds)
}
}
Now seconds is the time interval since the start of the current minute
(including fractional seconds). That interval is rounded to the nearest
multiple of 30 and added to the start of the minute.
I used the answer by Martin R to write a more generic version to round by any time period.
Answer is outdated and only works with time, check gist for the latest version.
https://gist.github.com/casperzandbergenyaacomm/83c6a585073fd7da2e1fbb97c9bcd38a
extension Date {
func rounded(on amount: Int, _ component: Calendar.Component) -> Date {
let cal = Calendar.current
let value = cal.component(component, from: self)
// Compute nearest multiple of amount:
let roundedValue = lrint(Double(value) / Double(amount)) * amount
let newDate = cal.date(byAdding: component, value: roundedValue - value, to: self)!
return newDate.floorAllComponents(before: component)
}
func floorAllComponents(before component: Calendar.Component) -> Date {
// All components to round ordered by length
let components = [Calendar.Component.year, .month, .day, .hour, .minute, .second, .nanosecond]
guard let index = components.index(of: component) else {
fatalError("Wrong component")
}
let cal = Calendar.current
var date = self
components.suffix(from: index + 1).forEach { roundComponent in
let value = cal.component(roundComponent, from: date) * -1
date = cal.date(byAdding: roundComponent, value: value, to: date)!
}
return date
}
}
To round to x minutes you need to also floor the seconds so this also contains the floor method I wrote.
How to use:
let date: Date = Date() // 10:16:34
let roundedDate0 = date.rounded(on: 30, .second) // 10:16:30
let roundedDate1 = date.rounded(on: 15, .minute) // 10:15:00
let roundedDate2 = date.rounded(on: 1, .hour) // 10:00:00
There's a Cocoapod called 'SwiftDate' that is great for date formatting and manipulation that can applied to your Date() instance.
Example of how to use the date rounding method:
let date = Date() // 2018-10-01 23:05:29
date.dateRoundedAt(at: .toFloor5Mins) // 2018-10-01 23:05:00
date.dateRoundedAt(at: .toCeil5Mins) // 2018-10-01 23:10:00
date.dateRoundedAt(at: .toFloorMins(1)) // 2018-10-01 23:05:00
date.dateRoundedAt(at: .toCeilMins(1)) // 2018-10-01 23:06:00
(For reference, check out the documentation at https://cocoapods.org/pods/SwiftDate)
let now = Date()
var timeInterval = now.timeIntervalSinceReferenceDate
timeInterval += 30 - timeInterval.truncatingRemainder(dividingBy: 30)
let rounded = Date(timeIntervalSinceReferenceDate: timeInterval)
print("\(now) rounded to nearest 30 seconds is \(rounded)")

Change Time Conversion Function to Swift Extension

I have a function that converts minutes to either a decimal or a HH:MM string based on a user preference in NSUserDefaults.
For example, 90 minutes would be either 1.5 or 1:30.
Here's my function:
func decimalOrHHMM(value:Int) -> String{
let totalMinutes = Double(value)
let defaults = UserDefaults.standard
if defaults.string(forKey: "displayTotalsAs") == "hhmm"{
//HH:MM
let hours = floor(totalMinutes/60)
let minutes = totalMinutes.truncatingRemainder(dividingBy: 60) //This gives us the remainder
let hrs = String(format: "%.0f", hours) //Remove tenths place
var mins = ""
if minutes < 10{
//Prepend 0
mins = String(format: "0%.0f", minutes)
}else{
mins = String(format: "%.0f", minutes)
}
return "\(hrs):\(mins)"
}else{
//Decimal
return String(format: "%.1f", totalMinutes/60)
}
}
This works great, but I'm wondering if this can be converted to an NSNumberFormatter Swift extension somehow. I'm having trouble knowing what I need to override in order to do all the converting.
Any idea how I can make this an extension?
This is the basic structure for a Swift extension, you could use a function instead of a computed property, but since your original function takes an Int value, a computed property makes sense.
extension Int {
var decimalOrHHMM: String {
return "\(self)" // do your actual conversion here
}
}
You can also choose to extend NSNumberFormatter.

Convert Date to Integer in Swift

I am updating some of my old Swift 2 answers to Swift 3. My answer to this question, though, is not easy to update since the question specifically asks for NSDate and not Date. So I am creating a new version of that question that I can update my answer for.
Question
If I start with a Date instance like this
let someDate = Date()
how would I convert that to an integer?
Related but different
These questions are asking different things:
Swift convert unix time to date and time
Converting Date Components (Integer) to String
Convert Date String to Int Swift
Date to Int
// using current date and time as an example
let someDate = Date()
// convert Date to TimeInterval (typealias for Double)
let timeInterval = someDate.timeIntervalSince1970
// convert to Integer
let myInt = Int(timeInterval)
Doing the Double to Int conversion causes the milliseconds to be lost. If you need the milliseconds then multiply by 1000 before converting to Int.
Int to Date
Including the reverse for completeness.
// convert Int to TimeInterval (typealias for Double)
let timeInterval = TimeInterval(myInt)
// create NSDate from Double (NSTimeInterval)
let myNSDate = Date(timeIntervalSince1970: timeInterval)
I could have also used `timeIntervalSinceReferenceDate` instead of `timeIntervalSince1970` as long as I was consistent. This is assuming that the time interval is in seconds. Note that Java uses milliseconds.
Note
For the old Swift 2 syntax with NSDate, see this answer.
If you are looking for timestamp with 10 Digit seconds since 1970 for API call then, below is code:
Just 1 line code for Swift 4/ Swift 5
let timeStamp = UInt64(Date().timeIntervalSince1970)
print(timeStamp) <-- prints current time stamp
1587473264
let timeStamp = UInt64((Date().timeIntervalSince1970) * 1000) // will give 13 digit timestamp in milli seconds
timeIntervalSince1970 is a relevant start time, convenient and provided by Apple.
If u want the int value to be smaller, u could choose the relevant start time you like
extension Date{
var intVal: Int?{
if let d = Date.coordinate{
let inteval = Date().timeIntervalSince(d)
return Int(inteval)
}
return nil
}
// today's time is close to `2020-04-17 05:06:06`
static let coordinate: Date? = {
let dateFormatCoordinate = DateFormatter()
dateFormatCoordinate.dateFormat = "yyyy-MM-dd HH:mm:ss"
if let d = dateFormatCoordinate.date(from: "2020-04-17 05:06:06") {
return d
}
return nil
}()
}
extension Int{
var dateVal: Date?{
// convert Int to Double
let interval = Double(self)
if let d = Date.coordinate{
return Date(timeInterval: interval, since: d)
}
return nil
}
}
Use like this:
let d = Date()
print(d)
// date to integer, you need to unwrap the optional
print(d.intVal)
// integer to date
print(d.intVal?.dateVal)