Get percentage between two dates as a float in Swift - swift

So I'm currently trying to add a feature that given two dates, will tell me how far through I am.
TLDR:
So, let's say I have a date that is July 1st, 2017 20:00:00 and another that is July 2nd, 2017 22:00:00 and today is July 2nd, 08:00:00, then I will get that I am 46.17% of the way through.
The way I tried to do this is using a simple formula:
progress = (current time - start time) / (end time - start time)
but when put into code, I can subtract two dates, and get the DateComponent difference, but I cannot divide two DateComponent's. Here is my code set up, with an extension t
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let startDate = formatter.date(from: "2017-07-01 20:00:00")
let endDate = formatter.date(from: "2017-07-02 22:00:00")
let currentDate = formatter.date(from: "2017-07-01 08:00:00")
let progress = (currentDate - startDate) / (endDate - startDate)
extension Date {
static func - (date1: Date, date2: Date) -> DateComponents {
let calender:Calendar = Calendar.current
return calender.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date1, to: date2)
}
}
There has to be a way around dividing two dates. I can't think of it though, everything I find online (using different languages) has required division between two dates.
I tried to convert everything to seconds and just divide that, but I didn't know what to do with the seconds to convert them back to a DateComponent because there might be a 5000 second difference. Any help appreciated!

You just need to turn the dates into numbers, because then you can add and subtract them.
You can use the timeIntervalSince1970 to turn the date into numbers, then you can use your formula:
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let startDate = formatter.date(from: "2017-07-01 20:00:00")!.timeIntervalSince1970
let endDate = formatter.date(from: "2017-07-02 22:00:00")!.timeIntervalSince1970
let currentDate = formatter.date(from: "2017-07-02 08:00:00")!.timeIntervalSince1970
let percentage = (currentDate - startDate) / (endDate - startDate)
Alternatively, use timeIntervalSince(_:):
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let startDate = formatter.date(from: "2017-07-01 20:00:00")!
let duration = formatter.date(from: "2017-07-02 22:00:00")!.timeIntervalSince(startDate)
let elapsed = formatter.date(from: "2017-07-02 08:00:00")!.timeIntervalSince(startDate)
let percentage = elapsed / duration
I think this way is better because you get less maths :).

What you are looking for is Date().timeIntervalSinceReferenceDate:
let currentInterval = currentDate.timeIntervalSinceReferenceDate
let startInterval = startDate.timeIntervalSinceReferenceDate
let endInterval = endDate.timeIntervalSinceReferenceDate
let progress = ((currentInterval - startInterval) / (endInterval - startInterval)) * 100
Here is apples documentation on it. Basically it returns the amount of seconds that have passed since the reference date of January 1, 2001 UTC.

Do the exact same calculation, just convert startdate, enddate, and currentdate into milliseconds using yourDate.timeIntervalSince1970
That should give you a decimal between 0 and 1, and just multiply by 100 for the percentage

Related

Is there a more efficient way to combine a date and time in swift 5? [duplicate]

This question already has an answer here:
How to combine two strings (date & time) into a new date in Swift 3
(1 answer)
Closed 2 years ago.
I have function which receives 2 strings the first is a date "y-M-d" and the second a time "HH:mm". I then combine them using the following code.
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "y-M-d"
let date = dateFormatter.date(from: dateStr)!
dateFormatter.dateFormat = "HH:mm"
let time = dateFormatter.date(from: timeStr)!
let calendar = Calendar.init(identifier: .iso8601)
let components = NSDateComponents()
components.day = calendar.component(.day, from: date) //split from date above
components.month = calendar.component(.month, from: date)
components.year = calendar.component(.year, from: date)
components.hour = calendar.component(.hour, from: time) //split from time above
components.minute = calendar.component(.minute, from: time)
let newDate = calendar.date(from: components as DateComponents)
The code all works fine and is doing what I want it to. However, I was wondering if anyone can suggest a slicker way of doing it, using less lines of code?
You can join the date & time strings and parse them in one go:
let dateStr = "2020-03-12"
let timeStr = "15:35"
let df = DateFormatter()
df.dateFormat = "y-M-d HH:mm"
let date = df.date(from: dateStr + " " + timeStr)
// prints: 2020-03-12 13:35:00 +0000 (my machine is GMT+2)
Edit: As Leo Dabus said in the comments, a more appropriate format for the provided strings should be yyyy-MM-dd HH:mm (just kept the provided format from the question). The spirit of the answer was not to propose a format but to provide a way to avoid parsing date/time separately.
Simply get a combined String using date and time. Then use that String to get the Date instance from it.
func getDate(d: String, t: String) -> Date? {
let str = d + t
let formatter = DateFormatter()
formatter.dateFormat = "y-M-dHH:mm"
let date = formatter.date(from: str)
return date
}

