NSDate comparison off by one day - swift

I'm trying to compare days between two dates - (current date and a user picked date) - and store the difference in a variable var daysleft = 0
However, the code I'm using makes daysleft always display 1 day too little. Basic terms : The current date and tomorrows date results in the same value (0)
Comparison code;
var daysleft = 0
let DayDifference = userCalendar.components(
dayCalendarUnit,
fromDate: Date,
toDate: dateView.date,
options: [])
daysleft = DayDifference.day
EXTRA CODE;
Here is some extra code if you need to take a look at the user-picked date code;
datePickerView.datePickerMode = UIDatePickerMode.Date
sender.inputView = datePickerView
datePickerView.addTarget(self, action: Selector("datePickerValueChanged:"), forControlEvents: UIControlEvents.ValueChanged)
func datePickerValueChanged(sender:UIDatePicker) {
let dateFormatter = NSDateFormatter()
dateFormatter.dateStyle = NSDateFormatterStyle.MediumStyle
dateFormatter.timeStyle = NSDateFormatterStyle.NoStyle
DateTextField.text = dateFormatter.stringFromDate(sender.date)
dateMakerFormatter.calendar = userCalendar
dateMakerFormatter.dateFormat = "yyyy/MM/dd"
}
EDIT
Here are the dates I'm using for example;
Current date as;
let Date = NSDate()
User-picked date;
var datePickerView:UIDatePicker = UIDatePicker()
let dayCalendarUnit: NSCalendarUnit = [.Day]
let dateFormatter = NSDateFormatter()
datePickerView.datePickerMode = UIDatePickerMode.Date
sender.inputView = datePickerView
datePickerView.addTarget(self, action: Selector("datePickerValueChanged:"), forControlEvents: UIControlEvents.ValueChanged)
func datePickerValueChanged(sender:UIDatePicker) {
let dateFormatter = NSDateFormatter()
dateFormatter.dateStyle = NSDateFormatterStyle.MediumStyle
dateFormatter.timeStyle = NSDateFormatterStyle.NoStyle
DateTextField.text = dateFormatter.stringFromDate(sender.date)
dateMakerFormatter.calendar = userCalendar
dateMakerFormatter.dateFormat = "yyyy/MM/dd"
}
Comparison;
dayCalendarUnit,
fromDate: Date,
toDate: datePickerView.date,
options: [])
daysleft = DayDifference.day

The problem is that there is not a full day difference (24+hours) between the two dates.
You need to get the day for each with component:fromDate: and subtract the days. You may need to handle months and years.
or
Set the time to 0 for each prior to the calculating the day difference with components:fromDate:toDate:options:.

Because your today has time so the difference to the user's picked date is less than 24 hours away.
You need to trim the time components off:
let userCalendar = NSCalendar.currentCalendar()
// Only get the Year, Month, Day of `now`
let components = userCalendar.components([.Year, .Month, .Day], fromDate: NSDate())
let today = userCalendar.dateFromComponents(components)!
// Assume the user picked Mar 28, 2016
let userPickedDate = userCalendar.dateWithEra(1, year: 2016, month: 3, day: 28, hour: 0, minute: 0, second: 0, nanosecond: 0)!
let dayDifference = userCalendar.components([.Day], fromDate: today, toDate: userPickedDate, options: [])
print(today)
print(userPickedDate)
print(dayDifference.day)

Related

Format hour and minute integers as the String "9:30 am"

