1

I'm trying to calculate the number of weeks and days that a pet is alive. I'm using SwiftDate for that. So I let the user select a birthdate and compare it to the current date.

I don't want to compare the time... so both the birthdate and current date should have the same time.

My code is:

let greg = Calendar(identifier: .gregorian)
var date1 = birthday // from datepicker
var date2 = Date()
var components1 = greg.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date1)
var components2 = greg.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date2)
components1.hour = 15
components1.minute = 0
components1.second = 0
components2.hour = 15
components2.minute = 0
components2.second = 0
date1 = greg.date(from: components1)!
date2 = greg.date(from: components2)!

let daysAlive = (date2 - date1).in(.day)
let weeksAlive = Int(weeksAlive! / 7)
let restDays = daysAlive! - (weeksAlive * 7)
let aLive = "Age: \(weeksAlive) wk, \(restDays) d (\(daysAlive!) d)"

Output is "Age: 28 wk, 3 d (199 d)"

The code works... but how can I refactor it? And is there an easier way to set the 'same time' for two given dates?

arakweker
  • 1,535
  • 4
  • 18
  • 40
  • I get the following output: 2017-06-24 22:00:00 +0000 2018-01-09 23:00:00 +0000 "[SwiftDate] Using .in() to extract calendar specific components without a reference date may return wrong values." Why is the time different? – arakweker Jan 10 '18 at 21:57

3 Answers3

2

If you don't care about the time difference, why not just leave the time out completely? It would give you the same output, and would just save you from putting in default time values and then ignoring them for the rest of your function:

let greg = Calendar(identifier: .gregorian)
var date1 = birthday // from datepicker
var date2 = Date()
var components1 = greg.dateComponents([.year, .month, .day], from: date1)
var components2 = greg.dateComponents([.year, .month, .day], from: date2)
date1 = greg.date(from: components1)!
date2 = greg.date(from: components2)!

let daysAlive = (date2 - date1).in(.day)
let weeksAlive = Int(weeksAlive! / 7)
let restDays = daysAlive! - (weeksAlive * 7)
let aLive = "Age: \(weeksAlive) wk, \(restDays) d (\(daysAlive!) d)"
creeperspeak
  • 5,403
  • 1
  • 17
  • 38
2

To ignore the time just omit the components for .hour, .minute and .second

let components1 = greg.dateComponents([.year, .month, .day], from: date1)
let components2 = greg.dateComponents([.year, .month, .day], from: date2)

date1 = greg.date(from: components1)!
date2 = greg.date(from: components2)!

This is a simpler solution without any third party library. It calculates the difference once for weekOfMonth and day and once for day with the Calendar method dateComponents(:from:to:. Extra math is not needed.

let greg = Calendar(identifier: .gregorian)
var date1 = greg.date(from: DateComponents(year: 2017, month: 6, day: 25))!
var date2 = Date()
var components1 = greg.dateComponents([.year, .month, .day], from: date1)
var components2 = greg.dateComponents([.year, .month, .day], from: date2)

let weeksAndDays = greg.dateComponents([.weekOfMonth, .day], from: components1, to: components2)
let justDays = greg.dateComponents([.day], from: components1, to: components2)

let aLive = "Age: \(weeksAndDays.weekOfMonth!) wk, \(weeksAndDays.day!) d (\(justDays.day!) d)"
// "Age: 28 wk, 3 d (199 d)"
vadian
  • 274,689
  • 30
  • 353
  • 361
  • Good suggestion, Vadian. But I do need more date calculations later on, so I'm trying to make use of SwiftDate as much as I can. – arakweker Jan 10 '18 at 21:52
  • I get the following output: 2017-06-24 22:00:00 +0000 2018-01-09 23:00:00 +0000 "[SwiftDate] Using .in() to extract calendar specific components without a reference date may return wrong values." Why is the time different? – arakweker Jan 10 '18 at 21:58
  • Looks like a timezone difference? – arakweker Jan 10 '18 at 21:59
  • `print()` displays dates in UTC. Your time zone is UTC + 2 in June (daylight saving time) and UTC + 1 in January. `2017-06-24 22:00:00 +0000` is the same point in time as `2017-06-25 00:00:00 +0200` – vadian Jan 10 '18 at 22:02
2

As others have pointed out, you can just grab .year, .month, .day from your calendar. You might also see if DateComponentsFormatter can achieve what you want:

let formatter: DateComponentsFormatter = {
    let _formatter = DateComponentsFormatter()
    _formatter.allowedUnits = [.day, .weekOfMonth]
    _formatter.unitsStyle = .full
    return _formatter
}()

@IBAction func didValueChange(_ picker: UIDatePicker) {
    let calendar = Calendar.current

    let birthDate = calendar.date(from: calendar.dateComponents([.year, .month, .day], from: picker.date))!
    let now = calendar.date(from: calendar.dateComponents([.year, .month, .day], from: Date()))!
    label.text = formatter.string(from: birthDate, to: now)
}

While the DateComponentsFormatter is a little constrained regarding the results, it is localized, resulting in the following in US locale:

28 weeks, 3 days

But if your app is configured to support other locales, for example German, it results in:

28 Wochen und 3 Tage

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • that is a pretty nice solution. didn't think of that at all. localization is a big bonus and works fine. i also need to calculate with weeks and days, so I'll keep on using SwiftDate. but for displaying purposes this solution is fine. thanks for sharing. – arakweker Jan 11 '18 at 10:53