IOS Count current streak of days - ios

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
}

Related

Number of days between two timestamps

I want to get number of days between two timestamps but I am getting wrong value using this code.
Code :
let currentDateTimeStamp = Date().timeIntervalSince1970 * 1000.0
let firstDate = Date.init(timeIntervalSince1970: currentDateTimeStamp)
let lastDate = Date.init(timeIntervalSince1970: individualCellData["joining_date"] as! TimeInterval)
// First Method using extension
let daysBetween = firstDate.interval(ofComponent: .day, fromDate: lastDate)
// Second method
let components = Calendar.current.dateComponents([.day], from: lastDate, to: firstDate)
extension Date {
func interval(ofComponent comp: Calendar.Component, fromDate date: Date) -> Int {
let currentCalendar = Calendar.current
guard let start = currentCalendar.ordinality(of: comp, in: .era, for: date) else { return 0 }
guard let end = currentCalendar.ordinality(of: comp, in: .era, for: self) else { return 0 }
return end - start
}
}
I am getting timestamp from server in milliseconds. What is the correct way ?
let date1 = NSDate(timeIntervalSince1970: 1507211263)//Thursday, 5 October 2017 13:47:43
let date2 = NSDate(timeIntervalSince1970: 1507556863)//Monday, 9 October 2017 13:47:43
var secondsBetween: TimeInterval = date2.timeIntervalSince(date1 as Date)
var numberOfDays = Int(secondsBetween / 86400)
print("There are \(numberOfDays) days in between the two dates.")
//FYI: 86400 seconds = 24 hr
extension Date {
func timeStampToDay(timeStampInMillisecond:Double) -> Int {
let date = Date()
let todaysDateStamp = date.timeIntervalSince1970
let timeStampDate = Date(timeIntervalSince1970: timeStampInMillisecond / 1000)
var secBetween = Date(timeIntervalSince1970: todaysDateStamp).timeIntervalSince(timeStampDate)
return Int(abs(secBetween) / 86400)
}
func timeStampToDay(timeStampInSecond:Double) -> Int {
let date = Date()
let todaysDateStamp = date.timeIntervalSince1970
let timeStampDate = Date(timeIntervalSince1970: timeStampInMillisecond)
var secBetween = Date(timeIntervalSince1970: todaysDateStamp).timeIntervalSince(timeStampDate)
return Int(abs(secBetween) / 86400)
}
}

How to get week start date and end date by using any Month and week number swift 3?

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)
}

How fetch objects with NSDate of today using NSPredicate?

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]

Strange date result calculating start of next month using NSDateComponents in Swift 2

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.

Apple Swift: how to get current seconds in 1/100 or 1/1000?

func updateTime() {
var date = NSDate()
var calendar = NSCalendar.currentCalendar()
var components = calendar.components(.CalendarUnitSecond, fromDate: date)
var hour = components.hour
var minutes = components.minute
var seconds = components.second
counterLabel.text = "\(seconds)"
var myIndicator = counterLabel.text?.toInt()
if myIndicator! % 2 == 0 {
// do this
} else {
// do that
}
}
I'd like to know how I can change this code so I get 1/10 or 1/100 or 1/1000 of a second to display in counterlabel.text.
Just can't figure it out... thanks!
There is a calendar unit for nanoseconds:
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(.CalendarUnitNanosecond, fromDate: date)
let nanoSeconds = components.nanosecond
Update for Swift 3
let date = Date()
let calendar = NSCalendar.current
let components = calendar.dateComponents([.nanosecond], from: date)
let nanoSeconds = components.nanosecond
This gives the fractional part of the seconds in units of 10-9 seconds.
For milliseconds, just divide this value by 106:
let milliSeconds = nanoSeconds / 1_000_000
Alternatively, if you just want to display the fractional
seconds, use a NSDateFormatter and the SSS format. Example:
let fmt = NSDateFormatter()
fmt.dateFormat = "HH:mm:ss.SSS"
counterLabel.text = fmt.stringFromDate(date)
Update for Swift 3
let fmt = DateFormatter()
fmt.dateFormat = "HH:mm:ss.SSS"
counterLabel.text = fmt.stringFromDate(date)

Resources