NSDateComponent Rounding - ios

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];

Related

How to get current seconds left to complete hour?

I want to get how many seconds are remaining to complete an hour. No matter which what time it is?
if its 05:01:00 then it should give 3540 seconds
and if its 11:58:40 then it gives 80 seconds and so on. I try to find it on google but could not able to find it.
Thanks in advance.
NSCalendar has got methods to do that kind of date math:
NSDate *now = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
// find next date where the minutes are zero
NSDate *nextHour = [calendar nextDateAfterDate:now matchingUnit:NSCalendarUnitMinute value:0 options:NSCalendarMatchNextTime];
// get the number of seconds between now and next hour
NSDateComponents *componentsToNextHour = [calendar components:NSCalendarUnitSecond fromDate:now toDate:nextHour options:0];
NSLog(#"%ld", componentsToNextHour.second);
#Vadian's answer is very good. (voted)
It requires iOS 8 or later however.
There are other ways you could do this using NSCalendar and NSDateComponents that would work with older OS versions.
You could use componentsFromDate to get the month, day, year, and hour from the current date, then increment the hour value and use the NSCalendar method dateFromComponents: to convert your to adjusted components back to a date.
NSDate *date1 = [NSDate dateWithString:#"2010-01-01 00:00:00 +0000"];
NSDate *date2 = [NSDate dateWithString:#"2010-02-03 00:00:00 +0000"];
NSTimeInterval secondsBetween = [date2 timeIntervalSinceDate:date1];
Fill in with the correct times and dates to get the difference in seconds
Edit: An alternative method is to work out the currenthour and return as an integer. Then add one to the NSInteger returned as below (you will have to make sure to handle the case where it is after midnight though!)
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit fromDate:[NSDate date]];
NSInteger currentHour = [components hour];

How to show time which is less than a day?

This code will return number of days between two days.
How to determine when time will pass and day become hours like (20 hours) or (40 minutes), then how do I check that it's a day or less than a day.
-(NSInteger)numberOfDaysUntilDay:(NSDate *)aDate
{
NSCalendar *calendar = [[NSCalendar alloc]
initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *components = [calendar components:NSCalendarUnitDay
fromDate:self
toDate:aDate options:kNilOptions];
return [components day];
}
Use NSCalendarUnitSecond instead.
NSDateComponents *components = [calendar components:NSCalendarUnitSecond
fromDate:date
toDate:aDate options:kNilOptions];
Later you can convert seconds to any unit (hour, day, year).

NSDateComponentsFormatter's method stringFromDate:ToDate: is returning a different value every second

I am trying to get the number of days between today's date and a future date.
I call the following every second:
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setYear:2025];
[components setMonth:1];
[components setDay:1];
NSDate *currentDate = [NSDate date];
NSDate *futureDate = [[NSCalendar currentCalendar] dateFromComponents:components];
NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init];
formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorNone;
formatter.allowedUnits = NSCalendarUnitDay;
formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleAbbreviated;
NSString *remainingDaysAsString = [formatter stringFromDate:currentDate toDate:futureDate];
I print the variable remainingDaysAsString every second and it is always different by a day.
3,551d
3,551d
3,550d
3,550d
3,551d
3,550d
3,551d
Has anyone experienced something similar? Does this seem like a bug with the class?
That is strange and looks like a bug. The result of NSDateComponentsFormatter are correct (in my test) if you specify
all units down to seconds:
formatter.allowedUnits = NSCalendarUnitDay + NSCalendarUnitHour + NSCalendarUnitMinute + NSCalendarUnitSecond;
A simpler and reliable method to get the number of days between two dates is
NSDateComponents *comp = [[NSCalendar currentCalendar] components:NSCalendarUnitDay
fromDate:currentDate
toDate:futureDate
options:0];
NSInteger days = comp.day;
I've got the same 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
restricting allowed units and/or 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)
So, in your case, just assign some positive amount of seconds to NSDateComponents:
[components setYear:2025];
[components setMonth:1];
[components setDay:1];
[components setSecond:10];
This will prevent rounding errors.
I don't know about the issue you are describing, just want you to know an alternative method.
NSDate * date1;
NSDate * date2;
//assuming both vars contain a date
NSTimeInterval duration = [date2 timeIntervalSince1970] - [date1 timeIntervalSince1970];
NSLog("#days between %# and %#: %f", date1, date2, duration / (24 * 60 * 60));

Trying to determine if current date is 3 days or less from the end of the month in iOS

