Swift: Formatting time string based on number of seconds - ios

There is a NSTimer object in my app that counts elapsed time in seconds.
I want to format an UILabel in my app's interface in the such way it matches the well know standard.
Example
00:01 - one second
01:00 - 60 seconds
01:50:50 - 6650 seconds
I wonder how to do that, do you know any pods/libraries that creates such String based on Int number of seconds?
Obviously I can come with a complicated method myself, but since it's recommended to not reinvent the wheel, I'd prefer to use some ready-to-use solution.
I haven't found anything relevant in Foundation library, nor in HealthKit
Do you have any suggestions how to get it done? If you say "go and write it yourself" - that's ok. But I wanted to be sure I'm not missing any simple, straightforward solution.
thanks in advance

(NS)DateComponentsFormatter can do that:
func timeStringFor(seconds : Int) -> String
{
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.second, .minute, .hour]
formatter.zeroFormattingBehavior = .pad
let output = formatter.string(from: TimeInterval(seconds))!
return seconds < 3600 ? output.substring(from: output.range(of: ":")!.upperBound) : output
}
print(timeStringFor(seconds:1)) // 00:01
print(timeStringFor(seconds:60)) // 01:00
print(timeStringFor(seconds:6650)) // 1:50:50

Figured it out based on this answer, quite simple!
func createTimeString(seconds: Int)->String
{
var h:Int = seconds / 3600
var m:Int = (seconds/60) % 60
var s:Int = seconds % 60
let a = String(format: "%u:%02u:%02u", h,m,s)
return a
}

Related

Find how long it takes a function to execute in seconds [duplicate]

This question already has answers here:
Checking response Time of API in iOS using Swift 3?
(4 answers)
Closed 3 years ago.
I'm trying to find a way to tell how long its taken a function to execute in seconds. Currently I'm doing it this way:
let startDate = Date()
let endDate = Date()
let calendar = Calendar.current
let dateComponents = calendar.compare(startDate, to: endDate, toGranularity: .second)
let seconds = dateComponents.rawValue
print("Seconds: \(seconds)")
but every time I print out the seconds it always reads -1. I've looked into this question: elapsed time but I need the output to be in seconds. Any suggestions?
Try this:
let start = Date.timeIntervalSinceReferenceDate
// do stuff
let end = Date.timeIntervalSinceReferenceDate
let secondsElapsed = end - start
secondsElapsed will be a Double, but it will be in seconds. You can round it or truncate it if you want an Int.
Hope this helps!

Swift: Trying to make a app that counts down the time from the present until one hour later [duplicate]

