I have an NSTimeInterval, and I'd like to know whether that timestamp falls between the start and end of a date. Basically I need to be able to do something like:
NSDate *today = [NSDate date];
NSTimeInterval myInterval = someInterval;
[date returnYesIfFallsThisDate:myInterval];
Is there a straight forward method to figure out if my NSTimeInterval falls on the same day as an NSDate object?
Thanks all.
An NSTimeInterval is just a number of seconds. To compare it to an NSDate, you need to interpret the time interval relative to some reference point, then create an NSDate with the result, then compare the two NSDates.
There are two standard reference dates: 2001-01-01 and 1970-01-01 (the latter being “UNIX time”). If neither of those is the correct reference date, use NSCalendar to create an NSDate for the correct reference date, then create your NSDate of interest relative to that.
Now for the comparison.
If all you care about is the date on the calendar, then you'll create the NSDate, then use an NSCalendar to get NSDateComponents for the date. Specifically, you'll get the year, month, and day-of-the-month, then compare those.
There's no need to worry about “start” and “end” times; in fact, if you only care about the date on the calendar, you can ignore the time-of-day entirely.
Keep in mind that NSDate doesn't represent a "day" in any form, just a point in time. You'll have to do a little work with NSDateComponents, converted using an NSCalendar (typically the user's default calendar) to figure out the start and end NSDate values, and compare use those to compare your time interval to.
I would start by taking the NSDate that falls within your day, and converting it to an NSDateComponents, but without hours, minutes or seconds. Here's a quick (untested) example to help you get started:
NSDateComponents *comps = [[NSCalendar currentCalendar] components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:[NSDate date]];
NSDateComponents *day = [[[NSDateComponents alloc] init] autorelease];
[day setDay:1];
NSDate *start = [[NSCalendar currentCalendar] dateFromComponents:comps];
NSDate *end = [[NSCalendar currentCalendar] dateByAddingComponents:day toDate:start options:0];
Then you can compare the time interval as suggested by Mr. Jalkut (or another method, NSDate can work with NSTimeIntervals in a few different ways). I would definitely spend some time looking over NSCalendar and NSDateComponents in the docs though, you might find a better method than what I'm suggesting for what you need to do.
Is it just me or does the OP seem to think that a NSTimeInterval is a representation of an interval in time from A to B? I.e. an absolute distance (in seconds) from Date A to Date B.
The original question needs to be repose to state that you need to find it based on the reference date. Something like this:
NSDate *refDate = someReferenceDate;
NSTimeInterval interval = someInterval;
NSDate *today = [NSDate date];
BOOL isInRange = false;
isInRange = [today isInterval:interval inRangeFromReference:refDate];
Then that method would use some (or parts of all) of the startegies that Damiel, Peter and Marc mention above.
Conceptually you want two dates that constitute the "start" and "end" of the target date in question. Then you can test the time interval directly against the respective time intervals of those dates.
For instance, if I live in San Francisco, then I want the "start" to be 12AM pacific time, and the end to be 11:59:59.99999 PM pacific time.
Once you've figured out what time zones/etc you want to consider as the start and end points, you can just use the time intervals to do the test:
if ((myInterval >= [earlyMorningDate timeIntervalSinceReferenceDate]) &&
(myInterval <= [lateEveningDate timeIntervalSinceReferenceDate]))
{
NSLog(#"Whoo hoo - it's the right date!");
}
Related
I am creating an iOS app to track attendance. Each attendance entry is stored in an object which has a status attribute (e.g. present, absent) and an NSDate attribute called date which denotes the day which that attendance record was taken. When I select a particular date (using a UIDatePickerView or alike) I want all the attendance records (objects) for that date to appear in a table view.
While this sounds simple in principle, I am running into an issue relating to timezones. I am aware that NSDates are stored independent of timezones (i.e. they are stored relative to UTC/GMT +0000). This means that if I am in Sydney and take attendance on, for example, Sunday 4 November 2012 because the date is stored as timezone independent, if I take my iPhone/iPad to a different time zone (such as San Francisco) all the attendance records would shift one day back, in this case to Saturday 3 November 2012, because that was the moment in time when the attendance was taken in San Francisco local time (which was actually the next day, in Sydney local time).
I don't want this to happen - I want the date to be absolute. In other words, if the attendance is taken on Sunday 4 November 2012 then it needs to stay on that date, no matter where in the world (and whichever timezone) I may go. As you can see, this is quite in contrast to, say, a calendar application where it is desirable for the timing of appointments to change depending on the timezone.
Any suggestions on a better way to approach this problem would be appreciated. Please keep in mind that I am selecting the date to display using a UIDatePickerView which returns the current NSDate in the timezone independent format, so I also need a way to do an easy comparison (preferably in an NSPredicate since the attendance objects are stored in Core Data) to get all the attendance objects for that particular day.
Have you tried converting the time to it's NSDateComponents? you can then recreate an NSDate from it regardless of the time zone.
Edited to add
// This is just so I can create a date from a string.
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"yyyy-MM-dd HH:mm:ss z"];
// Create a date as recorded in a timezone that isn't mine.
NSDate *localDate = [formatter dateFromString:#"2012-10-30 10:30:00 +0200"];
NSLog(#"Initial Date: %#", localDate);
// this logs 2012-10-30 08:30:00 +0000
// Which is what you would expect, as the original time was 2 hours ahead
NSDateComponents *components = [[NSDateComponents alloc] init];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
components = [gregorian components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit fromDate:localDate];
NSLog(#"Components: %#", components);
// Create a date from these time components in some other time zone
[gregorian setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"EST"]];
NSDate *newDate = [gregorian dateFromComponents:components];
NSLog(#"New Date: %#", newDate);
// This logs 2012-10-30 12:30:00 +0000
// Which is the local EST of 8:30 am expressed in UTC
Which demonstrates how I can turn make 8:30 am in +2 time zone look the same as for a -4 timezone.
I believe the better way for you is to use timestamp since it independ of any time zone. You can use vary methods to convert timestamps to date and back. And implement any logic you wish.
Also you can easily compare them.
I'm trying to get the difference of days between today [NSDate date] and a day that the user selects from a UIDatePicker, d, which will be returned as an integer.
Here's what I'm using to figure out the difference:
NSDate *startDate = d;
NSDate *endDate = [NSDate date];
NSLog(#"startDate: %#",startDate);
NSLog(#"endDate: %#",endDate);
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[gregorian setTimeZone:[NSTimeZone timeZoneWithName:#"GMT"]];
NSDateComponents *components = [gregorian components:NSDayCalendarUnit
fromDate:endDate
toDate:startDate
options:0];
NSInteger days = [components day];
NSLog(#"Days: %i",days);
Both startDate and endDate are returned correctly, but the problem I get is the day difference I get is wrong. For example, when I select today's date, the difference is 0, when I select tomorrow, the difference is still 0, and when I select the day after, the difference is only 1, when it should be 2.
Does anyone have any ideas on what I could do to fix this? It's driving me crazy and I don't know what the deal is with it.
For example, when I select today's date, the difference is 0, when I select tomorrow, the difference is still 0, and when I select the day
after, the difference is only 1, when it should be 2.
assume tomorrows selected date is 05:00 pm and todays current date is 08:00 PM. Then if try to get the difference between the dates based on the number of day then it should be zero. Because the difference between the above mention dates is 2 hour less then one complete day.
I hope this will solve your problem. Always set the component of both the dates which you are comparing to same. Use NSDateComponent for this purpose.
Usually with this type of issue, it has to do with different date formats which causes this exact problem.
Check this question as reference: find total number of days between two dates in iphone
OR
Objective C - calculating the number of days between two dates
I have a web service that returns a JSON object. The response consists of a refreshTime in UNIX timestamp (milliseconds) format. I convert the UNIX time stamp to NSDate by the following code:
NSDate *date = [NSDate dateWithTimeIntervalSince1970:[[timeInUNIX objectAtIndex:i]doubleValue]/1000];
This gives me a time in the format
2009-11-13 19:47:49 +0000
I am only interested in the date object here. This is important since the day is used in NSSortDescriptor and also as section header in tableview and hence I separate the date and time with the following code:
+ (NSDate *)dateWithOutTime:(NSDate *)datDate {
if( datDate == nil ) {
datDate = [NSDate date];
}
NSDateComponents* comps = [[NSCalendar currentCalendar] components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:datDate];
return [[NSCalendar currentCalendar] dateFromComponents:comps];
}
The problem here is the returned NSDate object still contains a junk time value. Is there any way to store only the date in NSDate format?
Can someone help me with this?
Regards,
iSee
NSDate has to have a time - it is a number of seconds since a reference date, it represents a specific point in time.
If you are only interested in the day/month/year sections, the correct way to deal with this is to use a date formatter or date components when presenting the date, rather then when you store it. Alternatively, store the date as a string, but this way you lose any date functionality.
Two possible solutions, both not very pretty:
Take the NSTimeInterval from the date, then truncate it down to the nearest 60*60*24 (seconds*minutes*hours). Something like interval = floor(interval - interval % (60*60*24)). Create a new date from that.
Convert the date into a string showing only the date, then convert that string back to NSDate (might still have junk time components, though, but you can pad the string with 0 values).
How would I be able to determine if a certain NSDate object falls within a certain day? Since all dates calculate to the GMT time zone, a date maybe actually be on the 8th instead of the 9th, for example. What's the best way to account for the time zone difference when calculating things like days, months, day of week, etc.
An NSDate is an absolute point in time. When you want to know which day it falls on, you have to consider your specific time zone. Since you get the NSDateComponents with an NSCalendar, you can use setTimeZone: on the calendar to get the components (month, day, hour etc.) for the time zone you're interested in.
If you deal with "calendar dates" (i.e. dates that should always represent a specific day/month and not an absolute point in time), you could always use a fixed time zone (e.g. GMT) to present the dates. NSDateFormatter (which you use for displaying dates to the user) also has a timeZone property.
You need to set the desired timezone in NSCalendar before using it to break the date into parts:
NSCalendar* gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
[gregorian setTimeZone:[NSTimeZone localTimeZone]]; // or timeZoneWithName:#"GMT" etc.
unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
NSDateComponents* compsDate = [gregorian components:unitFlags fromDate:date];
// [compsDate year] [compsDate month] [compsDate day]
What I used to do was using the timestamp and then adding 86400 seconds ( 24 * 60 * 60 ). As I read a recent question on stackoverflow noting that that is not correct for every day, I want to change this.
So the next thing I came up with was by using NSDateComponents. I simply get the current day and add 1. Now I was wondering how "smart" that is. Like if the day is equal to 31, does it set the day to 1 and the month to whatever it is +1 ( or when it is 12 to 1 again ) ? I can do this manually but that would only work properly for the gregorian calendar so I don't really know whether that would be a good solution either..
It is smart, unless you want it to be dumb.
From the documentation for -[NSCalendar dateByAddingComponents:toDate:options]:
If you specify no options (you pass 0), overflow in a unit carries into the higher units (as in typical addition).
If you did not want units overflowing into higher units, then you would pass "NSWrapCalendarComponents" as the value of the options: parameter.
Be very careful of DST. It is different for various time zones. AND It happens at Midnight
I believe that if you call [NSCalendar date by addingComponents:toDate:options] and your date is between 11-12 pm, you risk missing or re-running a day because of DST.
To get around this, is suggest changing your date to noon on the day and then adding the day.
// increment by 1 calendar day
// and convert back to NSDate objects
NSDateComponents *comps = [[[NSDateComponents alloc] init] autorelease];
[comps setDay:1];
NSCalendar *gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
[gregorian setTimeZone:self.myTimeZone]; // time zone of location;
// Use local noontime for the date to avoid problems around
// midnight, particularly near daylightsavings time discontinuities.
NSUInteger unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
NSDateComponents* dateComponents = [gregorian components:unitFlags fromDate:date];
dateComponents.hour = 12;
NSDate* noonDate = [gregorian dateFromComponents:dateComponents];
NSDate *newDate = [gregorian dateByAddingComponents:comps toDate:noonDate options:0];
I'm not sure about the options:0 setting, but I did test this pretty thoroughly. I use it to get an array of with 10 days of dates. It gives correct wrapping around the various day/month/year discontinuities. Check out SunPose Rise Set on the app store if you want to test it. (yes, it's free)