15

Is there any easy way to truncate date like we can do in Oracle Database? For example, I need to set value starting from midnight. In Oracle I can do TRUNC(SYSDATE). But I cannot see similar way to do it in Swift. I checked StackOverflow and saw some examples with DateFormatter, Date Components with converting Date to String. I have also seen this question, but didn't understand how to handle Date (not NSDate) type and i

But I don't need String values. And I would like to avoid converting to string and back to Date. I just want to get three values:

Start of the current date (just like we do TRUNC(SYSDATE) in Oracle)

Start of the month (just like we do TRUNC(SYSDATE, 'MONTH') in Oracle)

Start of the year (just like we do TRUNC(SYSDATE, 'YEAR') in Oracle)

I tried this, but it does not return midnight:

let comp: DateComponents = Calendar.current.dateComponents([.year, .month, .day], from: Date())
let truncated = Calendar.current.date(from: comp)!

enter image description here

What should I do?

DJ-Glock
  • 1,277
  • 2
  • 12
  • 39
  • 1
    I have bad news for you: not every day has a midnight. – Alexander Jun 14 '17 at 20:41
  • 1
    Hm... Why? Some background: I need to extract stats for current Day, current Month, current Year. I used this like a temporary solution: Date().addingTimeInterval((-1)*60*60*24) but it is not what I want. – DJ-Glock Jun 14 '17 at 20:44
  • Okay, I have changed title to "start of the day/month/year". It sounds much better. – DJ-Glock Jun 14 '17 at 20:50
  • 1
    Because of daylight savings, among other things: https://stackoverflow.com/a/17726013/3141234 – Alexander Jun 14 '17 at 20:52

2 Answers2

34

Calendar has dedicated methods:

  • Start of the day

      let truncated = Calendar.current.startOfDay(for: Date())
    
  • Start of the month

      let now = Date()
      var startOfMonth = now
      var timeInterval : TimeInterval = 0.0
      Calendar.current.dateInterval(of: .month, start: &startOfMonth, interval: &timeInterval, for: now)
    
      print(startOfMonth)
    
  • Start of the year

      let now = Date()
      var startOfYear = now
      var timeInterval : TimeInterval = 0.0
      Calendar.current.dateInterval(of: .year, start: &startOfYear, interval: &timeInterval, for: now)
    
      print(startOfYear)
    

Or as Date extension

extension Date {
    
    func startOf(_ dateComponent : Calendar.Component) -> Date {
        var calendar = Calendar.current
        calendar.timeZone = TimeZone(secondsFromGMT: 0)!
        var startOfComponent = self
        var timeInterval : TimeInterval = 0.0
        calendar.dateInterval(of: dateComponent, start: &startOfComponent, interval: &timeInterval, for: now)
        return startOfComponent
    }
}

let now = Date()

let startOfDay = now.startOf(.day)
let startOfMonth = now.startOf(.month)
let startOfYear = now.startOf(.year)

Regarding the time zone issue you can set the time zone of the current calendar accordingly.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • Yep, thanks. I saw this method, but it did not help me to get start of the month. Anyway Jeffery's answer looks valid to me. I missed TimeZones issue. – DJ-Glock Jun 14 '17 at 21:01
  • I updated the answer for the respective ways for month and year. – vadian Jun 14 '17 at 21:06
  • Thanks. Your solution also looks valid. But as Jeffery said - initially there were no issues. I just missed TimeZone issue :) – DJ-Glock Jun 14 '17 at 21:12
16

Note the +0000 at the end of the time in your sample.

The start of the day where? I updated the sample to show what you want.

var comp: DateComponents = Calendar.current.dateComponents([.year, .month, .day], from: Date())
comp.timeZone = TimeZone(abbreviation: "UTC")!
let truncated = Calendar.current.date(from: comp)!
print(truncated)

It prints 2017-06-14 00:00:00 +0000. Again, note the +0000 at the end.

By default the timezone is set to be the current timezone and the hour, minute, and second are 0 in that timezone.


I will assume that you want the current timezone. With that stipulation, your code is correct.

Jeffery Thomas
  • 42,202
  • 8
  • 92
  • 117