2

I creating an itinerary generation app where the user is required to enter the dates of his/her trip. The only problem is, using UIDatePicker the dates are always given as the current time for a given day/month/year.

In a separate file I've extended Date class to try and write a simple method that will return midnight for a given date.

First I tried

    var midnight:Date{
    let cal = Calendar(identifier: .gregorian)
    return cal.startOfDay(for: self)
}

However this always gave me either 04:00 or 05:00 depending on daylights savings, which gave me the idea that I should simply remove 4 or 5 hours depending on daylight savings, and so I created the following methods:

    var timezone:TimeZone{
    return TimeZone.current
}
///Returns the first instance of the date, e.g. 2018-02-26 00:00:00
var trueMidnight:Date{
    let cal = Calendar(identifier: .gregorian)
    let midnight = cal.startOfDay(for: self)
    let secondsFromGMT = TimeZone.current.secondsFromGMT()
    print("Daylight savings? \(daylightSavings)")
    return midnight.addingTimeInterval(Double(secondsFromGMT))
}
///If this var returns true, then daylight savings time is active and an hour of daylight is gained (during the summer).
var isDaylightSavings:Bool{
    return timezone.daylightSavingTimeOffset(for: self) == 0 ? false : true
}
var daylightSavings:Double{
    return isDaylightSavings ? 3600.0 : 0.0
}

However these methods sometimes return midnight, 23:00, or even 22:00 the previous day.

I'm a relatively inexperienced programmer so I feel like I'm lacking a basic understanding for the date class or missing a large concept. Why is it so difficult for me to simply find midnight on a given date?

I even forsook the idea of returning midnight and tried to just find noon on a given day with the code:

    var noon:Date{
    let gregorian = Calendar(identifier: .gregorian)
    var components = gregorian.dateComponents([.year, .month, .day, .hour, .minute, .second], from: self)

    components.hour = 12
    components.minute = 0
    components.second = 0
    return gregorian.date(from: components)!
}

But this returns 16:00 or 17:00 as opposed to noon. Any help would be appreciated.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
jacob_g
  • 674
  • 8
  • 21
  • what do you need startOfDay local time or UTC midnight? – Leo Dabus Mar 11 '18 at 00:30
  • Does true midnight means UTC 12am ? if so it would be much easier to simply change your calendar timezone to UTC – Leo Dabus Mar 11 '18 at 00:31
  • I'm not sure, but in the future I'd eventually like to be able to export the itinerary using EventKit, so local time? – jacob_g Mar 11 '18 at 00:31
  • this will depend on your goal. Generally you just save the date using UTC timezone https://stackoverflow.com/questions/28016578/swift-how-to-create-a-date-time-stamp-and-format-as-iso-8601-rfc-3339-utc-tim/28016692#28016692 – Leo Dabus Mar 11 '18 at 00:33
  • https://stackoverflow.com/questions/27053135/how-to-get-a-users-time-zone/27053592?s=2|44.2602#27053592 – Leo Dabus Mar 11 '18 at 00:34
  • FYI - the 1st bit of code in your question is just fine. It is giving you midnight (local time). Why do you think otherwise? Is it because you print the resulting Date and doing so happens to show you that date in UTC? That doesn't make your date wrong, it simply means you are misunderstanding the output of printing a `Date` instance. – rmaddy Mar 11 '18 at 00:35
  • Don't print the date print its description using current timezone `print(Date().description(with: .current))` – Leo Dabus Mar 11 '18 at 00:38
  • Thanks guys, I guess that was what I was misunderstanding. – jacob_g Mar 11 '18 at 00:41
  • Please delete the question. Failing to understand the output when printing a date is very common and has been discussed here MANY times. – matt Mar 11 '18 at 03:35
  • 2
    This questions is marked as duplicating two others. However, one of those two does not involve Swift and the other has -2 upvotes. – Edward Brey Jul 24 '21 at 13:06
  • @LeoDabus but the description prints an ugly Wednesday, March 1, 2023 at 12:00:00 AM ... so if it's a date object, the time will always be 16:00 ? it is impossible to get a date object with 00:00 as time? – chitgoks Mar 01 '23 at 06:32
  • @chitgoks Under the hood the date object stores the amount of seconds since a reference date (UTC). When you print a date object it will always show the UTC time description. It you need another locale description you can use `date.description(with: .current)` to display the timezone of your current locale. You can also pass another locale(fixed) if you need. – Leo Dabus Mar 01 '23 at 17:31
  • @chitgoks if you need to display the date to the user you should use DateFormatter date and time style. Check this [post](https://stackoverflow.com/a/28347285/2303865) – Leo Dabus Mar 01 '23 at 17:34
  • @LeoDabus i resolved it. added an extension to get the local date by converting utc timezone to user's current timezone – chitgoks Mar 02 '23 at 06:13
  • Don’t do that a date does not have a timezone. Only it description does. – Leo Dabus Mar 02 '23 at 15:37

2 Answers2

8

When you print a date, it is printed in UTC time. So when you print your Dates, they differ from your local time by 4/5 hours.

If you use the following code instead

print(yourDate.description(with: .current))

Where yourDate is your date, it will be in the correct time zone.

Papershine
  • 4,995
  • 2
  • 24
  • 48
  • 1
    I didn't know about the `Date` method `description(with:)` Thanks for that. (voted) – Duncan C Mar 11 '18 at 03:14
  • 1
    It seems to use a very verbose format though. I prefer `DateFormatter.localizedString(from:dateStyle:timeStyle)`, or better yet, the extension I provided, since it lets you control the output format. – Duncan C Mar 11 '18 at 03:15
4

You're confused.

If you use

print(Date()) 

You will get a date in UTC. If you're in the UTC+5 time zone, that date will be 5 hours greater than your local time. Thus if you try to display midnight local time in UTC, it will show up as 5:00 AM in UTC.

Try this:

extension Date {
    func localString(dateStyle: DateFormatter.Style = .medium, 
      timeStyle: DateFormatter.Style = .medium) -> String {
        return DateFormatter.localizedString(
          from: self, 
          dateStyle: dateStyle, 
          timeStyle: timeStyle)
    }

    var midnight:Date{
        let cal = Calendar(identifier: .gregorian)
        return cal.startOfDay(for: self)
    }
}
print("Tonight at midnight is " + Date().midnight.localString())

That code uses a function localString() that takes advantage of a DateFormatter method localizedString(from:dateStyle:timeStyle:) that converts a Date to a string in the current locale (which includes the local time zone.

I suggest adding that extension to your apps.

Duncan C
  • 128,072
  • 22
  • 173
  • 272