What would be a possible way to update labels that displays static time received once from API.I have a tableView where each cell displays city name current temperature and time just like the native iPhone WeatherApp.Is there a way to observe the device clock minutes so I can trigger some code to update the times when a minute goes by?
You can subclass a UILabel and add a timer to it so that it autoupdates itself:
Considering your last question where you get the timeZone offset from GMT from your API, you can subclass a UILabel, add a timeZone property to it and a didSet closure to setup a timer to fire at the next even minute with a 60 seconds interval and set it to repeat. Add a selector to update its label every time this method is called:
class Clock: UILabel {
let dateFormatter = DateFormatter()
var timer = Timer()
var timeZone: Int = 0 {
didSet {
dateFormatter.timeStyle = .short
dateFormatter.timeZone = TimeZone(secondsFromGMT: timeZone)
var components = Date().components
components.minute! += 1
components.second = 0
components.nanosecond = 0
timer = .init(fireAt: components.date!, interval: 60, target: self, selector: #selector(update), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .common)
update()
}
}
#objc func update(_ timer: Timer? = nil) {
text = dateFormatter.string(from: Date())
}
}
extension Date {
var components: DateComponents {
Calendar.current.dateComponents(in: .current, from: self)
}
}
Now you can just create your clock label and when you setup its timeZone property it will start running automatically:
let clock: Clock = .init(frame: .init(origin: .zero,
size: .init(width: 200, height: 30)))
clock.timeZone = -14400
clock.text // "11:01 PM"
I want to add / or subtract a day from a selected date. The user selects for example 09-10-2019, when user press a button, the date should be 10-10-2019.
I am not trying to add or reduce a day from Current date.
On the previous screen , I have a variable -> Which is user selected and is not static
selecteddate = "09.10.2019"
I declared it as a global variable so it can be used in several screens, so in this screen , I get this 'selected date'.
I have 3 UIButtons. yesterdaybutton, selecteddatebutton and tomorrowbutton
When user presses yesterday button then 1 day should be reduced from
'selecteddate' variable
When user presses tomorrow button then 1 day should be added to
'selecteddate' variable.
I followed what the answer said. But unfortunately my selecteddate was a string and I needed to sent string to api in its format.
So , I did the following.
let dateFormattera = DateFormatter()
dateFormattera.dateFormat = "dd.MM.yyyy"
let date = dateFormattera.date(from: datetoday)
let newdate = Calendar.current.date(byAdding: .day, value: +1, to: date!)
let dateFormatterb = DateFormatter()
dateFormatterb.dateFormat = "dd.MM.yyyy"
let tomorrowdate = dateFormatterb.string(from: newdate!)
let dateFormatterx = DateFormatter()
let day = dateFormatterx.date(from: datetoday)
let newday = Calendar.current.date(byAdding: .day, value: +1, to: date!)
let dateFormattery = DateFormatter()
dateFormattery.dateFormat = "EEE"
let tomorrowday = dateFormattery.string(from: newdate!)
There might be junk code in this, but this made everything work as it should. This gets the DATE and DAY. I had to use this because converting string to date added +1 day to the date (due to UTC timing 18:00) despite doing -1 or +1
I suggest that you use:
Calendar.current.date(byAdding: .day, value: 1, to: selecteddate)
This will add one day to selecteddate
Then, if you have 2 buttons, as you have explained, you could set a tag to each button:
yesterdaybutton.tag = -1
tomorrowbutton.tag = 1
We will use each button's tag to add or reduce days from selecteddate
So, both buttons will use this:
#IBAction func buttonAction(_ sender: UIButton) {
let newDate = Calendar.current.date(byAdding: .day, value: sender.tag, to: selecteddate)
}
You can create a method like below that calculates the date.
func calculateDate(fromDate date: Date, withUnit value: Int) -> Date? {
return Calendar.current.date(byAdding: .day, value: value, to: date)
}
How to use it?
When yesterday button is selected then you need call method like below...
if let date = calculateDate(fromDate: selecteddate, withUnit: -1) {
print(date)
} else {
print("date is nil. may be due to different formats of dates")
}
When tomorrow button is selected then you need call method like below...
if let date = calculateDate(fromDate: selecteddate, withUnit: 1) {
print(date)
} else {
print("date is nil. may be due to different formats of dates")
}
Note: Please use date format if required. The date format should be same for all dates.
You still need to convert your string to a concrete object which in your case is Date. To it you can add or subtract components as you wish. In your case those are days alone. Check the following:
func addDays(_ days: Int, toDate dateString: String?) throws -> String {
guard let dateString = dateString else { throw NSError(domain: "Add days", code: 400, userInfo: ["dev_message": "String is null"]) }
let formatter = DateFormatter()
formatter.dateFormat = "dd'.'MM'.'yyyy"
// formatter.dateFormat = "MM'.'dd'.'yyyy" // Not sure which
guard let date = formatter.date(from: dateString) else { throw NSError(domain: "Add days", code: 400, userInfo: ["dev_message": "Incorrect date format. Expecting \(formatter.dateFormat!)"]) }
guard let newDate = formatter.calendar.date(byAdding: {
var components: DateComponents = DateComponents()
components.day = days
return components
}(), to: date) else { throw NSError(domain: "Add days", code: 400, userInfo: ["dev_message": "Could not add days for some reason"]) }
return formatter.string(from: newDate)
}
A usage I tried was:
print(try! addDays(1, toDate: "09.10.2019")) // Prints 10.10.2019
print(try! addDays(30, toDate: "09.10.2019")) // Prints 08.11.2019
print(try! addDays(-1, toDate: "09.10.2019")) // Prints 08.10.2019
So in your case you need addDays(1, toDate: "09.10.2019")to get to next day and addDays(-1, toDate: "09.10.2019") for previous day.
This is a regular timer (chrono) with minutes and seconds.
It works fine but is there a way to have the same result with an NSDateFormatter.
var min = 0
var sec = 0
func stringFromTimeInterval(interval: NSTimeInterval) -> String {
let interval = Int(interval)
let seconds = interval % 60
let minutes = (interval / 60)
return String(format: "%2d:%02d",minutes, seconds)
}
func updateTimer(){
sec++
self.timeElapsed.text = self.stringFromTimeInterval(Double(sec))
}
Here is my attempt :
var sec = 0
func theTime(interval:NSTimeInterval) -> String {
var dateFormatter = NSDateFormatter()
var date = NSDate(timeIntervalSince1970: NSTimeInterval())
dateFormatter.dateFormat = "mm:ss"
return dateFormatter.stringFromDate(date)
}
func updateTheTime(){
sec++
self.newTimer.text = self.theTime(Double(sec))
}
I'm doing something wrong because it is always 00:00.
How can I make it work?
You can use NSDate and NSDateFormatter to get time string.
create an object of NSDateFormatter and NSDate, then set the style of your time to NSDateFormmatter, then call stringFromDate to get time string.
var dateFormatter = NSDateFormatter()
var date = NSDate(timeIntervalSince1970: interval)
dateFormatter.dateFormat = "mm-ss"
dateFormatter.stringFromDate(date)
You can use NSDateComponentsFormatter.
class ViewController: UIViewController {
// 1
var startDate: NSDate!
// 2
#IBOutlet weak var timerLabel: UILabel!
// 3
func tick() {
// 4
let elapsed = NSDate().timeIntervalSinceDate(startDate)
// 5
let formattedTime = NSDateComponentsFormatter().stringFromTimeInterval(elapsed)
// 6
timerLabel.text = formattedTime
}
// 7
#IBAction func startButtonPressed() {
// 8
startDate = NSDate()
// 9
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "tick", userInfo: nil, repeats: true)
}
}
Here's what's happening in that Swift code:
The startDate property will be initialized in startButtonPressed()
The timerLabel will show the output. It is a standard UILabel.
tick() will be called from an NSTimer
elapsed calculates how much time has transpired since the startDate. NSDate() is a handy way to get the current date.
This is where the magic happens. The NSDateComponentsFormatter will output a correctly trimmed String for the elapsed time.
The formattedTime is displayed on the screen
startButtonPressed() is called when the user taps the Start button.
The startDate property is set to the current date.
An NSTimer will call tick() every 1 second.
Here's a screenshot of what it looks like in practice with:
four seconds elapsed
one minute and four seconds elapsed
I'm making an alarm clock where it will tell you how many hours and minutes of sleep you get. I set up a UIDatePicker where the user chooses what time they wanna wake up. It also tells the exact time to the very second. The part that I'm stuck on is how many hours of sleep they are going to get. I tried just basically subtracting the exact time from the UIDatePicker. This worked if they were both in the AM. For example if the user wanted to wake up at 10:30 AM and it is 9:30 AM all you have to do is subtract 10:30 from 9:30 to get 1 hour. I soon realized this wouldn't work if they were different time of days e.g. AM or PM.
How I got the time from UIDatePicker
func handler(sender: UIDatePicker) {
var timeFormatter = NSDateFormatter()
timeFormatter.timeStyle = NSDateFormatterStyle.ShortStyle
var strDate = timeFormatter.stringFromDate(theDatePicker.date)
}
theDatePicker.addTarget(self, action: Selector("handler:"), forControlEvents: UIControlEvents.ValueChanged)
How I got the exact time
var date = NSDate()
var outputFormat = NSDateFormatter()
outputFormat.locale = NSLocale(localeIdentifier:"en_US")
outputFormat.dateFormat = "HH:mm:ss"
timeLabel.text = (outputFormat.stringFromDate(date))
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("getTime"), userInfo: nil, repeats: true)
My Question:
How do I subtract the UIDatePicker from the exact time to get the hours of sleep the user is getting?
You can use NSCalendar method components:fromDate:toDate:options:, for example:
#IBAction func valueChangedForPicker(sender: UIDatePicker) {
let now = NSDate()
let wakeUpTime = sender.date
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(.HourCalendarUnit | .MinuteCalendarUnit | .SecondCalendarUnit, fromDate: now, toDate: wakeUpTime, options: nil)
println(String(format: "%02d:%02d:%02d", components.hour, components.minute, components.second))
}
If you're getting negative values, that's because fromDate is not before toDate. In this case, if you're dealing with a NSDatePicker with time only, you might want to adjust the time of the wakeUpTime to make sure it is in the future.
var wakeUpTime = datePicker.date
if wakeUpTime.compare(now) == .OrderedAscending {
wakeUpTime = calendar.dateByAddingUnit(.DayCalendarUnit, value: 1, toDate: wakeUpTime, options: nil)!
}
Here is an example from a Swift playground:
// Setting up a date since I don't have a UIDatePicker
let dateString = "2014-11-12 07:25"
let dateFormatter: NSDateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd hh:mm"
var wakeupTime: NSDate = dateFormatter.dateFromString(dateString)!
// var wakeupTime: NSDate = theDatePicker.date
let fromDate = NSDate()
let gregorianCalendar: NSCalendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)!
let flags: NSCalendarUnit = .HourCalendarUnit | .MinuteCalendarUnit
let components = gregorianCalendar.components(flags, fromDate: fromDate, toDate: wakeupTime, options: NSCalendarOptions(0))
println("\(components.hour) hours, \(components.minute) minutes")
In a swift playground, I have been using
NSDate.date()
But, this always appears with the time element appended. For my app I need to ignore the time element. Is this possible in Swift? How can it be done? Even if I could set the time element to be the same time on every date that would work too.
Also, I am trying to compare two dates and at the moment I am using the following code:
var earlierDate:NSDate = firstDate.earlierDate(secondDate)
Is this the only way or can I do this in a way that ignores the time element? For instance I don't want a result if they are the same day, but different times.
Use this Calendar function to compare dates in iOS 8.0+
func compare(_ date1: Date, to date2: Date, toGranularity component: Calendar.Component) -> ComparisonResult
passing .day as the unit
Use this function as follows:
let now = Date()
// "Sep 23, 2015, 10:26 AM"
let olderDate = Date(timeIntervalSinceNow: -10000)
// "Sep 23, 2015, 7:40 AM"
var order = Calendar.current.compare(now, to: olderDate, toGranularity: .hour)
switch order {
case .orderedDescending:
print("DESCENDING")
case .orderedAscending:
print("ASCENDING")
case .orderedSame:
print("SAME")
}
// Compare to hour: DESCENDING
var order = Calendar.current.compare(now, to: olderDate, toGranularity: .day)
switch order {
case .orderedDescending:
print("DESCENDING")
case .orderedAscending:
print("ASCENDING")
case .orderedSame:
print("SAME")
}
// Compare to day: SAME
Xcode 11.2.1, Swift 5 & Above
Checks whether the date has same day component.
Calendar.current.isDate(date1, equalTo: date2, toGranularity: .day)
Adjust toGranularity as your need.
There are several useful methods in NSCalendar in iOS 8.0+:
startOfDayForDate, isDateInToday, isDateInYesterday, isDateInTomorrow
And even to compare days:
func isDate(date1: NSDate!, inSameDayAsDate date2: NSDate!) -> Bool
To ignore the time element you can use this:
var toDay = Calendar.current.startOfDay(for: Date())
But, if you have to support also iOS 7, you can always write an extension
extension NSCalendar {
func myStartOfDayForDate(date: NSDate!) -> NSDate!
{
let systemVersion:NSString = UIDevice.currentDevice().systemVersion
if systemVersion.floatValue >= 8.0 {
return self.startOfDayForDate(date)
} else {
return self.dateFromComponents(self.components(.CalendarUnitYear | .CalendarUnitMonth | .CalendarUnitDay, fromDate: date))
}
}
}
In Swift 4:
func compareDate(date1:Date, date2:Date) -> Bool {
let order = NSCalendar.current.compare(date1, to: date2, toGranularity: .day)
switch order {
case .orderedSame:
return true
default:
return false
}
}
I wrote the following method to compare two dates by borrowing from Ashley Mills solution. It compares two dates and returns true if the two dates are the same (stripped of time).
func compareDate(date1:NSDate, date2:NSDate) -> Bool {
let order = NSCalendar.currentCalendar().compareDate(date1, toDate: date2,
toUnitGranularity: .Day)
switch order {
case .OrderedSame:
return true
default:
return false
}
}
And it is called like this:
if compareDate(today, date2: anotherDate) {
// The two dates are on the same day.
}
Two Dates comparisions in swift.
// Date comparision to compare current date and end date.
var dateComparisionResult:NSComparisonResult = currentDate.compare(endDate)
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.
}
I wrote a Swift 4 extension for comparing two dates:
import Foundation
extension Date {
func isSameDate(_ comparisonDate: Date) -> Bool {
let order = Calendar.current.compare(self, to: comparisonDate, toGranularity: .day)
return order == .orderedSame
}
func isBeforeDate(_ comparisonDate: Date) -> Bool {
let order = Calendar.current.compare(self, to: comparisonDate, toGranularity: .day)
return order == .orderedAscending
}
func isAfterDate(_ comparisonDate: Date) -> Bool {
let order = Calendar.current.compare(self, to: comparisonDate, toGranularity: .day)
return order == .orderedDescending
}
}
Usage:
startDate.isSameDateAs(endDate) // returns a true or false
For iOS7 support
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let date1String = dateFormatter.stringFromDate(date1)
let date2String = dateFormatter.stringFromDate(date2)
if date1String == date2String {
println("Equal date")
}
You can compare two dates using it's description.
let date1 = NSDate()
let date2 = NSDate(timeIntervalSinceNow: 120)
if date1.description == date2.description {
print(true)
} else {
print(false) // false (I have added 2 seconds between them)
}
If you want set the time element of your dates to a different time you can do as follow:
extension NSDate {
struct Calendar {
static let gregorian = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
}
var day: Int { return Calendar.gregorian.component(.Day, fromDate: self) }
var month: Int { return Calendar.gregorian.component(.Month, fromDate: self) }
var year: Int { return Calendar.gregorian.component(.Year, fromDate: self) }
var noon: NSDate {
return Calendar.gregorian.dateWithEra(1, year: year, month: month, day: day, hour: 12, minute: 0, second: 0, nanosecond: 0)!
}
}
let date1 = NSDate()
let date2 = NSDate(timeIntervalSinceNow: 120)
print(date1.noon == date2.noon) // true
or you can also do it using NSDateFormatter:
extension NSDate {
struct Date {
static let formatterYYYYMMDD: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyyMMdd"
return formatter
}()
}
var yearMonthDay: String {
return Date.formatterYYYYMMDD.stringFromDate(self)
}
func isSameDayAs(date:NSDate) -> Bool {
return yearMonthDay == date.yearMonthDay
}
}
let date1 = NSDate()
let date2 = NSDate(timeIntervalSinceNow: 120)
print(date1.yearMonthDay == date2.yearMonthDay) // true
print(date1.isSameDayAs(date2)) // true
Another option (iOS8+) is to use calendar method isDate(inSameDayAsDate:):
extension NSDate {
struct Calendar {
static let gregorian = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
}
func isInSameDayAs(date date: NSDate) -> Bool {
return Calendar.gregorian.isDate(self, inSameDayAsDate: date)
}
}
let date1 = NSDate()
let date2 = NSDate(timeIntervalSinceNow: 120)
if date1.isInSameDayAs(date: date2 ){
print(true) // true
} else {
print(false)
}
Swift 3
let order = NSCalendar.current.compare(date1, to: date2, toGranularity: .day)
if order == .orderedAscending {
// date 1 is older
}
else if order == .orderedDescending {
// date 1 is newer
}
else if order == .orderedSame {
// same day/hour depending on granularity parameter
}
For Swift3
var order = NSCalendar.current.compare(firstDate, to: secondDate, toGranularity: .hour)
if order == .orderedSame {
//Both the dates are same.
//Your Logic.
}
Swift:
extension NSDate {
/**
Compares current date with the given one down to the seconds.
If date==nil, then always return false
:param: date date to compare or nil
:returns: true if the dates has equal years, months, days, hours, minutes and seconds.
*/
func sameDate(date: NSDate?) -> Bool {
if let d = date {
let calendar = NSCalendar.currentCalendar()
if NSComparisonResult.OrderedSame == calendar.compareDate(self, toDate: d, toUnitGranularity: NSCalendarUnit.SecondCalendarUnit) {
return true
}
}
return false
}
}
When you NSDate.date() in the playground, you see the default description printed. Use NSDateFormatter to print a localized description of the date object, possibly with only the date portion.
To zero out specific portions of a date (for the sake of comparison), use NSDateComponents in conjunction with NSCalendar.
In my experience, most people's problems with using NSDate comes from the incorrect assumption that an NSDate can be used to represent a date in the 'normal' sense (i.e. a 24 period starting at midnight in the local timezone). In normal (everyday / non-programming) usage, 1st January 2014 in London is the same date as 1st January in Beijing or New York even though they cover different periods in real time. To take this to the extreme, the time on Christmas Island is UTC+14 while the time on Midway Island is UTC-11. So 1st January 2014 on these two island are the same date even though one doesn't even start until the other has been completed for an hour.
If that is the kind of date you are recording (and if you are not recording the time component, it probably is), then do not use NSDate (which stores only seconds past 2001-01-01 00:00 UTC, nothing else) but store the year month and day as integers - perhaps by creating your own CivilDate class that wraps these values - and use that instead.
Only dip into NSDate to compare dates and then make sure to explicitly declare the time zone as "UTC" on both NSDates for comparison purposes.
Swift 4
func compareDate(date1:Date, date2:Date) -> Bool {
let order = Calendar.current.compare(date1, to: date2,toGranularity: .day)
switch order {
case .orderedSame:
return true
default:
return false
}
}
If you need to compare just if date is in the same day as other date use this:
Calendar.current.isDate(date1, inSameDayAs: date2)
To answer your question:
Is this possible in Swift?
Yes, it is possible
Ahh, you also want to now HOW
let cal = NSCalendar.currentCalendar()
cal.rangeOfUnit(.DayCalendarUnit, startDate: &d1, interval: nil, forDate: d1) // d1 NSDate?
cal.rangeOfUnit(.DayCalendarUnit, startDate: &d2, interval: nil, forDate: d2) // d2 NSDate?
Now d1 and d2 will contain the dates at beginning of their days.
compare with d1!.compare(d2!)
To display them without time portion, us NSDateFormatter.