I'm stuck in a problem with my Core Data model and a fetch request that involves dates.
I have some objects in a entity with a NSDate attribute; I need to extract the objects with the date of today but I always get nil from this code:
public func getObjectsOfToday() -> Array<myObject>?
{
let entityDescription = NSEntityDescription.entityForName("Objects", inManagedObjectContext: DataAccess.sharedInstance.managedObjectContext)
let request = NSFetchRequest()
request.entity = entityDescription
request.returnsObjectsAsFaults = false
let today = NSDate()
request.predicate = NSPredicate(format: "(dateStart => %#) AND (dateStart <= %#)", today, today)
var objects: [AnyObject]?
do
{
objects = try DataAccess.sharedInstance.managedObjectContext.executeFetchRequest(request)
}
catch let error as NSError
{
print(error)
objects = nil
}
return objects as? Array<Objects>
}
the problem I think it's the NSPredicate because it considers also hours, minute and seconds. If I print today is something like:
Printing description of today: 2016-02-28 22:02:01 +0000
but I want to fetch objects with just the same date, ignoring hours, minutes and seconds. What I need to do?
I also tried to create another NSDate using components:
let components = cal.components([.Day , .Month, .Year ], fromDate: today)
let newDate = cal.dateFromComponents(components)
but the result it's the same. What am I doing wrong?
What I do is compare it to the start and end of the day and have a couple helper functions to calculate them:
class DateHelper{
internal class func startOfDay(day: NSDate) -> NSDate {
let gregorian = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
let unitFlags: NSCalendarUnit = [.Minute, .Hour, .Day, .Month, .Year]
let todayComponents = gregorian!.components(unitFlags, fromDate: day)
todayComponents.hour = 0
todayComponents.minute = 0
return (gregorian?.dateFromComponents(todayComponents))!
}
internal class func endOfDay(day: NSDate) -> NSDate {
let gregorian = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
let unitFlags: NSCalendarUnit = [.Minute, .Hour, .Day, .Month, .Year]
let todayComponents = gregorian!.components(unitFlags, fromDate: day)
todayComponents.hour = 23
todayComponents.minute = 59
return (gregorian?.dateFromComponents(todayComponents))!
}
}
So in your code, you would call:
request.predicate = NSPredicate(format: "(dateStart => %#) AND (dateStart <= %#)", DateHelper.startOfDay(today), DateHelper.endOfDay(today))
create a start date, get the length of the day (interval), add the interval to start date to get the next day's start.
var startOfToday: NSDate?
var interval: NSTimeInterval = 0
NSCalendar.currentCalendar().rangeOfUnit(.Day, startDate: &startOfToday, interval: &interval, forDate: NSDate())
let startOfTomorrow = startOfToday!.dateByAddingTimeInterval(interval)
create the predicate
let predicate = NSPredicate(format: "dateStart >= %# AND dateStart < %#", startOfToday, startOfTomorrow)
I used the following test code without the core data hassle
import Foundation
let dates:[NSDate] = {
var dates:[NSDate] = []
dates.append({
let c = NSDateComponents()
c.year = 2016
c.month = 2
c.day = 1
return NSCalendar.currentCalendar().dateFromComponents(c)!
}())
dates.append({
let c = NSDateComponents()
c.year = 2016
c.month = 2
c.day = 3
return NSCalendar.currentCalendar().dateFromComponents(c)!
}())
dates.append({
let c = NSDateComponents()
c.year = 2016
c.month = 3
c.day = 1
return NSCalendar.currentCalendar().dateFromComponents(c)!
}())
dates.append({
let c = NSDateComponents()
c.year = 2016
c.month = 2
c.day = 28
c.hour = 12
c.minute = 30
return NSCalendar.currentCalendar().dateFromComponents(c)!
}())
dates.append({
let c = NSDateComponents()
c.year = 2016
c.month = 2
c.day = 28
c.hour = 11
c.minute = 15
return NSCalendar.currentCalendar().dateFromComponents(c)!
}())
return dates
}()
var startOfToday: NSDate?
var interval: NSTimeInterval = 0
NSCalendar.currentCalendar().rangeOfUnit(.Day, startDate: &startOfToday, interval: &interval, forDate: NSDate())
if let startOfToday = startOfToday {
let startOfTomorrow = startOfToday.dateByAddingTimeInterval(interval)
let predicate = NSPredicate(format: "self >= %# AND self < %#", startOfToday, startOfTomorrow)
let filteredArray = dates.filter({predicate.evaluateWithObject($0)})
print(filteredArray)
}
Result:
[2016-02-28 11:30:00 +0000, 2016-02-28 10:15:00 +0000]
Related
I'm making a day streak counter using UserDefaults and Core Data.
The idea is that a number will be added unto by 1 every separate day an action is performed-- this will be the streak number.
If this action wasn't performed for 24 hours, the number would reset to zero.
I have a function to set the end of the streak:
// set date time to the end of the day so the user has 24hrs to add to the streak
func changeDateTime(userDate: NSDate) -> NSDate {
let dateComponents = NSDateComponents()
let currentCalendar = NSCalendar.current
let year = Int(currentCalendar.component(NSCalendar.Unit.Year, fromDate:
userDate))
let month = Int(currentCalendar.component(NSCalendar.Unit.Month, fromDate:
userDate))
let day = Int(currentCalendar.component(NSCalendar.Unit.Day, fromDate: userDate))
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
dateComponents.hour = 23
dateComponents.minute = 59
dateComponents.second = 59
guard let returnDate = currentCalendar.dateFromComponents(dateComponents) else {
return userDate
}
return returnDate
}
It is returning the following Errors:
'NSDate' is not implicitly convertible to 'Date'; did you mean to use
'as' to explicitly convert?
Cannot convert value of type 'NSCalendar.Unit' to expected argument
type 'Calendar.Component'
When using the suggested corrections I only get more errors with no suggested corrections. I'm having trouble figuring out the proper way to express this
The full Code is:
let userDefaults = UserDefaults.standard
var moc: NSManagedObjectContext!
var lastStreakEndDate: NSDate!
var streakTotal: Int!
override func viewDidLoad() {
super.viewDidLoad()
// checks for object if nil creates one (used for first run)
if userDefaults.object(forKey: "lastStreakEndDate") == nil {
userDefaults.set(NSDate(), forKey: "lastStreakEndDate")
}
lastStreakEndDate = (userDefaults.object(forKey: "lastStreakEndDate") as! NSDate)
streakTotal = calculateStreak(lastDate: lastStreakEndDate)
}
// fetches dates since last streak
func fetchLatestDates(moc: NSManagedObjectContext, lastDate: NSDate) -> [NSDate] {
var dates = [NSDate]()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "streakCount")
let datePredicate = NSPredicate(format: "date < %#", lastDate)
fetchRequest.predicate = datePredicate
do {
let result = try moc.fetch(fetchRequest)
let allDates = result as! [NSDate]
if allDates.count > 0 {
for date in allDates {
dates.append(date)
}
}
} catch {
fatalError()
}
return dates
}
// set date time to the end of the day so the user has 24hrs to add to the streak
func changeDateTime(userDate: NSDate) -> NSDate {
let dateComponents = NSDateComponents()
let currentCalendar = NSCalendar.current
let year = Int(currentCalendar.component(NSCalendar.Unit.Year, fromDate: userDate))
let month = Int(currentCalendar.component(NSCalendar.Unit.Month, fromDate: userDate))
let day = Int(currentCalendar.component(NSCalendar.Unit.Day, fromDate: userDate))
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
dateComponents.hour = 23
dateComponents.minute = 59
dateComponents.second = 59
guard let returnDate = currentCalendar.dateFromComponents(dateComponents) else {
return userDate
}
return returnDate
}
// adds a day to the date
func addDay(today: NSDate) -> NSDate {
let tomorrow = NSCalendar.currentCalendar.dateByAddingUnit(.Day, value: 1, toDate: today, options: NSCalendar.Options(rawValue: 0))
return tomorrow!
}
// this method returns the total of the streak and sets the ending date of the last streak
func calculateStreak(lastDate: NSDate) -> Int {
let dateList = fetchLatestDates(moc: moc, lastDate: lastDate)
let compareDate = changeDateTime(userDate: lastDate)
var streakDateList = [NSDate]()
var tomorrow = addDay(today: compareDate)
for date in dateList {
changeDateTime(userDate: date)
if date == tomorrow {
streakDateList.append(date)
}
tomorrow = addDay(today: tomorrow)
}
userDefaults.set(streakDateList.last, forKey: "lastStreakEndDate")
return streakDateList.count
}
Any Help is Appreciated
You need
// set date time to the end of the day so the user has 24hrs to add to the streak
func changeDateTime(userDate: Date) -> Date {
var dateComponents = DateComponents()
let currentCalendar = Calendar.current
let year = Int(currentCalendar.component(.year, from:
userDate))
let month = Int(currentCalendar.component(.month, from:
userDate))
let day = Int(currentCalendar.component(.day, from: userDate))
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
dateComponents.hour = 23
dateComponents.minute = 59
dateComponents.second = 59
guard let returnDate = currentCalendar.date(from:dateComponents) else {
return userDate
}
return returnDate
}
OR shortly
// set date time to the end of the day so the user has 24hrs to add to the streak
func changeDateTime(userDate: Date) -> Date {
var dateComponents = DateComponents()
let currentCalendar = Calendar.current
let res = currentCalendar.dateComponents([.year,.month,.day],from:userDate)
dateComponents.year = res.year
dateComponents.month = res.month
dateComponents.day = res.day
dateComponents.hour = 23
dateComponents.minute = 59
dateComponents.second = 59
guard let returnDate = currentCalendar.date(from:dateComponents) else {
return userDate
}
return returnDate
}
I have to implement graph so that I need to get week start date and weekend date if I will pass the date object and week number.
How can I achieve that I tried it but didn't get exactly?
Here below is my code:-
Weekday:-
//Day of week
func getDayOfWeek(today:String)->Int? {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
if let todayDate = formatter.date(from: today) {
let myCalendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian)!
let myComponents = myCalendar.components(.weekday, from: todayDate)
let weekDay = myComponents.weekday
return weekDay
} else {
return nil
}
}.
extension Date {
var millisecondsSince1970:Int {
return Int((self.timeIntervalSince1970 * 1000.0).rounded())
}
init(milliseconds:Int) {
self = Date(timeIntervalSince1970: TimeInterval(milliseconds / 1000))
}
func startOfWeek(weekday: Int?) -> Date {
var cal = Calendar.current
var component = cal.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self)
component.to12am()
cal.firstWeekday = weekday ?? 1
return cal.date(from: component)!
}
func endOfWeek(weekday: Int) -> Date {
let cal = Calendar.current
var component = DateComponents()
component.weekOfYear = 1
component.day = -1
component.to12pm()
return cal.date(byAdding: component, to: startOfWeek(weekday: weekday))!
}
}
internal extension DateComponents {
mutating func to12am() {
self.hour = 0
self.minute = 0
self.second = 0
}
mutating func to12pm(){
self.hour = 23
self.minute = 59
self.second = 59
}
}
This returns start- and end date for a given week number and date
func dayRangeOf(weekOfYear: Int, for date: Date) -> Range<Date>
{
let calendar = Calendar.current
let year = calendar.component(.yearForWeekOfYear, from: date)
let startComponents = DateComponents(weekOfYear: weekOfYear, yearForWeekOfYear: year)
let startDate = calendar.date(from: startComponents)!
let endComponents = DateComponents(day:7, second: -1)
let endDate = calendar.date(byAdding: endComponents, to: startDate)!
return startDate..<endDate
}
print(dayRangeOf(weekOfYear: 12, for: Date()))
Consider that print displays the dates in UTC and the start date depends on the first weekday setting of the current locale.
Edit
A version to determine the range of a given week of month
func dayRangeOf(weekOfMonth: Int, year: Int, month: Int) -> Range<Date>? {
let calendar = Calendar.current
guard let startOfMonth = calendar.date(from: DateComponents(year:year, month:month)) else { return nil }
var startDate = Date()
if weekOfMonth == 1 {
var interval = TimeInterval()
guard calendar.dateInterval(of: .weekOfMonth, start: &startDate, interval: &interval, for: startOfMonth) else { return nil }
} else {
let nextComponents = DateComponents(year: year, month: month, weekOfMonth: weekOfMonth)
guard let weekStartDate = calendar.nextDate(after: startOfMonth, matching: nextComponents, matchingPolicy: .nextTime) else {
return nil
}
startDate = weekStartDate
}
let endComponents = DateComponents(day:7, second: -1)
let endDate = calendar.date(byAdding: endComponents, to: startDate)!
return startDate..<endDate
}
print(dayRangeOf(weekOfMonth: 5, year: 2017, month: 6))
The result type of the second version is an optional because there are a few calculations which could fail for example if the number of week in the particular month is out of range.
For anyone interested in this, it looks like OP confusing weekOfMonth and weekOfYear…
//: Playground - noun: a place where people can play
import UIKit
var str = "Hello, playground"
let cal = Calendar.current
let dateComponents = DateComponents(year: 2018, month: 3, day: 15)
let date = cal.date(from: dateComponents)!
func weekOfMonthStart(forDate date: Date) -> Date {
var compsToWeekOfMonth = cal.dateComponents([.year, .month, .weekOfYear], from: date)
compsToWeekOfMonth.day = cal.range(of: .day, in: .weekOfMonth, for: date)?.lowerBound
return cal.date(from: compsToWeekOfMonth)!
}
Somebody mention an answer that will fail, so a test was included ;)
for i in 0...5000 {
let newDate = cal.date(byAdding: DateComponents(day:i), to: date)!
weekOfMonthStart(forDate: newDate)
}
I have a function:
internal func startOfNextMonth() -> NSDate? {
guard
let cal: NSCalendar = NSCalendar.currentCalendar(),
let comp: NSDateComponents = NSDateComponents() else { return nil }
comp.month = 1
comp.day = 0
comp.to12pm()
let date = cal.dateByAddingComponents(comp, toDate: self.startOfMonth()!, options: [])!
return date
}
This should calculate the first day of next month, however it's returning 2016-03-02 08:00:00 UTC.
Is this something to do with it being a leap year (Feb. 29 messing it up?)
Here's my startOfMonth function for reference, and the to12pm() extension:
internal func startOfMonth() -> NSDate? {
guard
let cal: NSCalendar = NSCalendar.autoupdatingCurrentCalendar(),
let comp: NSDateComponents = cal.components([.Year, .Month], fromDate: self.at12pm()!) else { return nil }
comp.to12pm()
return cal.dateFromComponents(comp)!
}
internal extension NSDateComponents {
func to12pm() {
self.hour = 12
self.minute = 0
self.second = 0
}
}
internal func at12pm() -> NSDate? {
let cal = NSCalendar.currentCalendar()
return cal.dateBySettingHour(12, minute: 0, second: 0, ofDate: self, options: [])
}
startOfMonth() returns first of next month at 12 pm (12:00) then you add another 12 hours in startOfNextMonth(), this results one day ahead.
NSCalendar has a smart method to calculate the start of next month regardless of daylight saving changes or other irregularity
let calendar = NSCalendar.currentCalendar()
let components = NSDateComponents()
components.day = 1
let startOfNextMonth = calendar.nextDateAfterDate(NSDate(), matchingComponents: components, options: .MatchNextTime)
If you just want to calculate the first day of the next month, all you have to do is:
let calendar = NSCalendar.autoupdatingCurrentCalendar()
let todayComponents = calendar.components([.Year, .Month], fromDate: NSDate())
todayComponents.month += 1
let firstDayOfMonth = calendar.dateFromComponents(todayComponents)
Test:
print(firstDayOfMonth)
Prints in the console:
Optional(2016-03-01 08:00:00 +0000)
Also, if it is December, the date will overflow to January of the next year.
I am trying to compare two NSDates to understand if they are in the same day or not. These are the two dates (the second one is always the current date, NSDate()):
2015-08-23 22:00:00 +0000
2015-08-23 19:13:45 +0000
It seems obvious that the dates are in the same day, as 23 = 23. However I can't seem to be able to establish this in the code. I tried:
1)
let date = tripObject["tripDate"] as! NSDate
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let str = dateFormatter.stringFromDate(date)
let date2 = NSDate()
let dateFormatter2 = NSDateFormatter()
dateFormatter2.dateFormat = "yyyy-MM-dd"
let str2 = dateFormatter2.stringFromDate(date2)
println(". \(str) and \(str2) .")
This gives 2015-08-24 and 2015-08-23
2)
let dateComponents = NSCalendar.currentCalendar().components(.CalendarUnitYear | .CalendarUnitMonth | .CalendarUnitDay, fromDate: tripObject["tripDate"] as! NSDate)
let dateComponents2 = NSCalendar.currentCalendar().components(.CalendarUnitYear | .CalendarUnitMonth | .CalendarUnitDay, fromDate: NSDate())
This gives different day components.
3)
if NSCalendar.currentCalendar().isDate(firstDate!, inSameDayAsDate: secondDate!){
4)
if NSCalendar.currentCalendar().isDateInToday(the NSDate) {
Both 3 and 4 fail.
I am totally lost, any ideas?
NSCalendar has a method isDate:equalToDate:toUnitGranularity: which does exactly what you need
date1 and date2 are the NSDate instances to be compared, the result is Bool
let calendar = NSCalendar.currentCalendar()
calendar.timeZone = NSTimeZone(forSecondsFromGMT: 0)
let datesAreInTheSameDay = calendar.isDate(date1, equalToDate: date2, toUnitGranularity: .CalendarUnitDay | .CalendarUnitMonth | .CalendarUnitYear)
Swift 2:
let datesAreInTheSameDay = calendar.isDate(date1, equalToDate: date2, toUnitGranularity: [.Day, .Month, .Year])
Swift 3:
var calendar = Calendar.current
calendar.timeZone = TimeZone(secondsFromGMT: 0)!
let datesAreInTheSameDay = calendar.isDate(date1, equalTo: date2, toGranularity:.day)
You can convert both dates to start of the day and then use compare method of NSDate to compare them
let date1StartOfTheDay = NSCalendar.currentCalendar().startOfDayForDate(date)
let date2StartOfTheDay = NSCalendar.currentCalendar().startOfDayForDate(date2
if date1StartOfTheDay.compare(date2StartOfTheDay) == OrderedSame
{
//same dates
}
You can use this extension:
extension NSDate {
func isEqualToDate2(dateToCompare : NSDate) -> Bool {
//Declare Variables
var isEqualTo = false
//Compare Values
if self.compare(dateToCompare) == NSComparisonResult.OrderedSame {
isEqualTo = true
}
//Return Result
return isEqualTo
}
}
like:
if date1.isEqualToDate2(date2) {
...
}
I cant find age in from birth date. What I got is
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
My code
override func viewDidLoad() {
super.viewDidLoad()
var dateString = user.birthday
var dateFormatter = NSDateFormatter()
// this is imporant - we set our input date format to match our input string
dateFormatter.dateFormat = "dd-MM-yyyy"
// voila!
var dateFromString = dateFormatter.dateFromString(dateString)
let age = calculateAge(dateFromString!)
}
func calculateAge (birthday: NSDate) -> NSInteger {
var userAge : NSInteger = 0
var calendar : NSCalendar = NSCalendar.currentCalendar()
var unitFlags : NSCalendarUnit = NSCalendarUnit.CalendarUnitYear | NSCalendarUnit.CalendarUnitMonth | NSCalendarUnit.CalendarUnitDay
var dateComponentNow : NSDateComponents = calendar.components(unitFlags, fromDate: NSDate())
var dateComponentBirth : NSDateComponents = calendar.components(unitFlags, fromDate: birthday)
if ( (dateComponentNow.month < dateComponentBirth.month) ||
((dateComponentNow.month == dateComponentBirth.month) && (dateComponentNow.day < dateComponentBirth.day))
)
{
return dateComponentNow.year - dateComponentBirth.year - 1
}
else {
return dateComponentNow.year - dateComponentBirth.year
}
}
update: Xcode 11 • Swift 5.1
You can use the Calendar method dateComponents to calculate how many years from a specific date to today:
extension Date {
var age: Int { Calendar.current.dateComponents([.year], from: self, to: Date()).year! }
}
let dob = DateComponents(calendar: .current, year: 2000, month: 6, day: 30).date!
let age = dob.age // 19
Important:
The timezone must be set to create a UTC birth date otherwise there will be inconsistencies between timezones.
Swift 3
extension Date {
//An integer representation of age from the date object (read-only).
var age: Int {
get {
let now = Date()
let calendar = Calendar.current
let ageComponents = calendar.dateComponents([.year], from: self, to: now)
let age = ageComponents.year!
return age
}
}
init(year: Int, month: Int, day: Int) {
var dc = DateComponents()
dc.year = year
dc.month = month
dc.day = day
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(secondsFromGMT: 0)!
if let date = calendar.date(from: dc) {
self.init(timeInterval: 0, since: date)
} else {
fatalError("Date component values were invalid.")
}
}
}
Usage:
let dob = Date(year: 1975, month: 1, day: 1)
let age = dob.age
print(age)
In Swift 2.0+ age computing code should look something like this:
extension NSDate {
var age:Int {
return NSCalendar.currentCalendar()
.components(NSCalendarUnit.Year,
fromDate: self,
toDate: NSDate(),
options: NSCalendarOptions(rawValue: 0)
.year
}
}
Just use the DateTools pod. Absolutely the easiest way.
https://github.com/MatthewYork/DateTools
For Swift 3
import DateTools
let birthday: Date = ....
let ageString = String((Date() as NSDate).years(from: birthday))