How can I express hour and minute integer values as a String formatted like "9:30 am"?
Currently, I have:
let hour: Int = 9
let minute: Int = 30
var dateComponents = DateComponents()
dateComponents.hour = hour
dateComponents.minute = minute
let time: String = DateComponentsFormatter.localizedString(from: dateComponents, unitsStyle: DateComponentsFormatter.UnitsStyle.positional)
print(time) // Prints "9:30", not "9:30 am"
I know I can manually concatenate the time meridian at the end, but I'm hoping there's a built in function for this. Perhaps a different UnitsStyle?
You could use DateFormatter to achieve this.
let formatter = DateFormatter()
formatter.dateFormat = "hh:mm a"
formatter.amSymbol = "am"
formatter.pmSymbol = "pm"
let dateString = formatter.string(from: Date())
print(dateString) // prints "12:17 pm"
If you want to only include single digits for the hour, then you only include one "h" in the dateFormat:
formatter.dateFormat = "h:mm a" // prints "1:30 pm" instead of "01:30 pm"
let formatter = DateFormatter()
formatter.dateFormat = "hh:mm a"
formatter.calendar = .current
let hour: Int = 9
let minute: Int = 30
var dateComponents = DateComponents()
dateComponents.hour = hour
dateComponents.minute = minute
If let fixedDate: Date = Calendar.current.date(from: dateComponents) {
let formattedString = formatter.string(from: fixedDate)
print(formattedString) //prints 09:30 AM
}
You need to add current calendar/date to get am or pm from your time from what I know.
EDIT:
Thanks to Leo Dabus in the comments for pointing this out: the above method will result in a date that is on January 1st 0001, if the date is important for you you have to specify the date (day/month/year)
for example:
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "hh:mm a"
formatter.calendar = .current
let hour: Int = 9
let minute: Int = 30
var dateComponents = DateComponents()
dateComponents.hour = hour
dateComponents.minute = minute
dateComponents.year = Calendar.current.component(.year, from: date)
dateComponents.month = Calendar.current.component(.month, from: date)
dateComponents.day = Calendar.current.component(.day, from: date)
If let fixedDate: Date = Calendar.current.date(from: dateComponents) {
let formattedString = formatter.string(from: fixedDate)
print(formattedString)
}

how to use DateComponentsFormatter for showing date as "Today" or "Yesterday"?

I'm using swift in my project and I want to show specific date like this: 5 days ago or 5 month ago or ...
I am using DateComponentsFormatter and it's doing well, but the problem is I want to show 1 day ago like "Yesterday" or showing 3 second ago like "Today". how can I do this? can I use DateComponentsFormatter for this problem? this is my codes:
func shortDate(_ local: LocaleIdentifier = .fa) -> String {
let now = Date()
let date = self
let formatter = DateComponentsFormatter()
formatter.calendar?.locale = Locale(identifier: local.rawValue)
formatter.unitsStyle = .full
formatter.maximumUnitCount = 1
formatter.allowedUnits = [.year, .month, .day, .hour, .minute, .second]
guard let timeString = formatter.string(from: date, to: now) else {
return ""
}
return String(format: "Ago".localized, timeString)
}
For iOS 13 or later you can use RelativeDateTimeFormatter
let relativeDateTimeFormatter = RelativeDateTimeFormatter()
relativeDateTimeFormatter.dateTimeStyle = .named
relativeDateTimeFormatter.unitsStyle = .full
let date = Date.init(timeIntervalSinceNow: -60*60*24)
relativeDateTimeFormatter.string(for: date) // "yesterday"
edit/update:
If you would like to support iOS 11 you would need to implement your own relative date formatter. You can use Calendar method isDateInToday and isDateInYesterday to combine a relative date formatter with date components formatter. Note that there is no need to check the time interval for setting a single unit in your date components formatter you can set the allowed units of your date components formatter, just make sure you set them respecting the desired priority to be displayed:
// This will avoid creating a formatter every time you call relativeDateFormatted property
extension Formatter {
static let dateComponents: DateComponentsFormatter = {
let dateComponentsFormatter = DateComponentsFormatter()
dateComponentsFormatter.allowedUnits = [.day, .month, .year] // check the order of the units it does matter when allowing only 1 unit to be displayed
dateComponentsFormatter.maximumUnitCount = 1
dateComponentsFormatter.unitsStyle = .full
return dateComponentsFormatter
}()
static let relativeDate: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.doesRelativeDateFormatting = true
dateFormatter.timeStyle = .none
dateFormatter.dateStyle = .medium
return dateFormatter
}()
}
extension Date {
var relativeDateFormatted: String {
Calendar.current.isDateInToday(self) || Calendar.current.isDateInYesterday(self) ?
Formatter.relativeDate.string(from: self) :
Formatter.dateComponents.string(from: self, to: Date()) ?? ""
}
}
Playground testing:
let date1 = DateComponents(calendar: .current, year: 2020, month: 9, day: 4, hour: 5).date!
let date2 = DateComponents(calendar: .current, year: 2020, month: 9, day: 3, hour: 23, minute: 50).date!
let date3 = DateComponents(calendar: .current, year: 2020, month: 8, day: 25, hour: 10).date!
let date4 = DateComponents(calendar: .current, year: 2020, month: 8, day: 3).date!
let date5 = DateComponents(calendar: .current, year: 2019, month: 8, day: 27).date!
date1.relativeDateFormatted // "Today"
date2.relativeDateFormatted // "Yesterday"
date3.relativeDateFormatted // "10 days"
date4.relativeDateFormatted // "1 month"
date5.relativeDateFormatted // "1 year"
You are looking for the doesRelativeDateFormatting property of a DateFormatter.
I think, I must use both DateComponentsFormatter for more than 1 day and DateFormatter for showing Today(day=0) and Yesterday(day=1) for my problem.
I found my answer here.
but I make codes a little better, this is my codes:
var calendar = Calendar.current
calendar.locale = Locale(identifier: local.rawValue)
let componentFormatter = DateComponentsFormatter()
componentFormatter.calendar = calendar
componentFormatter.unitsStyle = .full
componentFormatter.maximumUnitCount = 1
let interval = Calendar.current.dateComponents([.year, .month, .day], from: self, to: Date())
if let year = interval.year, year > 0 {
componentFormatter.allowedUnits = .year
} else if let month = interval.month, month > 0 {
componentFormatter.allowedUnits = .month
} else if let day = interval.day, day > 1 {
componentFormatter.allowedUnits = .day
} else { // if let day = interval.day, day == 0 || day == 1 for Today or Yesterday
let formatter = DateFormatter()
formatter.doesRelativeDateFormatting = true
formatter.calendar.locale = Locale(identifier: local.rawValue)
formatter.timeStyle = .none
formatter.dateStyle = .medium
return formatter.string(from: self)
}
guard let timeString = componentFormatter.string(from: self, to: Date()) else {
return ""
}
return String(format: "Ago".localized, timeString)

