Giving an example, I have 2 NSDate in the same timezone UTC
NSDate *date1 = [NSDate dateWithString:#"2019-05-31 22:00:00 +0000"];
NSDate *date2 = [NSDate dateWithString:#"2019-06-01 01:00:00 +0000"];
I want to make a function to calculate difference between them in Year, month, day ignore time part. Expect result above
Year: 0
Month: 1
Day: 1
Or another example with
NSDate *date1 = [NSDate dateWithString:#"2019-02-28 22:00:00 +0000"];
NSDate *date2 = [NSDate dateWithString:#"2020-03-01 01:00:00 +0000"];
Will get result
Year: 1
Month: 13
Day: 367
I have tried answers in this question How can I calculate the difference between two dates?
But those approach using NSTimeInterval seems not reliable because of 365 or 366 days of year and does not ignore time. Answer like
NSDateComponents *components;
NSInteger days;
components = [[NSCalendar currentCalendar] components: NSDayCalendarUnit
fromDate: date1 toDate: date2 options: 0];
days = [components day];
Give wrong result too.
You could convert each NSDate value to an NSDateComponents value using NSCalendar's componentsInTimeZone:fromDate: method. This will give you the year, month and day values for your two dates, now implement your difference algorithm, which might be:
subtract the earlier date from the larger one - you can determine that based on comparing the two NSDate values
the year difference is just simple subtraction of the year components of the NSDateComponents values
the month difference is the difference between the month components, if this is negative add 12
the day difference is similar but in the negative case you have to add the length of month, which is 28, 29, 30 or 31 - figuring which is left as an exercise :-) (NSCalendar/NSDate Methods should help here)
While this guess at your required algorithm might be wrong, whatever your algorithm you should be able to implement it based on the year, month and day components. HTH
Update
So my first guess at your algorithm was wrong, my second guess is that your three differences; years, months, days; are all meant to be independent approximations of the difference in the corresponding unit. So the year difference ignores the months, days and time; the month difference ignores the days and time; and the days difference ignores the time. This is why 31 May and 1 June are "1 month" apart - the day is ignored. This guess may also be wrong of course but here is how to do it:
order you two dates so the difference is going to be positive.
get just the year, month and day components (or get them all and then discard the others) – this will discard the time component. Use one of NSCalendar's methods to do this.
your year difference is just the difference between the year components
your month difference is the difference between your month components (which could be negative) plus 12 times your year difference
your day difference can be found using components:fromDateComponents:toDateComponents:options: requesting only the day component
[Note: be careful to use the same timezone as the original dates – this is a bit fiddly as you may need to extract it from the date strings yourself (extract the +hhmm and make a time zone). You must remember that an NSDate does not store the time zone, its just an absolute point in time (so equivalent times in different times zones produce the same NSDate value) and for your calculations you want them based on the original time zone as two times one the same day in one timezone can be on different days in a different time zone...). You can set the timezone of an NSCalendar instance or use methods which take timezones when converting from NSDate to NSDateComponents]
Related
I'm trying to calculate the number of calendar weeks between two dates. I'm using the following code:
[cal ordinalityOfUnit:NSCalendarUnitWeekOfYear inUnit:NSCalendarUnitEra forDate:thenDate];
[cal ordinalityOfUnit:NSCalendarUnitWeekOfYear inUnit:NSCalendarUnitEra forDate:nowDate];
A date of 15-1-2017, a Sunday, results in 105192 weeks, and 16-1-2017, a Monday, results in 105193. That would indicate it is using Monday as the first day of the week. I have verified that cal.firstWeekday is 1. If I change the inUnit from NSCalendarUnitEra to NSCalendarUnitYear it works correctly. Is there a way around this?
I suppose I could subtract one day from both dates but that seems very hackie.
After much experimenting, if you make sure all dates are in GMT and you set cal timeZone to GMT it works as one would expect.
In my iOS app, I have a week number and I need to get the start and end date for that week number.
I'm building an app with which the manager of a company can keep track of the worked hours of staff. These worked hours are processed per day in a custom Registration object.
In this object, the date, begin time, end time and break time are stored and based on those values, the worked hours are calculated.
Then, all Registration objects are stored in a WorkWeek object, containing a week number and an array of registrations. WorkWeek's are constructed based on weeknumbers and run from monday through sunday. In this WorkWeek object, the total worked hours, extra hours and wage are calculated.
Now obviously, I can't reliably calculate extra hours if a Workweek is not a full week that runs from monday through friday. This particularly occurs when the user chooses to get all registrations from a mont from my database. A month does not start on monday and does not end on sunday exactly four weeks later, so i'm dealing with unreliable week object.
Wrapping up
To make sure the information I display in my app is reliable, I need to determine whether a certain week (like week 1 or week 52) contains at least 7 days and, if not, I need to set a bool to FALSE which then triggers a notification to my user.
How can I get the begin and end date of a week based on a weeknumer?
This shows how it could be done:
NSCalendar *cal = [NSCalendar currentCalendar];
// Start of week:
NSDateComponents *comp = [[NSDateComponents alloc] init];
comp.weekday = cal.firstWeekday;
comp.weekOfYear = 1; // <-- fill in your week number here
comp.year = 2015; // <-- fill in your year here
NSDate *startOfWeek = [cal dateFromComponents:comp];
// Add 6 days:
NSDate *endOfWeek = [cal dateByAddingUnit:NSCalendarUnitDay value:6 toDate:startOfWeek options:0];
// Show results:
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
fmt.dateStyle = NSDateFormatterShortStyle;
NSLog(#"%#", [fmt stringFromDate:startOfWeek]);
NSLog(#"%#", [fmt stringFromDate:endOfWeek]);
Some notes:
cal.firstWeekday gives the locale dependent index of the first weekday, e.g.
2 = Monday in Germany, or 1 = Sunday in the U.S. Depending on your needs,
you can also use a constant value here.
It might be necessary to set cal.minimumDaysInFirstWeek, compare
NSDateFormatter reports June 2, 2013 as being in week zero.
The dateByAddingUnit:... method is available in OS X 10.9 or later.
Alternatively, use dateByAddingComponents:....
I have assumed that you use the Gregorian calendar, so that a week has 7 days.
Alternatively, you can add one week and then subtract one day.
I want to get the current date, even if the time has passed midnight. Imagine it's friday night the 6th of June 2014 - we check the date Saturday at 2 am, but we still want this to count as being friday. How would I go about this?
Let's just say we cut it at 9am the next day. I.e. we will assume previous date until the time has passed 9 am. Yes, this is software used at a nightclub, as you can imagine.
I guess this would involve something like subtracting 1 day from the current date if the hour is less than 10?
You can use interval to specific date like
[[NSDate date] dateByAddingTimeInterval:interval];
Assuming the cutoff is 9am, all you need to do is create an NSDate that's 9 hours earlier than the actual time.
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:-(9 * 3600)];
That way every day begins and ends at 9am.
I'm putting together a game where there's a tournament every week, and every week there's a different special bonus for the game.
To make this work I need to know which week it is so I can select the right bonus, and make sure the score goes to the right tournament.
A trivial answer is to take the number of days since epoch, offset to get to a monday, then compute the number of days and divide by 7. Obviously this fails because of leap year.
Another option would be to figure out which week of the year you're on, but that gets weird when you transition from one year to the next. Also, the tournament ends at the end of the day on Sunday, so it doesn't follow the normal week borders.
I was about to start doing some fairly complicated stuff using the year, day of year and day of week to try to figure it out, but I thought I'd ask here in case there was an easy solution I was missing.
This will be done in Objective-C on iOS.
This should work:
// Choose any reference date which is a Monday:
NSCalendar *cal = [NSCalendar currentCalendar];
NSDateComponents *refComp = [[NSDateComponents alloc] init];
refComp.year = 1970;
refComp.month = 1;
refComp.day = 5;
NSDate *refDate = [cal dateFromComponents:refComp];
// Compute number of weeks between your date and the reference date:
NSDateComponents *comp = [cal components:NSCalendarUnitWeekOfYear fromDate:refDate toDate:yourDate options:0];
NSInteger weeks = comp.weekOfYear;
But calculating the number of days (since some Monday) and dividing by 7 should
give the same result because every week has 7 days, regardless of leap years.
I am trying to get the time difference between two NSDates.
NSDate *d1 = somedate;
NSDate *d2 = someOtherdate;
NSTimeInterval sec = [d2 timeIntervalSinceDate:d1];
How accurate is NSTimeInterval?
Does it take care of the variation in number of days in a month, for example if the number of days is 28,29,30 or 31? Also time zone difference?
How accurate is NSTimeInterval
On the platforms I know they have Cocoa, NSTimeInterval is typedeffed to double,
Does it take care of the variation in number of days in a month?
It's not NSTimeInterval that does that, it's a type. It's the various classes (NSDate, whatever) that take care of all these deviances. And they do it really well.
NSTimeInterval is in fact just a typedeffed double. Because NSDate represents a single point in time (independent of time zone, calendar, etc.), NSTimeInterval represents the number of seconds between the two dates (points in time).
NSTimeInterval is actually just a typedef for double.
NSDate encapsulates a time interval and provides an interface to interact with it.
If you want that date to be referenced to our actual calendar, you have to use the NSCalendar and NSDateComponents classes.
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit fromDate:[NSDate date]];
Then you can get data out of the components object:
components.day
components.week
// etc
Assume the Mayan culture never went extinct. This right instant in time would be represented by the same NSDate object, but through a different NSCalendar (like a NSMayanCalendar) you would get a completely different representation of that date.
How accurate is NSTimeInterval?
It's accurate to milliseconds (if not more).
Does it take care of the variation in number of days in a month, for example if the number of days is 28,29,30 or 31?
Yes, the difference will be in seconds. NSDate deals with actual days in a month as well as leap years and daylight savings, etc.
Also time zone difference?
NSDate values are always stored in UTC so your NSDate objects are always in the same timezone. There is nothing to deal with for this.