I have a Current, Start Date, and End Date. How can I convert these dates to elapsed and remaining time format using Swift?
The format needed is 00:00:00 (hours: minutes: seconds)
Currently, I get elapsed and remaining strings using the below code.
The code returns incorrectly to the Time I see in the Simulator.
Example: If the simulator time changes to 2:45 pm
My elapsed time's second is ahead (10s) of the actual seconds shown.
And the remaining seconds shows ahead for (9s)
I am getting like this
elapsed = 01:30:10, rem = 12:30:09
Can you help me fix the code or a simpler way to get the format I want correctly?
Code:
{
let currDate = Date();
let startDate = currDate;
let endDate: Date = startDate.addingTimeInterval(TimeInterval(minutes * 60));
Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(callback), userInfo: nil, repeats: true);
}
#objc func Callback() {
let time = dateToTimerString(currDate: currDate, startDate: startDate, endDate: endDate );
NSLog( "Elapsed %#, Rem %#", time.elapsed, time.remaining );
}
func dateToTimerString(currDate: Date, startDate: Date, endDate: Date) -> (elapsed: String, remaining: String) {
let elapsedTime: TimeInterval = currDate.timeIntervalSince(startDate);
let remainingTime: TimeInterval = endDate.timeIntervalSince(currDate);
let elapsedString = format(duration: elapsedTime);
let remainingString = format(duration: remainingTime);
return (elapsedString, remainingString);
}
func format(duration: TimeInterval) -> String {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute, .second]
formatter.unitsStyle = .positional
formatter.zeroFormattingBehavior = .pad
return formatter.string(from: duration)!
}
There is an API for that: DateComponentsFormatter
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.hour, .minute, .second]
formatter.zeroFormattingBehavior = .pad
let elapsedString = formatter.string(from: elapsedTime)
let remainingString = formatter.string(from: remainingTime)
It actually works with the exact date and time that you are creating with that current date constant. And if you are changing string to date from dateformatter then you need to check if you are getting the exact value there after changing.
Related
I have a datePickerthat I use to select a starting date and time, and a durationTextLabelto add minutes to that date and time. I set the duration to be minimum 30 if no text is inserted, but the value in
resulting date and time is identical.
Can you see where I'm mistaking?
Thank you very much as usual.
Here's the function:
func setQueryParameters() {
let dateFormatter = DateFormatter()
var convertedDate: String!
dateFormatter.dateFormat = "yyyy/MM/dd/hh/mm"
convertedDate = dateFormatter.string(from: datePicker.date)
let calendar = Calendar.current
let components = (calendar as NSCalendar).components([.year, .month, .day, .weekday, .hour, .minute] , from: datePicker.date)
let year: Int = components.year!
let month: Int = components.month!
let day: Int = components.day!
let weekday: Int = components.weekday!
let hour: Int = components.hour!
let minute: Int = components.minute!
var duration: Double?
duration = Double(durationTextField.text!)
let endDate = datePicker.date.addingTimeInterval(duration!)
let endComponents = (calendar as NSCalendar).components([.hour, .minute], from: endDate)
let endHour: Int = endComponents.hour!
let endMinute: Int = endComponents.minute!
if durationTextField.text != nil {
duration = Double(durationTextField.text!) ?? 30.00
} else { return}
// Opening Time Query parameter
openingTimeQueryStart = Int("\(String(describing: weekday))"+"00"+"00")!
openingTimeQueryEnd = Int("\(String(describing: weekday))"+"\(String(describing: hour))"+"\(String(describing: minute))")!
print("opening query is \(openingTimeQueryEnd)")
// Closing Time Query parameter
closingTimeQueryStart = Int("\(String(describing: weekday))"+"\(String(endHour))"+"\(String(endMinute))")!
closingTimeQueryEnd = Int("\(String(describing: weekday))"+"00"+"00")!
print("closing time query is \(closingTimeQueryStart)")
// Booking Query parameter
let bookingQueryString = "\(String(describing: year))"+"\(String(describing: month))"+"\(String(describing: day))"+"\(String(describing: weekday))"+"\(String(describing: hour))"+"\(String(describing: minute))"+"\(String(endHour))"+"\(String(endMinute))"
bookingQuery = Int(bookingQueryString)!// ?? openingTimeQuery // found nil unwripping optional
}
There are many problems here.
You actually never make any use of dateFormatter other than creating and then never using convertedDate. So delete that unused code.
You have indicated at duration should be in minutes but you treat it as seconds. You need to multiply by 60 to convert it to minutes.
All of your code for calculating things such as openingTimeQueryEnd depend on each value being two digits but your code doesn't give the desired results.
For example, the line:
openingTimeQueryEnd = Int("\(String(describing: weekday))"+"\(String(describing: hour))"+"\(String(describing: minute))")!
should be rewritten as:
openingTimeQueryEnd = Int(String(format: "%02d%02d%02d", weekday, hour, minute))!
or as:
openingTimeQueryEnd = weekday * 10000 + hour * 100 + minute
Make similar changes to the other similar lines.
I did a lot of searching through Stackoverflow but I havent found answer for my problem.
I am developing an app and I get JSON data for some events. What I get is the start time of the event and the duration of the event. All data in recived as String.
In one screen of the app I would like to show only the event that are currently going on.
for example:
Class Event {
var startTime: String?
var duration: String?
}
let event1 = Event()
event1.starTime = "12-12-2016, 10:50 AM"
event1.duration = "50min"
let event2 = Event()
event2.starTime = "12-12-2016, 09:50 AM"
event2.duration = "40min"
let event3 = Event()
event3.starTime = "12-12-2016, 10:10 AM"
event3.duration = "90min"
let allEvents = [event1, event2, event3]
and let say the the current date and time is 12-12-2016, 11:00AM. How can I filter/find events in allEvents that are still going on if we compare them to the current date?
Thank you in advance.
EDIT: My solution
I created method for converting dateString and durationString to startDate: Date and endDate: Date
static func convertDateStringAndDurationStringToStartAndEndDate(date: String, duration: String) -> (start: Date, end: Date)? {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
guard let startDate = dateFormatter.date(from: date) else { return nil }
guard let duration = Int(duration) else { return nil }
// recived interval is in minutes, time interval must be calculated in seconds
let timeInterval = TimeInterval(Int(duration) * 60 )
let endDate = Date(timeInterval: timeInterval, since: startDate)
return (startDate, endDate)
}
For filtering I have created separated method. In my case I am using Realm database, but you will get the point.
static func filterResultsForNowPlaying(results: Results<Show>?) -> Results<Show>? {
let currentDate = NSDate()
let datePredicate = NSPredicate(format: "startDate <= %# AND %# <= endDate", currentDate, currentDate)
let filteredShows = results?.filter(datePredicate)
return filteredShows
}
You will need to convert them into dates, using DateFormatter, and then use a .filter over the array and have it match on if the current date is in range.
If you have the ability to change the Event class, you can greatly simplify your code if you replace your Event class with the DateInterval class, which does the same thing:
let minutes = 60;
let formatter = DateFormatter()
formatter.dateFormat = "MM-dd-yyyy"
let event1 = DateInterval(
start: formatter.date(from: "12-12-2016")!,
duration: TimeInterval(20 * minutes)
)
let now = Date()
if (event1.contains(now)) {
print("Event 1 is still active")
}
The API I'm using returns time as Unix time (1424952512) So far I can convert the unix time to NSDate using
func timeStamp(unixTime: Double)-> NSDate {
let interval:NSTimeInterval = unixTime
let date:NSDate = NSDate(timeIntervalSince1970: interval)
print(date)
//now date
let nowDate = NSDate()
print(nowDate)
return date
}
Now how can I compare date with nowDate and print the difference in hours or minutes?
For example with NSDateComponentsFormatter
let formatter = NSDateComponentsFormatter()
formatter.allowedUnits = [.Hour, .Minute]
formatter.unitsStyle = .Short
if let difference = formatter.stringFromDate(date, toDate: nowDate) {
print(difference)
} else {
print("invalid difference")
}
I'm trying to get NSDate from UIDatePicker, but it constantly returns me a date time with trailing 20 seconds. How can I manually set NSDate's second to zero in swift?
extension Date {
var zeroSeconds: Date? {
let calendar = Calendar.current
let dateComponents = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: self)
return calendar.date(from: dateComponents)
}
}
Usage:
let date1 = Date().zeroSeconds
let date2 = Date()
print(date2.zeroSeconds)
From this answer in Swift:
var date = NSDate();
let timeInterval = floor(date .timeIntervalSinceReferenceDate() / 60.0) * 60.0
date = NSDate(timeIntervalSinceReferenceDate: timeInterval)
This is how to do it in Swift 3.
In this example I remove the seconds in the date components:
let date = picker.date
let calendar = Calendar.current
let components = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: date)
let fullMinuteDate = calendar.date(from: components)!
Working on a playground:
Truncating a date to a full minute can be done with
let date = NSDate()
let cal = NSCalendar.currentCalendar()
var fullMinute : NSDate?
cal.rangeOfUnit(.CalendarUnitMinute, startDate: &fullMinute, interval: nil, forDate: date)
println(fullMinute!)
Update for Swift 4 and later:
let date = Date()
let cal = Calendar.current
if let fullMinute = cal.dateInterval(of: .minute, for: date)?.start {
print(fullMinute)
}
This method can easily be adapted to truncate to a full hour, day, month, ...
Just reformat the date:
func stripSecondsFromDate(date: NSDate) -> NSDate {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
let str = dateFormatter.stringFromDate(date)
let newDate = dateFormatter.dateFromString(str)!
return newDate
}
import Foundation
let now = Date()
let calendar = Calendar.current
let date = calendar.date(bySettingHour: 0,
minute: 0,
second: 0,
of: now,
direction: .backward)
There is another way, with two more parameters: matchingpolicy and repeatedTimePolicy.
let date = calendar.date(bySettingHour: 0,
minute: 0,
second: 0,
of: now,
matchingPolicy: .strict,
repeatedTimePolicy: .first,
direction: .backward)
To check the result:
let formatter = ISO8601DateFormatter()
formatter.timeZone = TimeZone.current // defaults to GMT
let string = formatter.string(from: date!)
print(string) // 2019-03-27T00:00:00+01:00
I know this doesn't address NSDate directly, but it might be worth anyways - I had this exact same problem with Date and also because I think this might be a more clean approach.
extension Calendar {
/// Removes seconds `Calendar.Component` from a `Date`. If `removingFractional` is `true`, it also
/// removes all fractional seconds from this particular `Date`.
///
/// `removingFractional` defaults to `true`.
func removingSeconds(fromDate date: Date, removingFractional removesFractional: Bool = true) -> Date? {
let seconds = component(.second, from: date)
let noSecondsDate = self.date(byAdding: .second, value: -seconds, to: date)
if removesFractional, let noSecondsDate = noSecondsDate {
let nanoseconds = component(.nanosecond, from: noSecondsDate)
return self.date(byAdding: .nanosecond, value: -nanoseconds, to: noSecondsDate)
}
return noSecondsDate
}
}
Now, to solve your problem, we created the function removingSeconds(fromDate: removingFractional). It's really simple - as you can see in the docs of the function. It removes the .second component and, if removingFractional is true, it also removes any fractional seconds that this Date may have - or the .nanosecond component.
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")