Swift get the remaining seconds of time on target string date

The current date time today was May 9, 2020 10:03 PM, and I have a target string date with the value of 2020-05-09 22:07:30 with the format of yyyy-MM-dd HH:mm:ss.
How can I get the remaining date from that 2 date and print the value with the string of 04:30 as the range of those 2 dates are 4 minutes and 30 seconds
What I can only do is convert the milliseconds to time format like
func msToTime(ms: Int) {
let seconds = ms % 60
let minutes = ms / 60000
return String(format: "%0.2d:%0.2d",minutes,seconds)
}
Output 04:30
But I don't know how to get the range of milliseconds from today's date time to target's date time.
Or if there's any other easier way to do it?
You can use Calendar and DateComponents to easily calculate differences between dates in whatever units you desire. For example, this gets the difference in minutes and seconds:
let dateformatter = DateFormatter()
dateformatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = dateformatter.date(from: "2020-05-09 22:07:30")!
let now = Date()
let components = Calendar.current.dateComponents([.minute, .second], from: now, to: date)
print("difference: \(components.minute!):\(components.second!)")
A straightforward way, with no calculation of any kind needed:
let d1 = Date()
let s = "2020-05-09 22:07:30"
let f = DateFormatter()
f.dateFormat = "yyyy-MM-dd HH:mm:ss"
f.timeZone = TimeZone.current // or whatever
if let d2 = f.date(from: s) {
let f = DateComponentsFormatter()
f.unitsStyle = .positional
let result = f.string(from: d1, to: d2)
}
If you don't like the resulting string format of result, you can eliminate pieces of it. — However, note that this works only because no full days are involved. It isn't clear what the range of possible inputs might be or what the desired output would be if the second date were three months into the future, for example.

TimeDifference between minimum date and maximum date

i am completely new in iPhone app development. i am trying to find difference between min_date and max_date in hours. and wants save its value in textfield. Kindly Provide me complete code to find out difference between both of dates. e.g. if min_date: 12/07/1989, 12:00 am and max_date: 13/07/1989,12:00 am , then total hours will be 24 hours. Please provide me code in swift 3.0.
First, use timeIntervalSince to get the difference in seconds:
let timeInterval = max_date.timeIntervalSince(min_date)
Then you can do some maths to calculate the number of hours
let hours = timeInterval / 60 / 60
You can choose to floor or ceiling this number, depending on your requirements.
let previousDate = ...
let now = Date()
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.allowedUnits = [.month, .day, .hour, .minute, .second]
formatter.maximumUnitCount = 2 // often, you don't care about seconds
if the elapsed time is in months, so you'll set max unit to whatever is
appropriate in your case
let string = formatter.string(from: previousDate, to: now)
let minDate = Date() // your min date
let maxDate = Date() // your max date
let components = Calendar.current.dateComponents([.hour], from: minDate, to: maxDate)
It's the most flexible method for search difference between two dates. You can use other components to search for months, days, years etc.

Swift: get the offset value from a date object