This question already has answers here:
How can I make a countdown timer like in a music player?
(2 answers)
Closed 7 years ago.
I'm trying to make an app that tell us the rest of time from the present time till one hour later.
This is the code but now it only has a function that tell us the countdown time by decreasing one second from the present time.
I'm thinking that I haven't definite the definition of the "cnt"
so that's why I'm thinking it doesn't work.
Can somebody tell me the reason and a solution?
import UIKit
class ViewController: UIViewController {
var cnt : Int = 0
var timer : NSTimer!//NSTimerというデフォルト機能から引っ張る
var myInt:Int = 0
override func viewDidLoad() {
let myDate: NSDate = NSDate()
let myCalendar: NSCalendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let myComponents = myCalendar.components([.Year, .Hour, .Minute, .Second],
fromDate: myDate) // myDate、すなわちNSDateから要素として引っ張り出してる
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "onUpdate:", userInfo: nil, repeats: true)//カウントダウンのインターバル
timer.fire()
var myStr: String = "\(myComponents.hour)"
myStr += "\(myComponents.minute)"
myStr += "\(myComponents.second)"
myInt = Int(myStr)! // toInt()がSwift2より無効になったようです。myInt=Str(my components,hour,minute,second)=現時刻
}
func onUpdate(timer : NSTimer){
cnt += 1//cnt+1=cnt,
let count = myInt - cnt //残り時間=現在時刻ー現在時刻に1時間足した時刻
print(count) // println()は、Swift2よりDeprecatedになりました。
}
}
It is difficult to understand what you're asking, but I will do my best.
In your viewDidLoad method, you're setting myInt to the integer representation of myStr. If the time is 18:30:50, myInt will be equal to 183050. That is not an appropriate representation of the time. Time is base 60, integers are base 10, for one thing. If you want to represent time as a single number, you can use timeIntervalSinceDate, or timeIntervalSinceReferenceDate or timeIntervalSince1970 to get the NSTimeInterval (ie. fractional seconds) representation of the date relative to a certain epoch either of your choosing or one built into Foundation.
Subtracting 1 from myInt each time the timer fires isn't going to give you an indication of the time remaining.
Also, NSTimer is not an accurate way to keep time. You should instead save the start date as a property and determine the time remaining based on timeIntervalSinceDate
e.g.
func onUpdate(timer : NSTimer){
let currentTime = NSDate()
let timeElapsed = currentTime.timeIntervalSinceDate(myDate)
println(timeElapsed)
}
If you want to show time elapsed in minutes, you can divide it by 60. You can look into NSDateComponentsFormatter to easily get a string representation of time intervals.
If you want the countdown to stop after an hour, then check for when timeElapsed is over 3600.
If you want it to show a countdown from 1 hour, then subtract the timeElapsed from 3600.

In Swift, how can I get an NSDate from a dispatch_time_t?

"Walltime" is a little-known time format used by Grand Central Dispatch. Apple talks about it here:
https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/
There are some things it's really handy for, though, but it's a sticky wicket. It's hard to make it play nice with other time formats, which is what my question's about.
I can make a walltime by turning an NSDate into a timespec, and then using with dispatch_walltime:
let now = NSDate().timeIntervalSince1970
let nowWholeSecsFloor = floor(now)
let nowNanosOnly = now - nowWholeSecsFloor
let nowNanosFloor = floor(nowNanosOnly * Double(NSEC_PER_SEC))
var thisStruct = timespec(tv_sec: Int(nowWholeSecsFloor),
tv_nsec: Int(nowNanosFloor))
let wallTime = dispatch_walltime(& thisStruct, 0)
But lord love a duck, I can't figure out how to get it back into an NSDate. Here's my try:
public func toNSDate(wallTime: dispatch_time_t)->NSDate {
let wallTimeAsSeconds = Double(wallTime) / Double(NSEC_PER_SEC)
let date = NSDate(timeIntervalSince1970: wallTimeAsSeconds)
return date
}
The resulting NSDate is not just off, but somewhat hilariously off, like five hundred years or something. As Martin R pointed out, the problem is that dispatch_time_t is an opaque value, with an undocumented representation of time.
Does anyone know how to do this?
EDIT: if the process of creating the walltime is confusing, this is basically what's going on:
NSDate defines time with a Double, and everything after the decimal point is the nanoseconds. dispatch_time, which can create a walltime, defines time with UInt64, so you have to convert between Double and UInt64 to use it. To do that conversion you need to use a timespec, which takes seconds and nanoseconds as separate arguments, each of which must be Int.
A whole lotta convertin' going on!
The real answer is: you can't.
In the "time.h" header file it is stated:
/*!
* #typedef dispatch_time_t
*
* #abstract
* A somewhat abstract representation of time; where zero means "now" and
* DISPATCH_TIME_FOREVER means "infinity" and every value in between is an
* opaque encoding.
*/
typedef uint64_t dispatch_time_t;
So dispatch_time_t uses an undocumented "abstract" representation of time, which
may even change between releases.
That being said, let's have some fun and try to figure out what
a dispatch_time_t really is. So we have a look at "time.c", which contains the implementation of
dispatch_walltime():
dispatch_time_t
dispatch_walltime(const struct timespec *inval, int64_t delta)
{
int64_t nsec;
if (inval) {
nsec = inval->tv_sec * 1000000000ll + inval->tv_nsec;
} else {
nsec = (int64_t)_dispatch_get_nanoseconds();
}
nsec += delta;
if (nsec <= 1) {
// -1 is special == DISPATCH_TIME_FOREVER == forever
return delta >= 0 ? DISPATCH_TIME_FOREVER : (dispatch_time_t)-2ll;
}
return (dispatch_time_t)-nsec;
}
The interesting part is the last line: it takes the negative value of the
nanoseconds, and this value is cast back to an (unsigned) dispatch_time_t. There are also some special cases.
Therefore, to reverse the conversion, we have to negate the
dispatch_time_t and take that as nanoseconds:
public func toNSDate(wallTime: dispatch_time_t)->NSDate {
// Tricky part HERE:
let nanoSeconds = -Int64(bitPattern: wallTime)
// Remaining part as in your question:
let wallTimeAsSeconds = Double(nanoSeconds) / Double(NSEC_PER_SEC)
let date = NSDate(timeIntervalSince1970: wallTimeAsSeconds)
return date
}
And indeed, this converts the walltime correctly back to the original
NSDate, at least when I test it in an OS X application.
But again: don't do it! You would rely on an undocumented representation which could change between OS releases. There may also
be special cases that are not considered in the above code.
Also the representation in the iOS runtime could be different, I did
not try that.
You have been warned!

