I have an NSDate object and I want to set it to an arbitrary time (say, midnight) so that I can use the timeIntervalSince1970 function to retrieve data consistently without worrying about the time when the object is created.
I've tried using an NSCalendar and modifying its components by using some Objective-C methods, like this:
let date: NSDate = NSDate()
let cal: NSCalendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)!
let components: NSDateComponents = cal.components(NSCalendarUnit./* a unit of time */CalendarUnit, fromDate: date)
let newDate: NSDate = cal.dateFromComponents(components)
The problem with the above method is that you can only set one unit of time (/* a unit of time */), so you could only have one of the following be accurate:
Day
Month
Year
Hours
Minutes
Seconds
Is there a way to set hours, minutes, and seconds at the same time and retain the date (day/month/year)?
Your statement
The problem with the above method is that you can only set one unit of
time ...
is not correct. NSCalendarUnit conforms to the RawOptionSetType protocol which
inherits from BitwiseOperationsType. This means that the options can be bitwise
combined with & and |.
In Swift 2 (Xcode 7) this was changed again to be
an OptionSetType which offers a set-like interface, see
for example Error combining NSCalendarUnit with OR (pipe) in Swift 2.0.
Therefore the following compiles and works in iOS 7 and iOS 8:
let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
// Swift 1.2:
let components = cal.components(.CalendarUnitDay | .CalendarUnitMonth | .CalendarUnitYear, fromDate: date)
// Swift 2:
let components = cal.components([.Day , .Month, .Year ], fromDate: date)
let newDate = cal.dateFromComponents(components)
(Note that I have omitted the type annotations for the variables, the Swift compiler
infers the type automatically from the expression on the right hand side of
the assignments.)
Determining the start of the given day (midnight) can also done
with the rangeOfUnit() method (iOS 7 and iOS 8):
let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
var newDate : NSDate?
// Swift 1.2:
cal.rangeOfUnit(.CalendarUnitDay, startDate: &newDate, interval: nil, forDate: date)
// Swift 2:
cal.rangeOfUnit(.Day, startDate: &newDate, interval: nil, forDate: date)
If your deployment target is iOS 8 then it is even simpler:
let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let newDate = cal.startOfDayForDate(date)
Update for Swift 3 (Xcode 8):
let date = Date()
let cal = Calendar(identifier: .gregorian)
let newDate = cal.startOfDay(for: date)
Yes.
You don't need to fiddle with the components of the NSCalendar at all; you can simply call the dateBySettingHour method and use the ofDate parameter with your existing date.
let date: NSDate = NSDate()
let cal: NSCalendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)!
let newDate: NSDate = cal.dateBySettingHour(0, minute: 0, second: 0, ofDate: date, options: NSCalendarOptions())!
For Swift 3:
let date: Date = Date()
let cal: Calendar = Calendar(identifier: .gregorian)
let newDate: Date = cal.date(bySettingHour: 0, minute: 0, second: 0, of: date)!
Then, to get your time since 1970, you can just do
let time: NSTimeInterval = newDate.timeIntervalSince1970
dateBySettingHour was introduced in OS X Mavericks (10.9) and gained iOS support with iOS 8.
Declaration in NSCalendar.h:
/*
This API returns a new NSDate object representing the date calculated by setting hour, minute, and second to a given time.
If no such time exists, the next available time is returned (which could, for example, be in a different day than the nominal target date).
The intent is to return a date on the same day as the original date argument. This may result in a date which is earlier than the given date, of course.
*/
- (NSDate *)dateBySettingHour:(NSInteger)h minute:(NSInteger)m second:(NSInteger)s ofDate:(NSDate *)date options:(NSCalendarOptions)opts NS_AVAILABLE(10_9, 8_0);
Here's an example of how you would do it, without using the dateBySettingHour function (to make sure your code is still compatible with iOS 7 devices):
NSDate* now = [NSDate date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComponents = [gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:now];
NSDate* midnightLastNight = [gregorian dateFromComponents:dateComponents];
Yuck.
There is a reason why I prefer coding in C#...
Anyone fancy some readable code..?
DateTime midnightLastNight = DateTime.Today;
;-)
Swift 5+
let date = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: Date())
Swift iOS 8 and up: People tend to forget that the Calendar and DateFormatter objects have a TimeZone. If you do not set the desired timzone and the default timezone value is not ok for you, then the resulting hours and minutes could be off.
Note: In a real app you could optimize this code some more.
Note: When not caring about timezones, the results could be OK on one device, and bad on an other device just because of different timezone settings.
Note: Be sure to add an existing timezone identifier! This code does not handle a missing or misspelled timezone name.
func dateTodayZeroHour() -> Date {
var cal = Calendar.current
cal.timeZone = TimeZone(identifier: "Europe/Paris")!
return cal.startOfDay(for: Date())
}
You could even extend the language. If the default timezone is fine for you, do not set it.
extension Date {
var midnight: Date {
var cal = Calendar.current
cal.timeZone = TimeZone(identifier: "Europe/Paris")!
return cal.startOfDay(for: self)
}
var midday: Date {
var cal = Calendar.current
cal.timeZone = TimeZone(identifier: "Europe/Paris")!
return cal.date(byAdding: .hour, value: 12, to: self.midnight)!
}
}
And use it like this:
let formatter = DateFormatter()
formatter.timeZone = TimeZone(identifier: "Europe/Paris")
formatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
let midnight = Date().midnight
let midnightString = formatter.string(from: midnight)
let midday = Date().midday
let middayString = formatter.string(from: midday)
let wheneverMidnight = formatter.date(from: "2018/12/05 08:08:08")!.midnight
let wheneverMidnightString = formatter.string(from: wheneverMidnight)
print("dates: \(midnightString) \(middayString) \(wheneverMidnightString)")
The string conversions and the DateFormatter are needed in our case for some formatting and to move the timezone since the date object in itself does not keep or care about a timezone value.
Watch out! The resulting value could differ because of a timezone offset somewhere in your calculating chain!
Just in case someone is looking for this:
Using SwiftDate you could just do this:
Date().atTime(hour: 0, minute: 0, second: 0)
In my opinion, the solution, which is easiest to verify, but perhaps not the quickest, is to use strings.
func set( hours: Int, minutes: Int, seconds: Int, ofDate date: Date ) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let newDateString = "\(dateFormatter.string(from: date)) \(hours):\(minutes):\(seconds)"
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return dateFormatter.date(from: newDateString)
}
func resetHourMinuteSecond(date: NSDate, hour: Int, minute: Int, second: Int) -> NSDate{
let nsdate = NSCalendar.currentCalendar().dateBySettingHour(hour, minute: minute, second: second, ofDate: date, options: NSCalendarOptions(rawValue: 0))
return nsdate!
}
Use the current calendar to get the start of the day for the current time.
let today = Calendar.current.startOfDay(for: Date())
I have a Json Data which contains 2 Key:Value "startDate":"2008.11", and "endDate":"2011.10", respectively, and what i wanna do is to loop through the date range until this statement becomes false:
while startDate <= endDate {
let range += startDate + 1
print(range)
}
something similar like this.. .
how to loop through the date range in swift?
Assuming you have extracted the json values you can use DateComponents to calculate the number of months between the values
let startDate = "2008.11".split(separator: ".").compactMap {Int($0)}
let endDate = "2011.10".split(separator: ".").compactMap {Int($0)}
//Maybe add a check here that both arrays are of length 2
let startComponents = DateComponents(year: startDate[0], month: startDate[1])
let endComponents = DateComponents(year: endDate[0], month: endDate[1])
let months = Calendar.current.dateComponents([.month], from: startComponents, to: endComponents).month!
And now you can use a simple for loop
for n in 0..<months {
//...
}
If you want to work with days (and dates) you can calculate them as
let days = Calendar.current.dateComponents([.day], from: startComponents, to: endComponents).day!
but you need to decide what day in month to use for startDate and endDate
I have a date in string format, example:- "2017-07-31" or can be multiple dates (any) in string format. My requirement is to check this date to current date and if it is greater than 0 and less than 15, then that time I have to do another operation.
So first I am converting that date string to in date format. But it is giving one day ago date. Here is my code:
//Date from string
func dateFromString(date : String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let currentDate = (dateFormatter.date(from: date))//(from: date))
return currentDate!
}
Ex. my date is "2017-08-30" and this function is returning 2017-08-29 18:30:00 +0000 in date format. It means 1 day ago. I am little bit confuse about dates operation. I read so many blogs also.
After that I have to check this date to current date if it is in between 0 < 15 than I will do other operation.
Comparing two dates:
extension Date {
func daysBetweenDate(toDate: Date) -> Int {
let components = Calendar.current.dateComponents([.day], from: self, to: toDate)
return components.day ?? 0
}
}
If my date is today date and comparing to tomorrow date then also it is giving 0 days difference. Why?
If – for example – the current date is 2017-07-31 at 11AM then the
difference to 2017-08-01 (midnight) is 0 days and 13 hours, and that's
why you get "0 days difference" as result.
What you probably want is to compare the difference between the start
of the current day and the other date in days:
extension Date {
func daysBetween(toDate: Date) -> Int {
let cal = Calendar.current
let startOfToday = cal.startOfDay(for: self)
let startOfOtherDay = cal.startOfDay(for: toDate)
return cal.dateComponents([.day], from: startOfToday, to: startOfOtherDay).day!
}
}
Try this method for convert string to date:
func dateFromString(date : String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
dateFormatter.timeZone = TimeZone.init(abbreviation: "UTC")
let currentDate = (dateFormatter.date(from: date))//(from: date))
return currentDate!
}
Try this to compare the time between two dates in seconds :
var seconds = Calendar.current.dateComponents([.second], from: date1!, to: date2!).second ?? 0
seconds = abs(seconds)
let min = seconds/60 // this gives you the number of minutes between two dates
let hours = seconds/3600 // this gives you the number of hours between two dates
let days = seconds/3600*24 // this gives you the number of days between two dates
when i'm going set event from app to device calendar. i got wrong time.
i have three date picker one for date and other two for start time and end time for event. i set start date as end date in EKEvent because i have to set event on that day only.
get date from date-picker and store it as startdate and end date as nsdate type. below is my date-picker method
func pickerDate()
{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd"
routineStartDate = dateFormatter.string(from: self.startDatePicker.date)
// it is for database entry in string and i get right string
print(routineStartDate)
startDate = self.startDatePicker.date as NSDate
print(startDate)
endDate = startDate
}
below method is for start time where i get time and convert to Time Interval and set it to start date.
func starttime() {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
let then: Date? = self.startTimePicker.date
let difference: TimeInterval? = then?.timeIntervalSinceNow
startDate.addingTimeInterval(difference!)
routineStartTime = dateFormatter.string(from: self.startTimePicker.date)
// it is for database entry in string and i get right string
print(routineStartTime)
}
below method is for end time where i get time from picker and convert to Time Interval and set Time Interval to enddate
func endtime() {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
routineEndTime = dateFormatter.string(from: self.endTimePicker.date)
print(routineEndTime)
// it is for database entry in string and i get right string
let then: Date? = self.endTimePicker.date
let difference: TimeInterval? = then?.timeIntervalSinceNow
endDate.addingTimeInterval(difference!)
}
below image showing which date i set in picker
below is My EKEvent method where i create event.
existevent.title = tempDescription
existevent.startDate = startDate as Date
existevent.endDate = endDate as Date
existevent.isAllDay = false
existevent.notes = "This is a note"
existevent.calendar = cal
when i check event in calendar i got Problem, i get wrong time in event.i set start time 12:50 pm end time 1:50 pm on date 27 june 2017 in caledar app. date is set perfectly but why time is not set perfectly ? below image for calendar app.
i have doubt in conversion of time interval and set to date. but what i missing dont know.
please suggest me solution and ideas to solve.
Thank you
you need to convert the time to the desired time zone. Because now the date is set correctly in your timezone, but is displayed in +0000 Screenshot. Use calendar for date representation this
And change your code like this in both methods:
startDate.addingTimeInterval(difference!)
to
self.startDate = startDate.addingTimeInterval(difference!)
and
endDate.addingTimeInterval(difference!)
to
self.endDate = endDate.addingTimeInterval(difference!)
in your case Xcode Warning "Result of call to 'addingTimeInterval' is unused"
Try to convert date, before set it to you "existevent", or when you show it
func convertDate(date:Date) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm" // or other format
var comp = DateComponents()
let calendar = Calendar.current
comp.hour = Calendar.current.component(.hour, from: date)
comp.minute = Calendar.current.component(.minute, from: date)
comp.timeZone = TimeZone(abbreviation: "GMT")!
return calendar.date(from: comp)!
}
This question already has answers here:
iOS: Convert UTC NSDate to local Timezone
(15 answers)
Closed 6 years ago.
I have a requirement in which I have a local DB full of dates from a particular timezone and I want to convert this date from that particular timezone to user's local timezone. The way I've implemented is that first I am always converting the DB date to UTC by adding the hours difference and then converting it to user local time zone by taking the time interval using
NSTimeInterval(NSTimeZone.localTimeZone().secondsFromGMT
and adding it to the UTC date. I would like to know that is the approach fine because it worked till now on my limited testing. Also will it account for countries which has Day light saving currently active.
The complete code :
func calculateDate(model:EventModel) -> NSDate
{
let date = model.Date
let startTime = model.StartTime
let arrayForTime = startTime?.componentsSeparatedByString(":")
let arrayForDates = date?.componentsSeparatedByString("-")
let calender = NSCalendar(identifier:NSCalendarIdentifierGregorian)
let year = Int(arrayForDates![2])
let month = Int(arrayForDates![1])
let day = Int(arrayForDates![0])
let hour = Int(arrayForTime![0])! + 3 //UTC - 3 the local time
let minutes = Int(arrayForTime![1])
let dateComponents = NSDateComponents()
dateComponents.day = day!
dateComponents.month = month!
dateComponents.year = year!
dateComponents.hour = hour
dateComponents.minute = minutes!
dateComponents.second = 0
dateComponents.timeZone = NSTimeZone(name: "UTC")
let UTCDate = calender?.dateFromComponents(dateComponents)
let dateLocal = self.getLocalDate(UTCDate!)
return dateLocal
}
func getLocalDate(utcDate:NSDate) -> NSDate
{
let timeInterval = NSTimeInterval(NSTimeZone.localTimeZone().secondsFromGMT)
let localdate = utcDate.dateByAddingTimeInterval(timeInterval)
return localdate
}
Previously I was using this but it was not returning the correct local date for Day light saving countries.
func getLocalDate(utcDate:NSDate) -> NSDate
{
let timeInterval = NSTimeInterval(NSTimeZone.localTimeZone().secondsFromGMT)
// let timeZoneObj = NSTimeZone.localTimeZone()
let localdate = utcDate.dateByAddingTimeInterval(timeInterval)
// let isDayLightSavingOn = timeZoneObj.isDaylightSavingTimeForDate(localdate)
// if(isDayLightSavingOn == true)
// {
// let dayLightTimeInterval = timeZoneObj.daylightSavingTimeOffsetForDate(localdate)
// timeInterval -= dayLightTimeInterval
// }
// localdate = utcDate.dateByAddingTimeInterval(timeInterval)
return localdate
}
Thanks in advance. Cheers !
You don't "convert" the NSDate to a timezone. You simply create a string representation of the NSDate in the user's time zone using a NSDateFormatter. The timeZone of the NSDateFormatter defaults to the user's own timezone, so no adjustments are necessary when displaying it in the user's local timezone. For example:
let date = ... // let's imagine it was "2016-05-18 00:03:34 +0000"
let formatter = NSDateFormatter()
formatter.dateStyle = .LongStyle
formatter.timeStyle = .LongStyle
let userDateString = formatter.stringFromDate(date)
If the user was in GMT/UTC-7 timezone, for example, that would result in:
"May 17, 2016 at 5:03:34 PM PDT"
Clearly, change the dateStyle and timeStyle to format it however best for your user interface. But don't do any adjustments of NSDate objects at all, but rather just build a string representation in the user's own timezone.