as simple as it sounds, but it is hard to find my exact question in google.
I'm trying to ignore the UTC printed out value. I receive multiple dates, this one here is just an example: (it could be +0900, -0200, etc...)
"2017-05-01T12:30:00-0700"
once I apply it to a value using these lines:
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssxxxxx"
if let result = formatter.date(from: time) {print result}
the value of the dateTimeResult prints:
2017-05-01 19:30:00 UTC
Using swift date objects, how do I slice out the part "-0700", multiply the -7 or +7 (this example is negative) by minutes by seconds. I'll save that total as int in DB (I need it for categorizing the different timezones later). Then applying that total to the incoming date input using this line:
let output = Calendar.current.date(byAdding: .second, value: totalSecs, to: result)
The goal is to end up with this date:
"2017-05-01 12:30:00"
I already have a solution using string manipulation, but I don't think that is the ideal solution. If it must be done by string, how do you do it?
If I understand you correctly you want only the date and time portion ignoring always the time zone information.
In this case strip the time zone from the date string with regular expression
let dateString = "2017-05-01T12:30:00-0700"
let dateStringIgnoringTimeZone = dateString.replacingOccurrences(of: "[+-]\\d{4}", with: "", options: .regularExpression)
print(dateStringIgnoringTimeZone) // "2017-05-01T12:30:00"
let dateFormatter = DateFormatter()
dateFormatter.calendar = Calendar(identifier: .iso8601)
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let date = dateFormatter.date(from: dateStringIgnoringTimeZone)!
I think you should keep the date as it is and then just use DateFormatter to display the time at that timezone
let time = "2017-05-01T12:30:00-0700"
let dateFormatter = DateFormatter()
dateFormatter.calendar = Calendar(identifier: .iso8601)
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssxxxxx"
if let result = dateFormatter.date(from: time) {
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
print(dateFormatter.string(from: result)) // "2017-05-01 16:30:00 (corresponding time at my location GMT-3)
// to display it at -0700 just set the formatter timaZone
dateFormatter.timeZone = TimeZone(secondsFromGMT: -3600 * 7)
print(dateFormatter.string(from: result)) // "2017-05-01 12:30:00\n"
}
To get the timezone offset from the string:
let hours = Int(time.suffix(5).prefix(3)) ?? 0
let minutes = Int(time.suffix(2)) ?? 0
let offset = hours * 3600 + minutes * 60
print(offset) // -25200

NSDate startOfDay is 4AM?

I'm trying to write a loop for every 10 minutes of a given 24 hour day, starting at midnight and ending at ten minutes before midnight. So I tried this...
let x = Calendar.current.component(.year, from: Date())
let dateFormatter = DateFormatter()
dateFormatter.dateFormat="dd-MM-yyyy"
let june = dateFormatter.date(from: "21-06-" + String(x))
The result for june is "2017-06-21 04:00:00 UTC". Now technically this is correct, my local day will be 4 AM UTZ, but the code I'm passing this into, from the Astronomical Almanac, already handles local/global conversion.
So then I tried this:
var UTZCal = Calendar.current
UTZCal.timeZone = TimeZone(abbreviation: "GMT")!
let x = UTZCal.component(.year, from: Date())
let dateFormatter = DateFormatter()
dateFormatter.dateFormat="dd-MM-yyyy"
dateFormatter.calendar = UTZCal
let june = dateFormatter.date(from: "21-06-" + String(x))
This produced the exact same result. What am I missing?
It seems that the date formatter does not use the timezone of the
assigned calendar, and adding
dateFormatter.timeZone = UTZCal.timeZone
to your code makes it produce the expected result. But note that you
can simplify the calculation to
var utzCal = Calendar(identifier: .gregorian)
utzCal.timeZone = TimeZone(secondsFromGMT: 0)!
let year = utzCal.component(.year, from: Date())
let june = DateComponents(calendar: utzCal, year: year, month: 6, day: 21).date!
print(june) // 2017-06-21 00:00:00 +0000