I am trying to determine if the current date is in fact three days or less from the end of the month. In other words, if I am in August, then I would like to be alerted if it is the 28,29,30, or 31st. If I am in February, then I would like to be notified when it is the 25,26,27, or 28 (or even 29). In the case of a leap year, I would be alerted from 26th onwards.
My problem is that I am not sure how to perform such a check so that it works for any month. Here is my code that I have thus far:
-(BOOL)monthEndCheck {
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:[NSDate date]];
NSInteger day = [components day];
NSInteger month = [components month];
NSInteger year = [components year];
if (month is 3 days or less from the end of the month for any month) {
return YES;
} else {
return NO;
}
}
Because there are months with 28, 30, and 31 days, I would like a dynamic solution, rather than creating a whole series of if/else statements for each and every condition. Is there a way to do this?
This is how you get the last day of the month:
NSDate *curDate = [NSDate date];
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDateComponents* comps = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit|NSWeekCalendarUnit|NSWeekdayCalendarUnit fromDate:curDate]; // Get necessary date components
// set last of month
[comps setMonth:[comps month]+1];
[comps setDay:0];
NSDate *tDateMonth = [calendar dateFromComponents:comps];
NSLog(#"%#", tDateMonth);
Source: Getting the last day of a month
EDIT (another source): How to retrive Last date of month give month as parameter in iphone
Now you can simply count from the current date.
If < 3 do whatever you wanted to do.
Maybe something like this:
NSTimeInterval distanceBetweenDates = [date1 timeIntervalSinceDate:date2];
double timeInSecondsFor3Days = 280000; //Better use NSDateComponents here!
NSInteger hoursBetweenDates = distanceBetweenDates / timeInSecondsFor3Days;
However I did not test that^^
EDIT: Thanks to Aaron. Do NSDateComponents to calculate the time for three days instead!
First you have to compute the start of the current day (i.e. today at 00.00).
Otherwise, the current day will not count as a full day when computing the
difference between today and the start of the next month.
NSDate *now = [NSDate date];
NSCalendar *cal = [NSCalendar currentCalendar];
NSDate *startOfToday;
[cal rangeOfUnit:NSCalendarUnitDay startDate:&startOfToday interval:NULL forDate:now];
Computing the start of the next month can be done with rangeOfUnit:...
(using a "statement expression" to be fancy :)
NSDate *startOfNextMonth = ({
NSDate *startOfThisMonth;
NSTimeInterval lengthOfThisMonth;
[cal rangeOfUnit:NSCalendarUnitMonth startDate:&startOfThisMonth interval:&lengthOfThisMonth forDate:now];
[startOfThisMonth dateByAddingTimeInterval:lengthOfThisMonth];
});
And finally the difference in days:
NSDateComponents *comp = [cal components:NSCalendarUnitDay fromDate:startOfToday toDate:startOfNextMonth options:0];
if (comp.day < 4) {
// ...
}

How can I get the monday of a week in a special date in Objective-C?

e.g. 01.10.2010 is friday => 27.09.2010 is monday.
I have no idea how to manage this one. btw: how can I calculate with dates?
For time/date calculations use NSDateComponents.
Listing 2 Getting the Sunday in the current week
NSDate *today = [[NSDate alloc] init];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
// Get the weekday component of the current date
NSDateComponents *weekdayComponents = [gregorian components:NSWeekdayCalendarUnit fromDate:today];
/*
Create a date components to represent the number of days to subtract from the current date.
The weekday value for Sunday in the Gregorian calendar is 1, so subtract 1 from the number of days to subtract from the date in question. (If today's Sunday, subtract 0 days.)
*/
NSDateComponents *componentsToSubtract = [[NSDateComponents alloc] init];
[componentsToSubtract setDay: 0 - ([weekdayComponents weekday] - 1)];
NSDate *beginningOfWeek = [gregorian dateByAddingComponents:componentsToSubtract toDate:today options:0];
/*
Optional step:
beginningOfWeek now has the same hour, minute, and second as the original date (today).
To normalize to midnight, extract the year, month, and day components and create a new date from those components.
*/
NSDateComponents *components =
[gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit)
fromDate: beginningOfWeek];
beginningOfWeek = [gregorian dateFromComponents:components];
In later versions, there is a smarter way:
NSCalendar *cal = [NSCalendar currentCalendar];
[cal setFirstWeekday:2]; //2 is monday. 1:Sunday .. 7:Saturday don't set it, if user's locale should determine the start of a week
NSDate *now = [NSDate date];
NSDate *monday;
[cal rangeOfUnit:NSWeekCalendarUnit // we want to have the start of the week
startDate:&monday // we will write the date object to monday
interval:NULL // we don't care for the seconds a week has
forDate:now]; // we want the monday of today's week
If you actually change the weekday that represents the start of the week (Sunday vs. Monday), you should change it back after this snippet.

Resources