NSCalendarUnitSecond returning zero - ios

I have this method returning a string. But the seconds value is always zero. What am I doing wrong?
-(NSString*)secondsBetweenDate:(NSDate*)startDate andDate:(NSDate*)endDate {
NSCalendar *calendar = [NSCalendar currentCalendar];
unsigned int unitFlags = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSDateComponents *difference = [calendar components:unitFlags fromDate:startDate toDate:endDate options:0];
long hour = [difference hour];
long min = [difference minute];
long sec = [difference second];
NSLog(#"Hour: %ld Min: %ld Sec: %ld", hour, min, sec);
return [NSString stringWithFormat:#"%02ld:%02ld:%02ld", hour, min, sec];
}

Why don't you use the default NSDate difference calculations?
Returns an NSTimeInterval which is in seconds:
typedef double NSTimeInterval; Description Used to specify a time
interval, in seconds.
Operation is:
[aDate timeIntervalSinceDate:anotherDate];

Your code is correct. If you use the dates that actually differ in their seconds value it yields the expected results.
[self secondsBetweenDate:[NSDate dateWithTimeIntervalSinceNow:-99999] andDate:[NSDate date]];
logs:
Hour: 27 Min: 46 Sec: 39
And 99999 Seconds is 27*60*60 +46*60 +39
In our discussion in the comments we discovered that you had more of a conceptional problem. If you need to show a countdown or stopwatch type string you have to use the current time as one of the date parameters.
So if you want to show the time that has passed since a specific date (like a stop watch) you use:
NSDate *now = [NSDate date];
string = [self secondsBetweenDate:yourStartDate andDate:now];
If you want to show the time until a specific date (like a countdown) you use:
NSDate *now = [NSDate date];
string = [self secondsBetweenDate:now andDate:yourEndDate];
Btw: If you are only targetting iOS 8 and later you can use NSDateComponentsFormatter to format your date
NSDateComponentsFormatter *df = [[NSDateComponentsFormatter alloc] init];
df.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
df.allowedUnits = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSString *dateString = [df stringFromDate:startDate toDate:[NSDate date]];

Related

NSDate dateWithTimeIntervalSince1970 adding an extra day on

I have a method that updates a label and acts as a stop watch. It works fine accept when I format the string to factor days in it adds an additional day on. For example. If the stopwatch is started ten minutes ago the label will display:
01:00:10:00
it should just display 00:00:10:00
- (void)updateTimer
{
NSDate *currentDate = [NSDate date];
NSDate *dateValue=[[NSUserDefaults standardUserDefaults] objectForKey:#"pickStart"];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:dateValue];
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
// Create a date formatter
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"dd:HH:mm:ss"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
// Format the elapsed time and set it to the label
NSString *timeString = [dateFormatter stringFromDate:timerDate];
self.stopWatchLabel.text = timeString;
}
Any help would be greatly appreciated.
What you are doing isn't appropriate. Your goal seems to be to convert timeInterval into days, hours, minutes, and seconds. Your use of timerDate and NSDateFormatter are not the proper way to achieve that goal.
timeInterval is not an offset from January 1, 1970 and timeInterval doesn't represent a date.
What you should do is get the difference between currentDate and dateValue as a set of NSDateComponents.
- (void)updateTimer {
NSDate *currentDate = [NSDate date];
NSDate *dateValue = [[NSUserDefaults standardUserDefaults] objectForKey:#"pickStart"];
unsigned int unitFlags = NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
NSDateComponents *comps = [[NSCalendar currentCalendar] components:unitFlags fromDate:dateValue toDate:currentDate options:0];
int days = [comps day];
int hours = [comps hours];
int minutes = [comps minutes];
int seconds = [comps seconds];
NSString *timeString = [NSString stringWithFormat:#"%d:%02d:%02d:%02d", days, hours, minutes, seconds];
self.stopWatchLabel.text = timeString;
}
There is no "additional day", the "date" you produce is a point in time after the 1st Jan 1970, so if your format includes the day you get at least a 1...
Just stick with the NSTimeInterval value and use NSDateComponentsFormatter - which also formats time intervals despite the name - or just do the math yourself to get the seconds, minutes, etc. and format those.
HTH

iOS) value of timeIntervalSince70 is fixed when i approach at the level of second

I want to update label that shows time left between 2 nsdates everysecond.
my code is below :
NSDate *nowDate = [NSDate date];
double diff = [campaignDate timeIntervalSince1970] - [nowDate timeIntervalSince1970];
int diff_day = diff/60/60/24 - 1;
int diff_hour = ((int)diff/60/60)%24;
int diff_min = ((int)diff/60)%60;
int diff_sec = ((int)diff/60/60)%60;
When i logged diff_sec , it always shows me 49
day,hour and min value is printed in working order
why does that diff_sec make a problem? is there any solution ?
I think you should use NSDateComponents to get the number of days/hours/minutes/seconds between two days.
Then you can get the remaing time like this:
NSDate *fromDate = [NSDate date];
NSDate *toDate = [NSDate dateWithTimeIntervalSinceNow:12345];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *difference = [calendar components:NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond fromDate:fromDate toDate:toDate options:0];
NSLog(#"Event in %# days, %#:%#:%#", #(difference.day), #(difference.hour), #(difference.minute), #(difference.second));
Use NSCalendar
NSDate *dateA;
NSDate *dateB;
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit
fromDate:dateA
toDate:dateB
options:0];
NSLog(#"Difference in date components: %i/%i/%i", components.day, components.month, components.year);
The error is in calculating diff_sec, the divisions by 60 are incorrect because diff is already in seconds.
Incorrect code:
int diff_sec = ((int)diff/60/60)%60;
Correct code
int diff_sec = ((int)diff)%60;

Date Formatting is Weird

I have a date string that looks like this:
1391640679661
When I use this code:
NSString *seconds = #"1391640679661";
NSDate *date = [NSDate dateWithTimeIntervalSince1970:[seconds doubleValue]];
I end up with this:
46069-05-03 07:27:41 +0000
So what's happening here? Is this a particular date format that I'm not accounting for? Or am I doing something else wrong?
Apple's own api will do all the hard work for you to format components as per your need.
If you want to get individual components as well you can apply below approach.
NSTimeInterval theTimeInterval = 1391640679661;
// 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]);
To convert a timestamp string into NSDate, you need to divid the timestamp double value to 1000, and then call dateWithTimeIntervalSince1970:
NSString *timestamp = #"1391640679661";
double seconds = [timestamp doubleValue]/1000.0;
NSDate *date = [NSDate dateWithTimeIntervalSince1970:seconds];
The result is:
2014-02-05 22:51:19 +0000

NSDateComponent Rounding

I am looking to display the amount of months from an NSDate object.
//Make Date Six Months In The Future
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *sixMonthsFromNow = [[NSDateComponents alloc] init];
[sixMonthsFromNow setMonth:6];
NSDate *finishDate = [calendar dateByAddingComponents:sixMonthsFromNow toDate:[NSDate date] options:0]; //Six Months Time
//Display
NSCalendarUnit requiredFormat = NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
NSDateComponents *dateComponents = [calendar components:requiredFormat fromDate:[NSDate date] toDate:finishDate options:0];
NSLog(#"%d months %d days %d hours %d minutes %d seconds", [dateComponents month], [dateComponents day], [dateComponents hour], [dateComponents minute], [dateComponents second]);
This outputs: 5 months 29 days 23 hours 59 minutes 59 seconds
Which is great, but I only wish to display the amount of months.
If I limit to only months:
//Make Date Six Months In The Future
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *sixMonthsFromNow = [[NSDateComponents alloc] init];
[sixMonthsFromNow setMonth:6];
NSDate *finishDate = [calendar dateByAddingComponents:sixMonthsFromNow toDate:[NSDate date] options:0]; //Six Months Time
//Display
NSCalendarUnit requiredFormat = NSMonthCalendarUnit;
NSDateComponents *dateComponents = [calendar components:requiredFormat fromDate:[NSDate date] toDate:finishDate options:0];
NSLog(#"%d months", [dateComponents month]);
This outputs: 5 months
Although this is technically correct, I would like to round the amount of days to make it output six months.
Is there an easy way to achieve this effect? I noticed there wasn't a rounding property on NSDateComponents. Will I have to manually check the amount of days and decide to round up?
My end goal is to not only limit the rounding effect to months, this should be able to round hours to days if i only supplied: NSDayCalendarUnit
The following method could to what you want.
The idea is that after computing the (rounded down) number of calendar units
between start date and end date, add both that amount and one more to the start date
and check which one is closer to the end date:
#interface NSCalendar (MyCategory)
-(NSInteger)roundedUnit:(NSCalendarUnit)unit fromDate:(NSDate *)fromDate toDate:(NSDate *)toDate;
#end
#implementation NSCalendar (MyCategory)
-(NSInteger)roundedUnit:(NSCalendarUnit)unit fromDate:(NSDate *)fromDate toDate:(NSDate *)toDate
{
// Number of units between the two dates:
NSDateComponents *comps = [self components:unit fromDate:fromDate toDate:toDate options:0];
NSInteger value = [comps valueForComponent:unit];
// Add (value) units to fromDate:
NSDate *date1 = [self dateByAddingComponents:comps toDate:fromDate options:0];
// Add (value + 1) units to fromDate:
[comps setValue:(value + 1) forComponent:unit];
NSDate *date2 = [self dateByAddingComponents:comps toDate:fromDate options:0];
// Now date1 <= toDate < date2. Check which one is closer,
// and return the corresponding value:
NSTimeInterval diff1 = [toDate timeIntervalSinceDate:date1];
NSTimeInterval diff2 = [date2 timeIntervalSinceDate:toDate];
return (diff1 <= diff2 ? value : value + 1);
}
#end
And you would use it as
NSInteger months = [calendar roundedUnit:NSMonthCalendarUnit fromDate:[NSDate date] toDate:finishDate];
The code uses utility methods for NSDateComponents from
https://github.com/henrinormak/NSDateComponents-HNExtensions/blob/master/README.md:
- (void)setValue:(NSInteger)value forComponent:(NSCalendarUnit)unit;
- (NSInteger)valueForComponent:(NSCalendarUnit)unit;
These methods are new in OS X 10.9, but not available in iOS 7.
As the other commenter pointed out, you're calling NSDate twice, and the second date is slightly later than the first. Use the same date object twice and you won't get rounding errors.
As for how to round:
I don't think there is any rounding built into NSCalendars calendrical calculation methods.
You need to decide what that means to you. You might round at the halfway point. In that case, you could ask the calendar how many days there are in the current month, using the rangeOfUnit:inUnit:forDate method, and then add half that many days to the end date, then ask for the month. If you only want to "round up within a week, then only add a week to the end date before asking for the number of months difference.
I've got a similar problem:
let fmt1 = NSDateComponentsFormatter()
fmt1.allowedUnits = .CalendarUnitYear |
.CalendarUnitMonth |
.CalendarUnitDay
let dc1 = NSDateComponents()
dc1.month = 1
dc1.day = 15
// This occasionally outputs "1 month, 14 days" instead of
// "1 month, 15 days" when the formatter is constrained to
// years, months and days. If formatter is allowed to use
// all units, the output is "1 month, 14 days, 23 hours,
// 59 minutes, 59 seconds".
fmt1.stringFromDateComponents(dc1)
This can be fixed by specifying a positive amount of seconds and combining it with
setting maximumUnitCount formatter parameter to a fixed value:
let fmt2 = NSDateComponentsFormatter()
fmt2.allowedUnits = .CalendarUnitYear |
.CalendarUnitMonth |
.CalendarUnitDay
fmt2.maximumUnitCount = 2
fmt2.collapsesLargestUnit = true
let dc2 = NSDateComponents()
dc2.month = 1
dc2.day = 15
dc2.second = 10
// This always outputs "1 month, 15 days"
fmt2.stringFromDateComponents(dc2)
This fix will probably work in your case too: just assign some positive amount of seconds to NSDateComponents before calling dateByAddingComponents:
[sixMonthsFromNow setMonth: 6];
[sixMonthsFromNow setSecond: 10];

Possible to compare NSDate objects without using calendar components?

I'm looking to compare NSDate objects based on the day only (ignoring time). Instead of converting the time to 0:00:00, or using NSDateComponent like most solutions (ex. Comparing two NSDates and ignoring the time component)
Does anyone see an issue with converting the date to an int representing the number of days since 1970 with the timeIntervalSince1970 method?
return (int)([date timeIntervalSince1970]/(SECONDS_PER_DAY));
Yes, absolutely. There are an endless number of pitfalls with date math. Use NSDateComponents; they’re not hard.
NSCalendar *cal = [NSCalendar currentCalendar];
NSDateComponents *date1Components = [cal components:NSEraCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:date1];
NSDateComponents *date2Components = [cal components:NSEraCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:date2];
NSComparisonResult comparison = [[cal dateFromComponents:date1Components] compare:[cal dateFromComponents:date2Components];
Here's a way to convert an NSDate to an NSTimeInterval that represents midnight of the original date without using NSCalendar. Doing this with two NSDate objects would let you compare the two dates without regard to time.
NSDate *now = [NSDate date]; // Your original date with time
NSTimeInterval interval = [now timeIntervalSince1970]; // the full interval
NSDateFormatter *form = [[NSDateFormatter alloc] init];
[form setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; // Zulu time
[form setDateFormat:#"A"]; // milliseconds since midnight
NSString *secondsStr = [form stringFromDate:now];
NSTimeInterval seconds = [secondsStr integerValue] / 1000.0; // seconds since midnight
NSTimeInterval justDate = interval - seconds; // interval for date at midnight (Zulu time)
// For testing purposes
NSDate *nowDate = [NSDate dateWithTimeIntervalSince1970:justDate];
NSLog(#"now = %#, interval = %f", now, interval);
NSLog(#"seconds = %#", secondsStr);
NSLog(#"justDate = %f, nowDate = %#", justDate, nowDate);
This may or may not be better than using NSCalendar as shown in Noah's answer.
You must definitely not simply divide by SECONDS_PER_DAY. That will simply be wrong.
NSCalendar.currentCalendar().compareDate(date1, toDate: date2, toUnitGranularity: .Day)
Returns NSComparisonResult
Here is what apple talks about it

Resources