简体   繁体   中英

How can I consistently get the count of or a list of all of the days between two dates in swift4?

I keep failing to get a consistent list or count of dates between two datetimes when the date is different in the GMT and the user's timezone and when the difference in datetimes is less than 24 hours (even with separate dates). I've googled and keep trying different approaches, but I cannot figure it out. It's even uglier when the user wants to use multiple time zones, but I think that I can solve that once I understand what I am doing wrong here.

In my application, I get dates from the user in several time zones, store them as strings, like: 2017-12-08 12:30:00 +0900

I then attempt to make an array of dates between the start and end date. However, I have not been able to get the array to include all of the actual dates.

First, I tried to expand an array directly between the first and last days: (This works in my project but not in a playground?? I don't know why.

func getMyDates(_ start:String,_ end: String) -> [Date]? {
   dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
   let fromDate = dateFormatter.date(from: start)
   let toDate = dateFormatter.date(from: end)
   var dates = [Date]()
   dates.append(fromDate!)
   dates.append(toDate!)
   print(dates)
   return dates
}
func getDays() -> [Date]? {
    guard let dates = getMyDates() else {return nil}
    print("getDays got \(dates) from getMyDates()")
    var userdates = dates
    let fromDate = dates[0]
    let toDate = dates[1]
    print("changed it to: \(fromDate ... toDate)")
    return fromDate ... toDate
}

This is working if the user's first and last dates are the same for their timezone and the GMT. For example, if the string entries are: 2017-11-08 12:30:00 +0900 and 2017-11-10 23:30:15 +0900, the output includes the three expected dates.

//getDays got [2017-11-08 03:30:00 +0000, 2017-11-10 14:30:15 +0000] from getMyDates()
//changed it to: [2017-11-08 03:30:00 +0000, 2017-11-09 03:30:00 +0000, 2017-11-10 03:30:00 +0000]

However, it fails when the time of day in the second datetime is earlier than the first, like: 2017-12-08 12:30:00 +0900 and 2017-12-09 09:30:15 +0900. In that case, I only get 1 day out:

//getDays got [2017-12-08 03:30:00 +0000, 2017-12-09 01:00:00 +0000] from getTripDates()
//changed it to: [2017-12-08 03:30:00 +0000]

Then, I tried calculating the number of days between them as suggested in this This question , but I get the same problem, as in the first one counts 2 days and the second counts 0 days. My plan was to take the number of days and use this approach .

I think it is a time zone issue, but I can't figure out how to solve it. I've tried setting and not setting the time zone to .current.

I cannot arbitrarily add a day because that will fail when the first datetime is earlier in the day than the second datetime ( like 2017-12-08 12:30:00 +0900 and 2017-12-09 15:30:15 +0900.

Using @malik's suggestion seems like it will work, but somehow it does not. We get the same behavior. If the second datetime TIME is later in the day it doesn't need an additional day (and somehow the seconds remainder does not work - gives too many days.

//in playground swift4
import UIKit

let start = "2017-12-08 12:30:00 +0900"
let end = "2017-12-10 13:30:00 +0900"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
var fromDate = dateFormatter.date(from: start)
let toDate = dateFormatter.date(from: end)
let cal = Calendar.autoupdatingCurrent
let seconds=cal.dateComponents([.second], from: fromDate!, to: toDate!)
var days = Int(seconds.second ?? 0)/(86400)
//days prints 2
//if (Int(seconds.second ?? 0) % (86400)) > 0 {
//    days += 1
//}
var newDates = [Date]()
newDates.append(fromDate!)
for _ in 1 ... days {
    let daysBetween = cal.date(byAdding: .day, value: 1, to: fromDate!)
    fromDate = daysBetween
    newDates.append(fromDate!)
}
//print(newDates)

outputs desired:

// [2017-12-08 03:30:00 +0000, 2017-12-09 03:30:00 +0000, 2017-12-10 03:30:00 +0000]

However: if I change the end date to:

let end = "2017-12-10 09:30:00 +0900"

the output is wrong:

//[2017-12-08 03:30:00 +0000, 2017-12-09 03:30:00 +0000]

Adding in the additional day for partial days helps in this case (uncommenting out the logic:)

if (Int(seconds.second ?? 0) % (86400)) > 0 {
    days += 1
}

yields correctly:

//[2017-12-08 03:30:00 +0000, 2017-12-09 03:30:00 +0000, 2017-12-10 03:30:00 +0000]

BUT: if I change end back while leaving the logic in, I get:

//[2017-12-08 03:30:00 +0000, 2017-12-09 03:30:00 +0000, 2017-12-10 03:30:00 +0000, 2017-12-11 03:30:00 +0000]

I thought it would be fine, but with the additional day, it messes up my later calls to see what else is going on those days because the extra day isn't in the original data.

So, maybe I need to just set up some comparisons of the HH:mm strings? It just doesn't make sense to me. I feel like I'm missing something obvious that would allow me to expand dates in the users' locale.

Using @malik's revised suggestion which lops off the hours and then calculates days works. Then, I can loop over the number of days to create an array.

It has nothing to do with time zone. You were on the right plan with the two links. The only problem was that the first link you provided calculated the number of days which doesn't take into account fine grain detail like hour, minutes seconds. Since you want it fine grained, all you need to do is replace .day with .second

extension Date {
    func secondsBetweenDateTimes(toDateTime: Date) -> Int {
        let components = Calendar.current.dateComponents([.second], from: self, to: toDateTime)
        return components.second ?? 0
    }
}

This will return you total number of seconds between your two date times. Next, you need to divide the result from this method by 86400 (number of seconds in a day) to get the number of days. Then use the remainder, divide it by 24 to get the remaining hours and so on and so forth. This way, you can get the exact difference between two DateTimes.

UPDATE

Your if check is a bit faulty. I see what you are trying to do here but that is not the correct if check . Right now what you are telling your code to do is pretty much add an extra day if the number of seconds cannot be divided into whole days. As a result, you will see the behaviour that you are experiencing right now. In my original answer, I said to use the remainder not the whole value to decide whether you want to add a day or not. Anyways, since you are not using the hours, minutes, seconds as your deciding factor, you can do it another way. Just strip out the hour minutes and seconds before calculation and calculate the number of days as below.

let start = "2017-12-08 12:30:00 +0900"
let end = "2017-12-10 13:30:00 +0900"
let startDate = start.substring(to: start.index(start.startIndex, offsetBy: 10))
let endDate = end.substring(to: end.index(end.startIndex, offsetBy: 10))
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
var fromDate = dateFormatter.date(from: startDate)
let toDate = dateFormatter.date(from: endDate)
let cal = Calendar.autoupdatingCurrent
let days=cal.dateComponents([.day], from: fromDate!, to: toDate!)
print(days.day)    //Prints 2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM