This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Fuzzy Date algorithm in Objective-C
How do I break down an NSTimeInterval into year, months, days, hours, minutes and seconds on iPhone?
I am interested in printing the playback duration of a media item like '1 hour' or '2 minutes 10 seconds' depending on how large the NSTimeInterval is (without having a fixed format). Poorly there isn't any class provided by Cocoa Touch.
Before wasting time with reinventing the wheel I want to ask if somebody can recommend me an implementation of that. I am sure this is something often used, but I can't find any library/snippet which provide such a functionality on the web.
You could do something like this:
- (NSString*)timeIntervalStringforDouble:(double)timeInterval {
NSString *intervalString=#"";
NSInteger hours = (int)floor(timeInterval/3600.0);
double remainder = timeInterval-(hours*3600);
NSInteger minutes = (int)floor(remainder/60.0);
NSInteger seconds = (int)round(remainder-(minutes*60));
NSString *temporalSegment = #"hour";
if (hours) {
if (hours>1) temporalSegment = #"hours";
intervalString = [intervalString stringByAppendingFormat:#"%d %# ",hours,temporalSegment];
}
if (minutes || hours) {
temporalSegment = (minutes==1)? #"minute":#"minutes";
intervalString = [intervalString stringByAppendingFormat:#"%d %# ",minutes,temporalSegment];
}
if (seconds || minutes) {
temporalSegment = (seconds==1)?#"second":#"seconds";
intervalString = [intervalString stringByAppendingFormat:#"%d %#",seconds, temporalSegment];
}
return intervalString;
}
Punctuate and format to taste.
Related
Nt = N0e-λt
N0 is the initial quantity
Nt is the quantity that still remains after a time t,
t1/2 is the half-life
τ is the mean lifetime
λ is the decay constant
I am pretty stuck on how to make this into a formula for objective-c and I require it.
double sourceStart = [textField.text doubleValue];
double sourceNow = 0;
double daysBetween = 8;
if (textField.text.length > 0) {
//Find how many half lives have been accumulated
double totalNumberOfHalfLives = daysBetween / sourceHalfLife;
//Find the factor
double reductionFactor = pow(0.5, totalNumberOfHalfLives);
//Find the source strength now
double sourceNow = sourceStart * reductionFactor;
}
I'm assuming I need something a long the lines of this? or completely wrong.
Then, I also need to be able to find how many days have passed between a certain period of days, for instance. Start Date = Apr 15th Now Date = Apr 25th, 10 days between.. How do I work that out in objective c? As well as my original question.
This will do it (in straight C, but it'll work as-is in Objective-C, and you can extract the logic easily enough):
#include <stdio.h>
#include <math.h>
int main(void) {
double start_quantity = 100;
double half_life = 8;
double days = 16;
double end_quantity = start_quantity * pow(0.5, days / half_life);
printf("After %.1f days with a half life of %.1f days, %.1f decays to %.1f.\
n",
days, half_life, start_quantity, end_quantity);
return 0;
}
and outputs:
paul#local:~/src$ ./halflife
After 16.0 days with a half life of 8.0 days, 100.0 decays to 25.0.
paul#local:~/src$
For the second part of your question, you can store dates in an NSDate, and use the timeIntervalSinceDate: method to get the time between them in seconds. Something like this:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
#autoreleasepool {
const int kSecsInADay = 86400;
NSDate * startDate = [NSDate dateWithTimeIntervalSinceNow:-16 * kSecsInADay];
NSDate * endDate = [NSDate date];
NSTimeInterval seconds_diff = [endDate timeIntervalSinceDate:startDate];
double days_diff = seconds_diff / kSecsInADay;
NSLog(#"There are %.1f days between %# and %#.", days_diff,
[startDate description], [endDate description]);
}
return 0;
}
which outputs:
There are 16.0 days between 2014-04-09 21:41:12 +0000 and 2014-04-25 21:41:12 +0000.
Notes:
[NSDate date] returns an NSDate object representing the current time.
[NSDate dateWithTimeIntervalSinceNow:seconds] returns an NSDate object that's seconds seconds away from the current time. In this case, I've created it exactly 16 days before the current date. Based on the comments, in your case you'll also want to create startDate with [NSDate date], and then store it somewhere so you can calculate the difference between it and the current time at some point in the future.
Once a IBAction is pressed, I want a counter which counts up.
But in this form
00:00:00
Hours:minutes:seconds
This is the code so far:
-(void)countUp {
mainInt += 1;
seconds.text = [NSString stringWithFormat:#"%02d", mainInt];
}
This only counts up in 00 format
Thanks for the help!
Just do the appropriate math to break the count up into its constituent parts:
NSString *timeString = [NSString stringWithFormat:#"%02d:%02d:%02d",
totalSeconds/3600, // hours
(totalSeconds/60)%60, // minutes
totalSeconds%3600] // seconds
For the sake of readability, it'd be nice to replace that inline math with macros or functions, for example:
#define secondsPerMinute 60
#define minutesPerHour 60
int hours(int secs) {
return secs/(minutesPerHour * secondsPerMinute);
}
int minutes(int secs) {
return (secs/secondsPerMinute) % minutesPerHour;
}
int seconds(int secs) {
return secs % (minutesPerHour * secondsPerMinute);
}
// ...
NSString *timeString = [NSString stringWithFormat:#"%02d:%02d:%02d",
hours(totalSeconds),
minutes(totalSeconds),
seconds(totalSeconds)];
Often when implementing this sort of display, you don't want the colons to jump around as the elapsed time changes. Many fonts have fixed-width numerals, so it's not always a problem, but you might want to use three separate labels for the hours, minutes, and seconds, with unchanging labels in between for the colons.
Another approach to the math above is to store the seconds, minutes, and hours in three variables instead of one, and just be careful to increment minutes and reset seconds when seconds hit 60, and so on. To make this easier to use, encapsulate it in a class, like:
#interface Time : NSObject {
int seconds;
int minutes;
int hours;
}
- (void)countUp;
- (NSString*)timeString;
#end;
I'm creating a countdown timer and I need to printout the time left (hour:minute:seconds) until a specific date. I've found how to get the time interval between Now and the target date but I don't know how to format the time interval as a string. Does NSDateFormater work on NSTimeInterval?
NSTimeInterval is in seconds, use divide and remainder to break it up and format (code untested):
NSString *timeIntervalToString(NSTimeInterval interval)
{
long work = (long)interval; // convert to long, NSTimeInterval is *some* numeric type
long seconds = work % 60; // remainder is seconds
work /= 60; // total number of mins
long minutes = work % 60; // remainder is minutes
long hours = work / 60 // number of hours
// now format and return - %ld is long decimal, %02ld is zero-padded two digit long decimal
return [NSString stringWithFormat:#"%ld:%02ld:%02ld", hours, minutes, seconds];
}
You would first compare two NSDate objects to retrieve the difference in seconds between the two, the NSDate method you should use is
- (NSTimeInterval)timeIntervalSinceDate:(NSDate *)anotherDate
Then you could simply write a function to parse the seconds into hours/minutes/seconds, for example you could use this (untested):
-(NSDictionary*)createTimemapForSeconds:(int)seconds{
int hours = floor(seconds / (60 * 60) );
float minute_divisor = seconds % (60 * 60);
int minutes = floor(minute_divisor / 60);
float seconds_divisor = seconds % 60;
seconds = ceil(seconds_divisor);
NSDictionary * timeMap = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:[NSNumber numberWithInt:hours], [NSNumber numberWithInt:minutes], [NSNumber numberWithInt:seconds], nil] forKeys:[NSArray arrayWithObjects:#"h", #"m", #"s", nil]];
return timeMap;
}
This is code from my project:
-(NSString*)timeLeftString
{
long seconds = [self msLeft]/1000;
if( seconds == 0 )
return #"";
if( seconds < 60 )
return [NSString stringWithFormat:
pluralString(seconds,
NSLocalizedString(#"en|%ld second left|%ld seconds left", #"")), seconds];
long minutes = seconds / 60;
seconds -= minutes*60;
if( minutes < 60 )
return [NSString stringWithFormat:
NSLocalizedString(#"%ld:%02ld left",#""),
minutes, seconds];
long hours = minutes/60;
minutes -= hours*60;
return [NSString stringWithFormat:
NSLocalizedString(#"%ld:%02ld:%02ld left",#""),
hours, minutes, seconds];
}
msLeft --- my function that returns time in milliseconds
pluralString --- my function that provides different parts of format string depending on the value (http://translate.sourceforge.net/wiki/l10n/pluralforms)
Function returns different format for different timer values (1 second left, 5 seconds left, 2:34 left, 1:15:14 left).
In any case, progress bad should be visible during long operation
One more thought: In case that time left is "small" (less then a minute?), probably time left should not be shown --- just progress bar left to reduce interface "visual noise".
So, I'm having a difficult time (no pun intended) finding if an NSDate is within a specific range. Consider this: a scheduled visit between 5:00 PM and 6:00 PM. I want to find if the scheduled visit is within +/- 15 minutes of the current time. Thus my safe range is 4:45 PM to 6:15 PM. How can I find out if the scheduled visit is in or out of the range? Here's the code I've got so far, which doesn't work at all...
- (BOOL)isInScheduledRange {
// NSLog(#"Start: %f", [self.start timeIntervalSinceNow]);
// NSLog(#"End: %f", [self.end timeIntervalSinceNow]);
//
// if (([self.start timeIntervalSinceNow] >= -900) && ([self.end timeIntervalSinceNow] <= 3600)) {
// NSLog(#"In Range");
//
// return YES;
// }
//
NSDate *now = [[NSDate alloc] init];
// if ((([self.start timeIntervalSinceDate:now] >= -900) && ([self.start timeIntervalSinceDate:now] <= 900)) && (([self.end timeIntervalSinceDate:now] <= -900) && ([self.end timeIntervalSinceDate:now] >= 900))) {
// NSLog(#"In Range");
// }
NSLog(#"%#; %#; %#", now, self.start, self.end);
// NSLog(#"%#; %#", [self.start timeIntervalSinceDate:now], [self.end timeIntervalSinceDate:now]);
if ((([self.start timeIntervalSinceDate:now] >= -900) && ([self.start timeIntervalSinceDate:now] <= 0)) && (([self.end timeIntervalSinceDate:now] >= 0) && ([self.end timeIntervalSinceDate:now] <= 900))) {
NSLog(#"In Range");
}
return NO;
[now release];
}
I'd appreciate some help. I'm having difficulties with this time calculation.
On a separate note, I hate dealing with time no matter what platform I'm working with...
You want to check if the start time is less than 900 seconds after the current time, and the end time is less than 900 seconds before the current time. timeIntervalSinceDate: returns a positive number if the object it is called on is after the argument, you need to check for start <= 900 and end >= −900. Also, you could simplify your code by using timeIntervalSinceNow instead of manually getting the current date and passing it to timeIntervalSinceDate:. Finally, if you can assume (or previously ensured) that the start is before the end, then you don't need the two middle tests, as they will both have to be true for both of the others to be true.
if([self.start timeIntervalSinceNow] <= 900 && [self.end timeIntervalSinceNow] >= −900) {
NSLog(#"In range")
}
Here's some verbose (and silly) code that explains my approach to this problem
NSTimeInterval secondsBeforeStart = [self.start timeIntervalSinceNow];
if ( secondsBeforeStart > (15 * 60))
{
// The result for '[self.start timeIntervalSinceNow]' is positive as long
// as 'self.start' remains in the future. We're not in range until there
// are 900 seconds to go or less.
NSLog( #"Still time to chill.");
// More than fifteen minutes to go so return NO.
return NO;
}
else
{
NSLog( #"OMG did I miss it!!!");
NSTimeInterval secondsBeforeEnd = [self.end timeIntervalSinceNow];
if ( secondsBeforeEnd < -( 15 * 60))
{
// The result for '[self.end timeIntervalSinceNow]' is negative when
// 'self.end' is in the past.
// It's been more than 900 seconds since the end of the appointment
// so we've missed it.
NSLog( #"Ahhhhh!!!");
// More than 900 seconds past the end of the event so return NO.
return NO;
}
else
{
// We are somewhere between 900 seconds before the start and
// 900 seconds before the end so we are golden.
NSLog( #"Whew, we made it.");
return YES;
}
}
The way I would have coded it would have been
BOOL inRange = NO; // Assume we are not in the proper time range.
if ( [self.start timeIntervalSinceNow] <= ( 15 * 60))
{
// 'Now' is at least 900 seconds before the start time but could be later.
if ( [self.end timeIntervalSinceNow] >= -( 15 * 60))
{
// 'Now' is not yet 900 seconds past the end time.
inRange = YES
}
}
return inRange;
Note: I haven't actually compiled this but I'm pretty sure the logic is correct.
Finally, you did notice that your last two lines
return NO;
[now release];
}
would have created a memory leak. (Release and then return ;^))
This is a distance problem - you want to know if the current time is within 900 seconds of the target time. Compute distance problems by taking the absolute value of the difference between the two points. By taking the absolute value of the difference, the order of the two samples doesn't matter.
/**
* #brief Return YES if the current time is within 900 seconds of the meeting time (NSDate* time).
*/
-(BOOL)currentTimeIsWithin900SecondsOf:(NSDate*)time
{
NSDate* now = [NSDate date];
return fabs( [now timeIntervalSinceReferenceDate] - [time timeIntervalSinceReferenceDate] ) < 900;
}
You need to create three NSDates. Then convert NSDates to time intervals using intervalSinceReferenceDate and compare those. You manually set up NSDates by using NSDateComponents.
I've got an amount of seconds that passed from a certain event. It's stored in a NSTimeInterval data type.
I want to convert it into minutes and seconds.
For example I have: "326.4" seconds and I want to convert it into the following string:
"5:26".
What is the best way to achieve this goal?
Thanks.
Brief Description
The answer from Brian Ramsay is more convenient if you only want to convert to minutes.
If you want Cocoa API do it for you and convert your NSTimeInterval not only to minutes but also to days, months, week, etc,... I think this is a more generic approach
Use NSCalendar method:
(NSDateComponents *)components:(NSUInteger)unitFlags fromDate:(NSDate *)startingDate toDate:(NSDate *)resultDate options:(NSUInteger)opts
"Returns, as an NSDateComponents object using specified components, the difference between two supplied dates". From the API documentation.
Create 2 NSDate whose difference is the NSTimeInterval you want to convert. (If your NSTimeInterval comes from comparing 2 NSDate you don't need to do this step, and you don't even need the NSTimeInterval).
Get your quotes from NSDateComponents
Sample Code
// The time interval
NSTimeInterval theTimeInterval = 326.4;
// Get the system calendar
NSCalendar *sysCalendar = [NSCalendar currentCalendar];
// Create the NSDates
NSDate *date1 = [[NSDate alloc] init];
NSDate *date2 = [[NSDate alloc] initWithTimeInterval:theTimeInterval sinceDate:date1];
// Get conversion to months, days, hours, minutes
unsigned int unitFlags = NSHourCalendarUnit | NSMinuteCalendarUnit | NSDayCalendarUnit | NSMonthCalendarUnit;
NSDateComponents *conversionInfo = [sysCalendar components:unitFlags fromDate:date1 toDate:date2 options:0];
NSLog(#"Conversion: %dmin %dhours %ddays %dmoths",[conversionInfo minute], [conversionInfo hour], [conversionInfo day], [conversionInfo month]);
[date1 release];
[date2 release];
Known issues
Too much for just a conversion, you are right, but that's how the API works.
My suggestion: if you get used to manage your time data using NSDate and NSCalendar, the API will do the hard work for you.
pseudo-code:
minutes = floor(326.4/60)
seconds = round(326.4 - minutes * 60)
All of these look more complicated than they need to be! Here is a short and sweet way to convert a time interval into hours, minutes and seconds:
NSTimeInterval timeInterval = 326.4;
long seconds = lroundf(timeInterval); // Since modulo operator (%) below needs int or long
int hour = seconds / 3600;
int mins = (seconds % 3600) / 60;
int secs = seconds % 60;
Note when you put a float into an int, you get floor() automatically, but you can add it to the first two if if makes you feel better :-)
Forgive me for being a Stack virgin... I'm not sure how to reply to Brian Ramsay's answer...
Using round will not work for second values between 59.5 and 59.99999. The second value will be 60 during this period. Use trunc instead...
double progress;
int minutes = floor(progress/60);
int seconds = trunc(progress - minutes * 60);
If you're targeting at or above iOS 8 or OS X 10.10, this just got a lot easier. The new NSDateComponentsFormatter class allows you to convert a given NSTimeInterval from its value in seconds to a localized string to show the user. For example:
Objective-C
NSTimeInterval interval = 326.4;
NSDateComponentsFormatter *componentFormatter = [[NSDateComponentsFormatter alloc] init];
componentFormatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
componentFormatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorDropAll;
NSString *formattedString = [componentFormatter stringFromTimeInterval:interval];
NSLog(#"%#",formattedString); // 5:26
Swift
let interval = 326.4
let componentFormatter = NSDateComponentsFormatter()
componentFormatter.unitsStyle = .Positional
componentFormatter.zeroFormattingBehavior = .DropAll
if let formattedString = componentFormatter.stringFromTimeInterval(interval) {
print(formattedString) // 5:26
}
NSDateCompnentsFormatter also allows for this output to be in longer forms. More info can be found in NSHipster's NSFormatter article. And depending on what classes you're already working with (if not NSTimeInterval), it may be more convenient to pass the formatter an instance of NSDateComponents, or two NSDate objects, which can be done as well via the following methods.
Objective-C
NSString *formattedString = [componentFormatter stringFromDate:<#(NSDate *)#> toDate:<#(NSDate *)#>];
NSString *formattedString = [componentFormatter stringFromDateComponents:<#(NSDateComponents *)#>];
Swift
if let formattedString = componentFormatter.stringFromDate(<#T##startDate: NSDate##NSDate#>, toDate: <#T##NSDate#>) {
// ...
}
if let formattedString = componentFormatter.stringFromDateComponents(<#T##components: NSDateComponents##NSDateComponents#>) {
// ...
}
Brian Ramsay’s code, de-pseudofied:
- (NSString*)formattedStringForDuration:(NSTimeInterval)duration
{
NSInteger minutes = floor(duration/60);
NSInteger seconds = round(duration - minutes * 60);
return [NSString stringWithFormat:#"%d:%02d", minutes, seconds];
}
Here's a Swift version:
func durationsBySecond(seconds s: Int) -> (days:Int,hours:Int,minutes:Int,seconds:Int) {
return (s / (24 * 3600),(s % (24 * 3600)) / 3600, s % 3600 / 60, s % 60)
}
Can be used like this:
let (d,h,m,s) = durationsBySecond(seconds: duration)
println("time left: \(d) days \(h) hours \(m) minutes \(s) seconds")
NSDate *timeLater = [NSDate dateWithTimeIntervalSinceNow:60*90];
NSTimeInterval duration = [timeLater timeIntervalSinceNow];
NSInteger hours = floor(duration/(60*60));
NSInteger minutes = floor((duration/60) - hours * 60);
NSInteger seconds = floor(duration - (minutes * 60) - (hours * 60 * 60));
NSLog(#"timeLater: %#", [dateFormatter stringFromDate:timeLater]);
NSLog(#"time left: %d hours %d minutes %d seconds", hours,minutes,seconds);
Outputs:
timeLater: 22:27
timeLeft: 1 hours 29 minutes 59 seconds
Since it's essentially a double...
Divide by 60.0 and extract the integral part and the fractional part.
The integral part will be the whole number of minutes.
Multiply the fractional part by 60.0 again.
The result will be the remaining seconds.
Remember that the original question is about a string output, not pseudo-code or individual string components.
I want to convert it into the following string: "5:26"
Many answers are missing the internationalization issues, and most doing the math computations by hand. All just so 20th century...
Do not do the Math yourself (Swift 4)
let timeInterval: TimeInterval = 326.4
let dateComponentsFormatter = DateComponentsFormatter()
dateComponentsFormatter.unitsStyle = .positional
if let formatted = dateComponentsFormatter.string(from: timeInterval) {
print(formatted)
}
5:26
Leverage on libraries
If you really want individual components, and pleasantly readable code, check out SwiftDate:
import SwiftDate
...
if let minutes = Int(timeInterval).seconds.in(.minute) {
print("\(minutes)")
}
5
Credits to #mickmaccallum and #polarwar for adequate usage of DateComponentsFormatter
How I did this in Swift (including the string formatting to show it as "01:23"):
let totalSeconds: Double = someTimeInterval
let minutes = Int(floor(totalSeconds / 60))
let seconds = Int(round(totalSeconds % 60))
let timeString = String(format: "%02d:%02d", minutes, seconds)
NSLog(timeString)
Swift 2 version
extension NSTimeInterval {
func toMM_SS() -> String {
let interval = self
let componentFormatter = NSDateComponentsFormatter()
componentFormatter.unitsStyle = .Positional
componentFormatter.zeroFormattingBehavior = .Pad
componentFormatter.allowedUnits = [.Minute, .Second]
return componentFormatter.stringFromTimeInterval(interval) ?? ""
}
}
let duration = 326.4.toMM_SS()
print(duration) //"5:26"