How to change the current day's hours and minutes in Swift?

If I create a Date() to get the current date and time, I want to create a new date from that but with different hour, minute, and zero seconds, what's the easiest way to do it using Swift? I've been finding so many examples with 'getting' but not 'setting'.
Be aware that for locales that uses Daylight Saving Times, on clock change days, some hours may not exist or they may occur twice. Both solutions below return a Date? and use force-unwrapping. You should handle possible nil in your app.
Swift 3+ and iOS 8 / OS X 10.9 or later
let date = Calendar.current.date(bySettingHour: 9, minute: 30, second: 0, of: Date())!
Swift 2
Use NSDateComponents / DateComponents:
let gregorian = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let now = NSDate()
let components = gregorian.components([.Year, .Month, .Day, .Hour, .Minute, .Second], fromDate: now)
// Change the time to 9:30:00 in your locale
components.hour = 9
components.minute = 30
components.second = 0
let date = gregorian.dateFromComponents(components)!
Note that if you call print(date), the printed time is in UTC. It's the same moment in time, just expressed in a different timezone from yours. Use a NSDateFormatter to convert it to your local time.
swift 3 date extension with timezone
extension Date {
public func setTime(hour: Int, min: Int, sec: Int, timeZoneAbbrev: String = "UTC") -> Date? {
let x: Set<Calendar.Component> = [.year, .month, .day, .hour, .minute, .second]
let cal = Calendar.current
var components = cal.dateComponents(x, from: self)
components.timeZone = TimeZone(abbreviation: timeZoneAbbrev)
components.hour = hour
components.minute = min
components.second = sec
return cal.date(from: components)
}
}
//Increase the day & hours in Swift
let dateformat = DateFormatter()
let timeformat = DateFormatter()
dateformat.dateStyle = .medium
timeformat.timeStyle = .medium
//Increase Day
let currentdate = Date()
let currentdateshow = dateformat.string(from: currentdate)
textfield2.text = currentdateshow
let myCurrentdate = dateformat.date(from: dateTimeString)!
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: myCurrentdate) // Increase 1 Day
let tomorrowday = dateformat.string(from: tomorrow!)
text3.text = tomorrowday
text3.isEnabled = false
//increase Time
let time = Date()
let currenttime = timeformat.string(from: time)
text4.text = currenttime
let mycurrenttime = timeformat.date(from: currenttime)!
let increasetime = Calendar.current.date(byAdding: .hour, value: 2, to: mycurrenttime) //increase 2 hrs.
let increasemytime = timeformat.string(from: increasetime!)
text5.text = increasemytime

How to make a countdown to date Swift