Subtracting time in swift

I have 2 NSDate object thats shows 24hour time. Is there a way I can subtract those two times and put the result in a label. Ive searched for a method but couldn't find one. The only thing I could find was cover it to a double with timeIntervalFromDate method, which returns a double and I need the 24hour representation of that.
It just returns an NSTimeInterval that is typedef double NSTimeInterval is a representation in seconds.
Doing the math to know how may hours are in x seconds, you just need to divide the retuned interval to 3600 seconds and round the result.
I've build a simple extension to be used in swift to help in calendrical calculation, you can find it here: AFSwiftDateExtension
This function may solve your problem:
func hoursFrom(earlierDate : NSDate, laterDate:NSDate) -> Int
{
return NSCalendar.currentCalendar().components(NSCalendarUnit.CalendarUnitHour, fromDate: earlierDate, toDate: laterDate, options: nil).hour
}
Adapted from this related question.
extension NSTimeInterval {
var time:String {
return String(format:"%02d:%02d:%02d", Int((self/3600.0)%24), Int((self/60.0)%60), Int((self)%60))
}
}
println(30.time) // "00:00:30"
println(60.time) // "00:01:00"
println(600.time) // "00:10:00"
println(3600.time) // "01:00:00"

Format to MM:SS using NSDateComponentsFormatter

I'm trying to format to MM:SS using NSDateComponentsFormatter:
let formatter = NSDateComponentsFormatter()
formatter.zeroFormattingBehavior = .Pad
formatter.allowedUnits = .CalendarUnitMinute | .CalendarUnitSecond
let time: NSTimeInterval = 5
let timeText = formatter.stringFromTimeInterval(time)
The problem is, that even though I specified .Pad, the result is 0:05 instead of 00:05.
Here's the relevant piece from Apple's documentation:
static var Pad: NSDateComponentsFormatterZeroFormattingBehavior { get }
// Off: "1:0:10", On: "01:00:10"
I've tried to add .CalendarUnitHour just to try it and got H:MM:SS instead of HH:MM:SS despite what the documentation says.
How can I pad the very first number as shown in the documentation?
Sorry for wrong initial answer, there is no way to add leading zeros to hours:
When days, hours, minutes, and seconds are allowed, the value is displayed as “0d 1:00:00” using the positional style, and as “0d 1h 0m 0s” using the abbreviated style.
If you simply need to format NSTimeInterval into string like HH:MM:SS check this answer

Resources