I'm making a kind of challenge based app that requires that the user comes back every day. If he misses one day, he has to start all over again.
My problem is my dateChanged()-function; the first thing is, that it doesn't work very reliable, the second is that I just check if the date changed, I accordingly don't know if there were one or two days between using the app.
Here's my current function:
public func changeDays()
{
myFormatter.dateStyle = .short
myFormatter.locale = Locale(identifier: "de_DE")
oldDate = defaults.string(forKey: "oldDate")!
let newDate = Date()
let newDateString = myFormatter.string(from: newDate)
if newDateString == oldDate
{
NumberOfDaysInARow.text = "\(days) / 30"
}
else if newDateString != oldDate
{
days += 1
NumberOfDaysInARow.text = "\(days) / 30"
defaults.set(days, forKey: "days")
}
oldDate = newDateString
defaults.set(oldDate, forKey: "oldDate")
}
Just today it started giving me a fatal error when starting the app on my iPhone, did not happen in the simulator though... weird.
How do I have to change my function to make it a 100% reliable (and working) while also being able to check the amount of time between the two dates?
Thank you for having a look! Have a great day
You could extend Date with the function below that returns the amount of days from another date.
extension Date {
// Returns the amount of days from another date
func days(from date: Date) -> Int {
return Calendar.current.dateComponents([.day], from: date, to: self).day ?? 0
}
}
Instead of saving oldDate as a string you can set it to defaults as a date:
defaults.set(Date(), forKey: "oldDate")
You can get the old date from defaults using:
let oldDate = defaults.object(forKey:"oldDate") as! Date
Once you have your old date
let dateNow = Date()
let timeDifference = dateNow.days(from: oldDate!)
If timeDifference > 1 {
// older than 1 day
} else {
// Streak still alive
}
}
If you look in the documentation you will see that Date has a method whose sole purpose is too determine the interval between two dates timeIntervalSince(_:).
If you set the old date to always be 11:59PM on the day it was last used you only have to see if the interval is greater than 24 hours worth of seconds (60 seconds * 60 minutes * 24 hours).
You may want to look at the docs for DateComponents for help creating a date that uses the current date but with a specific time.
Related
I am having a small issue with getting the total days in a month using Swift.
I have extended the Date class and created this function:
func daysInMonth() -> Int {
print(self.day) ##30
print(self.month) ##12
print(self) ## 2021-11-30 23:46:29 +0000
print(Calendar.current.range(of: .day, in: .month, for: self)?.count) ##31
return Calendar.current.range(of: .day, in: .month, for: self)?.count ?? 0
}
I have set the Date&Time to the 30th of November, at 11:45 PM in the settings of my Mac, in Preferences.
I called the above function at 11:46 PM and obtained the above results (inline, next to the print statements).
The date output is correct as well as the day. The month output is wrong and the result is 31 days in the month of November.
If I run this exact same code before 10:00 PM, I get the right result which is 30 days.
Does anyone know why this is happening?
Thank you,
Paprika
It's a GMT offset issue combined with the current day in a month.
When you create a date without set a day, it will be set to the first day of the month.
So, if your timezone offset is for example -4 means your are 4 hours behind the GMT 0 and by default the timezone defined at Calendar.current is equal the system timezone. So what it means? Means you'll obtain the previous month if you test it in a boundary of 23 + (-4) or the next month if your offset is positive.
You can test this behaviour copying'n paste the following code in the Playground.
func getDaysInMonth(month: Int, year: Int, offset: Int = 0) -> Int? {
let someDate = DateComponents(year: year, month: month, hour: 3)
var current = Calendar.current
let timezone = TimeZone(secondsFromGMT: 60 * 60 * offset)!
current.timeZone = timezone
guard let someDay = current.date(from: someDate) else { return nil }
print("date: \(someDay)") // this will always
return someDay.daysInCurrentMonth
}
for hour in -12...12 {
print("hour: \(hour)\ndays: \(getDaysInMonth(month: 10, year: 2021, offset: hour) ?? -1)")
print("---\n")
}
extension Date {
var daysInCurrentMonth: Int? {
Calendar.current.range(of: .day, in: .month, for: self)?.count
}
}
Notice the days will change starting by your current system time zone (notice only the month will change).
How to fix this?
In your case, I guess you just want to show how many days a month have, so you can just set the to zero like this:
TimeZone(secondsFromGMT: 0)
Do this change at a instance of Calendar.current and check if it works for you.
It appears there something wrong with your Date extension methods for .day and .month.
Without seeing code it's hard to determine what the problem is though. Below is some code for returning the current month (Int) and current numbered day of month (Int)
extension Date
{
var month: Int
{
let date = Date()
let calendar = Calendar.current
let components = calendar.dateComponents([.month], from: date)
return components.month
}
var day: Int
{
let date = Date()
let calendar = Calendar.current
let components = calendar.dateComponents([.day], from: self)
return components.day
}
}
Please also ensure your time/date settings are correct on your mac/simulator/device. If these are wrong - it could have been jumping to a different month if you were in a timezone that was ahead a few hours.
I have been searching for a while and haven't found a good answer for this. I currently have when a user posts a photo, it takes an nsdate(timeIntervalSince1970) and puts it as a value when uploaded to firebase. One problem, the current way I change the nsdate to a timeStamp or mins/hours/days since it was posted is not Turing out to good, it does not correct to when a day passes, it just shows what time it was posted at. I was hoping I could get an answer or referred to an algorithm that can change an nsdate(timeIntervalSince1970) to a timeStamp or string that just gives me how many hours ago it was posted, and every 24 hours, change it to 1 day(s). I apologize ahead of time for asking kinda of easy question, I'm guessing. Here is my current code:
if let seconds = posts[visibli.row].timeStamp {
let time = NSDate(timeIntervalSince1970: TimeInterval(seconds))
let formatter = DateFormatter()
formatter.dateFormat = "hh:mm a"
labelTime.text = formatter.string(from: time as Date)
}
Thank you for any Help!
What you are looking for is DateComponentsFormatter. You can look at the different formatters Foundation offers, here.
If you want to have a string that denotes the amount of time that has elapsed since a given date, you can say something like:
if let seconds = posts[visibli.row].timeStamp {
let time = Date(timeIntervalSince1970: TimeInterval(seconds))
let dateComponentsFormater = DateComponentsFormatter()
dateComponentsFormater.unitsStyle = .positional
let string = dateComponentsFormater.string(from: time, to: Date())
labelTime.text = string
}
Current Timestamp as String.
public class func getCurrentTimeStamp()->String{
return String(describing: NSNumber(value: round(NSDate().timeIntervalSince1970 * 1000)))
}
this function use get current timestamp.
I have a value I would like to switch to another once a week either in the background on when the user launches the app on whichever day even if it's more than a week later. As the user uses the app during the week, each day turns a value e.g. variable b to false but every once a week I want to change that variable back to true for any day that it's been turned to false. I have a function I tried putting in the AppDelegate's didFinishLaunchingWithOptions but I am not precisely sure how to do the check and hence it didn't work. This is my attempt:
func resetOnSunday() {
let date = NSDate()
let formatter = NSDateFormatter()
let timeFormatter = NSDateFormatter()
formatter.dateFormat = "EEEE"
let WeekDay = formatter.stringFromDate(date)
timeFormatter.dateFormat = "HH:mm a"
let time = timeFormatter.stringFromDate(date)
if time >= "00:00 AM" && time <= "00:02 AM" && WeekDay == "Sunday" {
var b = true
}
}
Does anyone know how it can be done?
First of all, you can significantly cut down your code (and make it a lot more reliable) by using NSCalendar.
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
//This will return a NSDate optional which you can check against.
let nextSunday = calendar.nextDateAfterDate(date, matchingUnit: .Weekday, value: 1, options: .MatchNextTime)
if date.timeIntervalSinceDate(nextSunday!) > 0 {
//Do your stuff here
}
Note that:
Weekday units are the numbers 1 through n, where n is the number of days in the week. For example, in the Gregorian calendar, n is 7 and Sunday is represented by 1.
Next, I would use NSUserDefaults for your value:
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setBool(true, forKey: "yourVar")
//Then to read it
defaults.boolForKey("yourVar")
I am making a TimeTable app, and i have a method that adds 1 week to the current date, this works as it is supposed to, however if the week transitions from December to January, it adds 1 day extra.
Here is my code:
func getWeekDates(var date: NSDate) -> [NSDate] {
var dates: [NSDate] = [NSDate]()
for var i = 0; i < 5; i++ {
date = date.dateAtWeekStart() + 1.day - 1.week
date += i.day
dates.append(date)
}
return dates
}
And dateAtWeekStart():
func dateAtWeekStart() -> NSDate {
let flags : NSCalendarUnit = [NSCalendarUnit.Year,NSCalendarUnit.Month ,
NSCalendarUnit.WeekOfYear,
NSCalendarUnit.Weekday]
let components = NSCalendar.currentCalendar().components(flags, fromDate: self)
components.weekday = 1 // Sunday
components.hour = self.hour
components.minute = self.minute
components.second = self.second
return NSCalendar.currentCalendar().dateFromComponents(components)!
}
(dateAtWeekStart() is a function made in an extension to NSDate)
The reason i am adding 1 day and removing 1 week, is because dateAtWeekStart returns next sunday, so for example 08-10-2015.dateAtWeekStart() returns 11-10-2015.
So this works fine normally, however if we take this year as an example, 29-12-2015.dateAtWeekStart() returns 04-01-2015 instead of 03-01-2016.
By the way, the region on the device is set to Denmark.
dateAtWeekStart, comes from a helper class called SwiftDate made by malcommac: https://github.com/malcommac/SwiftDate
UPDATE EDIT:
I am still having trouble figuring out how to fix this, i tried adding year to components like so: components.year = self.year, but it sets the year to 2014 for some reason when returning the components..
That dateAtWeekStart() method simply does not work.
[.YearForWeekOfYear, .WeekOfYear] are sufficient as calendar units to
determine the (start of a) week uniquely. The additional units can make
the calculation undetermined. Also you can not just set
components.weekday = 1 because in some regions Monday (2) is the first
day of the week.
So it is actually a bit easier:
extension NSDate {
func dateAtWeekStart() -> NSDate {
let cal = NSCalendar.currentCalendar()
// cal.firstWeekday = 1 // If you insist on Sunday being the first day of the week.
let flags : NSCalendarUnit = [.YearForWeekOfYear, .WeekOfYear]
let components = cal.components(flags, fromDate: self)
return cal.dateFromComponents(components)!
}
}
This should work in all cases and give the start of the week (at midnight) for the given date. There are also other methods
one could use, such as rangeOfUnit().
If you want Sunday to be considered as the first day of the week
instead of using the user's regional settings
then you have to set the firstWeekday property of the calendar.
The code to add days or weeks to a date also looks highly suspicious.
The extensions method for Int in the SwiftDate project treats
a day as 24*60*60 seconds. This is not correct, because in regions with
daylight saving times, a day can have 23 or 25 hours when the clocks
are adjusted. The correct way to add one week to a date is to
use calendar components again:
date = cal.dateByAddingUnit(.WeekOfYear, value: 1, toDate: date, options: [])!
Update for Swift 3:
extension Date {
func dateAtWeekStart() -> Date {
var cal = Calendar.current
// cal.firstWeekday = 1 // If you insist on Sunday being the first day of the week.
let components = cal.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self)
return cal.date(from: components)!
}
}
Question:
I need to compare 2 times - the current time and a set one. If the set time is in the future, find out how many minutes remain until said future time.
Other Info:
I am currently using
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(.CalendarUnitHour | .CalendarUnitMinute, fromDate: date)
let hour = components.hour
let minutes = components.minute
which I stole from another answer on SO about how to get the current time in Int format. I then split the future time into hour (Int) and minutes(Int) and compare those... But that gets odd when you go over the hour barrier.
You have compare function to compare 2 NSDate to know which one is more recent. It returns NSCompareResults
enum NSComparisonResult : Int {
case OrderedAscending
case OrderedSame
case OrderedDescending
}
Get distance (in seconds) from 2 NSDate, you have .timeIntervalSinceDate(). Then, you know how to convert to minutes, hours, ...
let date1 : NSDate = ...
let date2 : NSDate = ...
let compareResult = date1.compare(date2)
let interval = date1.timeIntervalSinceDate(date2)
just to add to #tyt_g207's answer, I found the compare method, but hadn't found NSComparisonResult.OrderedDescending and the others. I used something like the modified below to check an expiration date against today's date
let date1 : NSDate = expirationDate
let date2 : NSDate = NSDate() //initialized by default with the current date
let compareResult = date1.compare(date2)
if compareResult == NSComparisonResult.OrderedDescending {
println("\(date1) is later than \(date2)")
}
let interval = date1.timeIntervalSinceDate(date2)
let dateComparisionResult: NSComparisonResult = currentDate.compare("Your Date")
if dateComparisionResult == NSComparisonResult.OrderedAscending
{
// Current date is smaller than end date.
}
else if dateComparisionResult == NSComparisonResult.OrderedDescending
{
// Current date is greater than end date.
}
else if dateComparisionResult == NSComparisonResult.OrderedSame
{
// Current date and end date are same.
}
Use timeIntervalSinceDate of date on further date and pass the earlier date as parameter, this would give the time difference