I was facing the struggle of making a timer app, so I thought that now that I solved it I could help others who face the problem. So basically this app counts down to a specific date from the current time. As stack overflow allows a Q and A format I hope that can help you. See the comments for explanations.
Cleaned up and updated with countdown computed on a timer and leading zero String format.
let futureDate: Date = {
var future = DateComponents(
year: 2020,
month: 1,
day: 1,
hour: 0,
minute: 0,
second: 0
)
return Calendar.current.date(from: future)!
}()
var countdown: DateComponents {
return Calendar.current.dateComponents([.day, .hour, .minute, .second], from: Date(), to: futureDate)
}
#objc func updateTime() {
let countdown = self.countdown //only compute once per call
let days = countdown.day!
let hours = countdown.hour!
let minutes = countdown.minute!
let seconds = countdown.second!
countdownLabel.text = String(format: "%02d:%02d:%02d:%02d", days, hours, minutes, seconds)
}
func runCountdown() {
Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
}
Here is the solution of how I managed to create a countdown timer to a specific NSDate, for SO allows Q and A Style Answers.
// here we set the current date
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(.CalendarUnitHour | .CalendarUnitMinute | .CalendarUnitMonth | .CalendarUnitYear | .CalendarUnitDay, fromDate: date)
let hour = components.hour
let minutes = components.minute
let month = components.month
let year = components.year
let day = components.day
let currentDate = calendar.dateFromComponents(components)
// here we set the due date. When the timer is supposed to finish
let userCalendar = NSCalendar.currentCalendar()
let competitionDate = NSDateComponents()
competitionDate.year = 2015
competitionDate.month = 6
competitionDate.day = 21
competitionDate.hour = 08
competitionDate.minute = 00
let competitionDay = userCalendar.dateFromComponents(competitionDate)!
// Here we compare the two dates
competitionDay.timeIntervalSinceDate(currentDate!)
let dayCalendarUnit: NSCalendarUnit = (.CalendarUnitDay | .CalendarUnitHour | .CalendarUnitMinute)
//here we change the seconds to hours,minutes and days
let CompetitionDayDifference = userCalendar.components(
dayCalendarUnit, fromDate: currentDate!, toDate: competitionDay,
options: nil)
//finally, here we set the variable to our remaining time
var daysLeft = CompetitionDayDifference.day
var hoursLeft = CompetitionDayDifference.hour
var minutesLeft = CompetitionDayDifference.minute
Hope that helps you guys if you're facing the same struggle as I have
Cleaned up/updated for latest Swift version of the accepted answer.
// here we set the current date
let date = NSDate()
let calendar = Calendar.current
let components = calendar.dateComponents([.hour, .minute, .month, .year, .day], from: date as Date)
let currentDate = calendar.date(from: components)
let userCalendar = Calendar.current
// here we set the due date. When the timer is supposed to finish
let competitionDate = NSDateComponents()
competitionDate.year = 2017
competitionDate.month = 4
competitionDate.day = 16
competitionDate.hour = 00
competitionDate.minute = 00
let competitionDay = userCalendar.date(from: competitionDate as DateComponents)!
//here we change the seconds to hours,minutes and days
let CompetitionDayDifference = calendar.dateComponents([.day, .hour, .minute], from: currentDate!, to: competitionDay)
//finally, here we set the variable to our remaining time
let daysLeft = CompetitionDayDifference.day
let hoursLeft = CompetitionDayDifference.hour
let minutesLeft = CompetitionDayDifference.minute
print("day:", daysLeft ?? "N/A", "hour:", hoursLeft ?? "N/A", "minute:", minutesLeft ?? "N/A")
//Set countdown label text
countDownLabel.text = "\(daysLeft ?? 0) Days, \(hoursLeft ?? 0) Hours, \(minutesLeft ?? 0) Minutes"
This worked for me.
The only thing that troubles me is that it doesn't really countdown as the user has to refresh the page for it to recount. You can see it "counting" when the user is scrolling up and down cells on a UITableView as the cells do refresh the view.
Another thing is that I have on NSTimeZone of the currentDate "GMT+2:00" as it works for my time but only because I haven't figured out how to use the device NSTimeZone yet.
let releaseDate = "2015-05-02'T'22:00:00:000Z"
let futureDateFormatter = NSDateFormatter()
futureDateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let date: NSDate = futureDateFormatter.dateFromString(releaseDate!)!
let currentDate = NSDate();
let currentFormatter = NSDateFormatter();
currentFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
currentFormatter.timeZone = NSTimeZone(abbreviation: "GMT+2:00")
let diffDateComponents = NSCalendar.currentCalendar().components([NSCalendarUnit.Month, NSCalendarUnit.Day, NSCalendarUnit.Hour, NSCalendarUnit.Minute], fromDate: currentDate, toDate: date, options: NSCalendarOptions.init(rawValue: 0))
let countdown = "\(diffDateComponents.month) m: \(diffDateComponents.day) d: \(diffDateComponents.hour) h: \(diffDateComponents.minute) min"

Calculate age from birth date using NSDateComponents in Swift

I am trying calculate the age from birthdayDate in Swift with this function:
var calendar : NSCalendar = NSCalendar.currentCalendar()
var dateComponentNow : NSDateComponents = calendar.components(
NSCalendarUnit.CalendarUnitYear,
fromDate: birthday,
toDate: age,
options: 0)
But I get an error Extra argument toDate in call
In objective c this was the code, but I don't know why get this error:
NSDate* birthday = ...;
NSDate* now = [NSDate date];
NSDateComponents* ageComponents = [[NSCalendar currentCalendar]
components:NSYearCalendarUnit
fromDate:birthday
toDate:now
options:0];
NSInteger age = [ageComponents year];
Is there correct form better than this?
You get an error message because 0 is not a valid value for NSCalendarOptions.
For "no options", use NSCalendarOptions(0) or simply nil:
let ageComponents = calendar.components(.CalendarUnitYear,
fromDate: birthday,
toDate: now,
options: nil)
let age = ageComponents.year
(Specifying nil is possible because NSCalendarOptions conforms to the RawOptionSetType protocol which in turn inherits
from NilLiteralConvertible.)
Update for Swift 2:
let ageComponents = calendar.components(.Year,
fromDate: birthday,
toDate: now,
options: [])
Update for Swift 3:
Assuming that the Swift 3 types Date and Calendar are used:
let now = Date()
let birthday: Date = ...
let calendar = Calendar.current
let ageComponents = calendar.dateComponents([.year], from: birthday, to: now)
let age = ageComponents.year!
I create this method its very easy just put the birthday date in the method and this will return the Age as a Int
Swift 3
func calcAge(birthday: String) -> Int {
let dateFormater = DateFormatter()
dateFormater.dateFormat = "MM/dd/yyyy"
let birthdayDate = dateFormater.date(from: birthday)
let calendar: NSCalendar! = NSCalendar(calendarIdentifier: .gregorian)
let now = Date()
let calcAge = calendar.components(.year, from: birthdayDate!, to: now, options: [])
let age = calcAge.year
return age!
}
Swift 2
func calcAge(birthday: String) -> Int{
let dateFormater = NSDateFormatter()
dateFormater.dateFormat = "MM/dd/yyyy"
let birthdayDate = dateFormater.dateFromString(birthday)
let calendar: NSCalendar! = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
let now: NSDate! = NSDate()
let calcAge = calendar.components(.Year, fromDate: birthdayDate!, toDate: now, options: [])
let age = calcAge.year
return age
}
Usage
print(calcAge("06/29/1988"))
For swift 4 works fine
func getAgeFromDOF(date: String) -> (Int,Int,Int) {
let dateFormater = DateFormatter()
dateFormater.dateFormat = "YYYY-MM-dd"
let dateOfBirth = dateFormater.date(from: date)
let calender = Calendar.current
let dateComponent = calender.dateComponents([.year, .month, .day], from:
dateOfBirth!, to: Date())
return (dateComponent.year!, dateComponent.month!, dateComponent.day!)
}
let age = getAgeFromDOF(date: "2000-12-01")
print("\(age.0) Year, \(age.1) Month, \(age.2) Day")
This works for Swift 3
let myDOB = Calendar.current.date(from: DateComponents(year: 1994, month: 9, day: 10))!
let myAge = Calendar.current.dateComponents([.month], from: myDOB, to: Date()).month!
let years = myAge / 12
let months = myAge % 12
print("Age : \(years).\(months)")
This is working in swift 3 for me..
let now = NSDate()
let calendar : NSCalendar = NSCalendar.current as NSCalendar
let ageComponents = calendar.components(.year, from: datePickerView.date, to: now as Date, options: [])
let age = ageComponents.year!
ageCalculated.text = String(age)
Thanks to #Martin R
//Create string extension to make more easy
extension String {
func getDate(format: String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
return dateFormatter.date(from: self) ?? Date()
}
}
Get today's date and your birthday date
let today = Date()
let birthDate = "1990-01-01".getDate(format: "yyyy-MM-dd")
Create an instance of the user's current calendar
let calendar = Calendar.current
Use calendar to get difference between two dates
let components = calendar.dateComponents([.year, .month, .day], from: birthDate, to: today)
let ageYears = components.year //get how many years old
let ageMonths = components.month //extra months
let ageDays = components.day // extra days
This is the best way on swift 5
lazy var dateFormatter : DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}()
let birthday = dateFormatter.date(from: "1980-04-25")
let timeInterval = birthday?.timeIntervalSinceNow
let age = abs(Int(timeInterval! / 31556926.0))