A UIDatePicker Bug? - ios5

Here's my code:
picker = [[UIDatePicker alloc] initWithFrame:CGRectMake(0,40,0,0)];
picker.datePickerMode = UIDatePickerModeDateAndTime;
picker.minuteInterval = 5;
picker.minimumDate = [NSDate date];
Ok, It's working fine until here. (Image: http://img29.imageshack.us/img29/8277/snap1r.png)
Days that are past in the DatePicker were all grayed out. It can't be selected.
And minute Intervals are 5.
But now when I click any row that were already Grayed out. The date of DatePicker returns time of this moment.
For Example:I cliked "9" on the DatePicker (It's already past time)
And the system time now is
22:27:57
and the Date of DatePicker returns: (Image: http://img42.imageshack.us/img42/2760/nslog.png)
2012-04-08 22:27
Because my minute interval is 5 minute, so I don't hope the picker returns the value that can't be divided by 5, this will cause my program crash.
Is this a Bug? or it's just my problem?
thanks!
------To inspector g (Sorry my English isn't very good)
Because the minuteInterval of Datepicker is 5. So the return value of DatePicker's date only returns minute that can divded by 5 (etc. 0, 5 , 10 , 15 .....)
and also I have the property minimumDate set to [NSDate date], so that users can't select the date in past.
but now of user click a row that was in past (grayed out), the DatePicker's date return the time at that moment.
so the minute of date could be any value (0~60) but not I wished ( 0 , 5 , 10 , 15....)
I've tried my best to explain >"< please forgive.
To Inspector g.
Thanks for your code, I suddenly realized there's a nice way to solve my problem.
But I dunno why, there are some problems if I use your code. (I guess it's about timeZone)
But I follow your logic and re-write a code, I'll share with you:
unsigned unitFlags_ = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit;
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *comps_ = [gregorian components:unitFlags_ fromDate:[jRemindPicker date]];
NSInteger remainder = [comps_ minute] % 5;
NSLog(#"%i-%i-%i %i:%i", comps_.year, comps_.month, comps_.day, comps_.hour, comps_.minute);
if ( remainder ) {
/* My Own code /*
} else {
/* My Own Code /*
}
[gregorian release];

Your description of the problem with selecting a date/time is a little unclear, so perhaps you can clarify? Provide a short screencast?
In any event, it sounds like you cannot select a date before today, so your error is in this line:
picker.minimumDate = [NSDate date];
You are setting the minimum selectable date to the current date and time (as that's what [NSDate date] returns.
Remove that line and you should be able to select whatever date/time you wish.
EDIT
If the problem is that you can't select a date in the future, try setting:
picker.maximumDate = [NSDate distantFuture];
Using your existing minimum and this new maximum, the range of selectable dates will be set to somewhere between today and a very long time after today.
SECOND EDIT
Thanks for clarifying! I see the problem now. When you receive the callback from the user changing the date, you have to round up or down appropriately. You can then use the rounded time at that point, or manually set the picker date/time to the rounded value via setDate: animated:
For example:
-(IBAction) pickerValueChanged:(id)selector_
{
UIDatePicker* picker = (UIDatePicker*) selector_;
// get the minutes from the picker
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDateComponents* components = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit) fromDate:picker.date];
NSInteger minutes = [components minute];
// check if the minutes should be rounded
NSInteger remainder = minutes % 5;
if(remainder)
{
minutes += 5 - remainder;
[components setMinute:minutes];
picker.date = [calendar dateFromComponents:components];
}
// now picker.date is "safe" to use!
}

Related

Math issue using timeIntervalSince 1970

I am trying to find how many milliseconds into the current day we are. I can't find a method to return the time in milliseconds ignoring date, so I figured I could calculate it off of the value returned by timeIntervalSince 1970 method.
I did this:
NSLog(#"%f", [[NSDate date] timeIntervalSince1970]);
2013-05-21 16:29:09.453 TestApp[13951:c07] 1369171749.453490
Now my assumption is that, since there are 86,400 seconds in a day I could divide this value by 86400 and get how many days have elapsed since 1970. Doing this gives me 15846.8952483 days. Now, if my assumption holds, I am 89.52483% through the current day. So multiple 24 hours by 86.52659% would give me a current time of the 21.4859592 hour or about 09:29 PM. As you can see from my NSLog this is about 5 hours from the real time, but I believe the interval returned is GMT so this would be 5 hours ahead of my time zone.
So I figured, well what the heck, I'll just roll with it and see what happens.
I cut off the decimal places by doing:
float timeSince1970 = [[NSDate date] timeIntervalSince1970]/86400.0;
timeSince1970 = timeSince1970 - (int)timeSince1970
Then calculate the milliseconds that have taken place thus far today:
int timeNow = timeSince1970 * 86400000;
NSLog(#"%i", timeNow);
2013-05-21 16:33:37.793 TestApp[14009:c07] 77625000
Then I convert the milliseconds (which still seem appropriate) to NSDate:
NSString *timeString = [NSString stringWithFormat:#"%d", timeNow];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:#"A"]
NSDate *dateNow = [dateFormatter dateFromString:timeString];
NSLog(#"%#", dateNow);
2013-05-21 16:29:09.455 TestApp[13951:c07] 2000-01-02 03:29:00 +0000
And there is my problem. Rather than returning a 2000-01-01 date with some hours and minutes attached, it is returning a 2000-01-02 date. Why!?
EDIT
I got it working by "removing" the extra 5 hours I noted in the above with:
int timeNow = (timeSince1970 * 86400000) - (5 * 60 * 60 * 1000);
I don't understand why this is necessary though. If someone can explain I'd greatly appreciate it.
EDIT 2
Perhaps I should be asking a more elementary question about how to accomplish the task I'm trying to accomplish. I care about times (for example, 4pm is important but I could care less about the date). I've been storing these in NSDates created by:
[dateFormatter setDateFormat:#"hh:mm a"];
[dateFormatter dateFromString#"04:00 PM"];
All this seems to be going fine. Now I want to compare current time to my saved time and find out if it is NSOrderedAscending or NSOrderedDescending and respond accordingly. Is there a better way to be accomplishing this?
You need to use NSCalendar to generate NSDateComponents based on right now, then set the starting hour, minute, and second all to 0. That will give you the beginning of today. Then you can use NSDate's -timeIntervalSinceNow method to get back the time elapsed between now and your start date.
NSCalendar *cal = [NSCalendar currentCalendar];
NSDate *now = [NSDate date];
// BUILD UP NSDate OBJECT FOR THE BEGINNING OF TODAY
NSDateComponents *comps = [cal components: (NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate: now];
comps.hour = 0;
comps.minute = 0;
comps.second = 0;
// USE CALENDAR TO GENERATE NEW DATE FROM COMPONENTS
NSDate *startOfToday = [cal dateFromComponents: comps];
// YOUR ELAPSED TIME
NSLog(#"%f", [startOfToday timeIntervalSinceNow]);
Edit 1
If you're just looking to compare some NSDateObjects you can see if the time interval between then and now is negative. If so, that date is in the past.
NSDate *saveDate = [modelObject lastSaveDate];
NSTimeInterval difference = [saveDate timeIntervalSinceNow];
BOOL firstDateIsInPast = difference < 0;
if (firstDateIsInPast) {
NSLog(#"Save date is in the past");
}
You could also use compare:.
NSDate* then = [NSDate distantPast];
NSDate* now = [NSDate date];
[then compare: now]; // NSOrderedAscending
The part of your question that says that you want to calculate "how many milliseconds into the current day we are" and then "4pm is important but I could care less about the date" makes it not answerable.
This is because "today" there could have been a time change, which changes the number of milliseconds since midnight (by adding or subtracting an hour, for instance, or a leap second at the end of a year, etc....) and if you don't have the date, you can't determine the number of milliseconds accurately.
Now, to address your edited question: If we assume today's date, then you need to use the time that you have stored and combine it with today's date to get a "specific point in time" which you can compare to the current date and time:
NSString *storedTime = #"04:00 PM";
// Use your current calendar
NSCalendar *cal = [NSCalendar currentCalendar];
// Create a date from the stored time
NSDateFormatter *dateFormatter = [NSDateFormatter new];
[dateFormatter setDateFormat:#"hh:mm a"];
NSDate *storedDate = [dateFormatter dateFromString:storedTime];
// Break it up into its components (ie hours and minutes)
NSDateComponents *storedDateComps = [cal components:NSHourCalendarUnit | NSMinuteCalendarUnit
fromDate:storedDate];
// Now we get the current date/time:
NSDate *currentDateAndTime = [NSDate date];
// Break it up into its components (the date portions)
NSDateComponents *todayComps = [cal components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit
fromDate:currentDateAndTime];
// Combine with your stored time
todayComps.hour = storedDateComps.hour;
todayComps.minute = storedDateComps.minute;
// Create a date from the comps.
// This will give us today's date, with the time that was stored
NSDate *currentDateWithStoredTime = [cal dateFromComponents:todayComps];
// Now, we have the current date and the stored value as a date, so it is simply a matter of comparing them:
NSComparisonResult result = [currentDateAndTime compare:currentDateWithStoredTime];
it is returning a 2000-01-02 date. Why!?
Because your dateFormatter uses the current system locale's timezone.
If you insert ...
dateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
... your date formatter will interpret the string correctly. But why not creating the date directly:
NSDate *dateNow = [NSDate dateWithTimeIntervalSinceReferenceDate:timeNow];

Create NSDate at specific time x days from now

I'm trying to learn Objective-C/iPhone SDK and right now I'm doing a kind of to-do app playing with local notifications.
I have a "timeOfDay" ivar stored as an NSDate from a DatePicker and a "numberOfDays" ivar stored as an NSNumber.
When I press a specific button, I would like to schedule a local notification x numberOfDays from the time the button is pressed but at the specific timeOfDay.
I seems easy to add an NSTimeInterval to the current date which would give me the a way to schedule the notification numberOfDays from current time but adding the timeOfDay feature makes it more complex.
What would be the correct way of achieving this?
Thanks
Use NSDateComponents to add time intervals to an existing date while respecting all the quirks of the user's current calendar.
NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
// Get the year, month and day of the current date
NSDateComponents *dateComponents = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit| NSDayCalendarUnit) fromDate:[NSDate date]];
// Extract the hour, minute and second components from self.timeOfDay
NSDateComponents *timeComponents = [calendar components:(NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit) fromDate:self.timeOfDay];
// Apply the time components to the components of the current day
dateComponents.hour = timeComponents.hour;
dateComponents.minute = timeComponents.minute;
dateComponents.second = timeComponents.second;
// Create a new date with both components merged
NSDate *currentDateWithTimeOfDay = [calendar dateFromComponents:dateComponents];
// Create new components to add to the merged date
NSDateComponents *futureComponents = [[NSDateComponents alloc] init];
futureComponents.day = [self.numberOfDays integerValue];
NSDate *newDate = [calendar dateByAddingComponents:futureComponents toDate:currentDateWithTimeOfDay options:0];
There is a pretty simple method to do this that won't involve as many lines of code.
int numDays = 5;
myDate = [myDate dateByAddingTimeInterval:60*60*24*numDays];
+ (id)dateWithTimeInterval:(NSTimeInterval)seconds sinceDate:(NSDate *)date
That should give you what you're looking for.

NSDateComponents is missing 1 second from difference of 2 dates

Got a really frustrating problem that doesn't seem to make any sense. I'm trying to get the number of years between 2 dates. Here is my code.
// Initialize variable to store calendar
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
// Break out date to year component
NSDateComponents *Components = [gregorian components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit
fromDate:startDate
toDate:endDate
options:0];
// Debugging code
NSLog(#"Debug start date = %#",startDate);
NSLog(#"Debug end date = %#", endDate);
NSLog(#"Debug year = %d",[Components year]);
NSLog(#"Debug month = %d",[Components month]);
NSLog(#"Debug day = %d", [Components day]);
NSLog(#"Debug hours = %d",[Components hour]);
NSLog(#"Debug minutes = %d", [Components minute]);
NSLog(#"Debug seconds = %d", [Components second]);
[gregorian release];
// Check which component to extract and return value accordingly
// Defaults to month for now
if ([datecomponent isEqualToString:#"year"]) {
return [Components year];
}
else {
return [Components month];
}
The start and end dates are set by UIDatePickers elsewhere. They would default to being 10 years apart. Once I go back to the UIDatePicker that controls the end date and move that up to 1 year earlier. The problem will start appearing. This is an example of the NSLog I will see after I move the end date back to the original date. I should see 10 years and 0 everywhere else but I'm missing 1 second in time.
Debug start date = 2011-08-15 15:55:07 +0000
Debug end date = 2021-08-15 15:55:07 +0000
Debug year = 9
Debug month = 11
Debug day = 30
Debug hours = 23
Debug minutes = 59
Debug seconds = 59
The start and end dates look identical saved for the 10 years between them to me but for some reason, I'm missing 1 second of time. Does anyone know why?
Thanks in advance!
ADDED
Here is the way I have initialized and stored the 2 dates.
The start date is in the viewWillAppear method.
- (void)viewWillAppear:(BOOL)animated {
if ([managedObject valueForKeyPath:self.keypath] != nil)
[self.datePicker setDate:[managedObject
valueForKeyPath:keypath] animated:YES];
else
[self.datePicker setDate:[NSDate date] animated:YES];
[self.tableView reloadData];
[super viewWillAppear:animated];
}
The same class is also used for my end date however I have also added the following code in my custom subclass of NSManagedObject:-
- (void)awakeFromInsert {
[super awakeFromInsert];
// Set default date of registration to be today's date
self.startdate = [NSDate date];
NSDate *today = [[NSDate alloc] init]; // I also tried to equate this to self.startdate but there is no difference
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
[offsetComponents setYear:10];
self.enddate = [gregorian dateByAddingComponents:offsetComponents toDate:today options:0];
}
Would this have caused any problems? If yes, why would the debug show the start and end dates including their times as exactly the same?
I created a simple project with two UIDatePickers in a single ViewController. I initialized each UIDatePicker to the reference date and set an action method to calculate the difference between the dates of the two UIDatePickers each time either one of them changed. I inserted your code into that method and then tested it out in the iPhone Simulator. The result is that your code outputs exactly 10 years:
2011-08-15 11:18:44.225 test[1004:b303] Debug start date = 2001-01-01 00:00:00 +0000
2011-08-15 11:18:44.225 test[1004:b303] Debug end date = 2011-01-01 00:00:00 +0000
2011-08-15 11:18:44.226 test[1004:b303] Debug year = 10
2011-08-15 11:18:44.226 test[1004:b303] Debug month = 0
2011-08-15 11:18:44.227 test[1004:b303] Debug day = 0
2011-08-15 11:18:44.227 test[1004:b303] Debug hours = 0
2011-08-15 11:18:44.227 test[1004:b303] Debug minutes = 0
2011-08-15 11:18:44.228 test[1004:b303] Debug seconds = 0
Have you initialized the UIDatePickers to the same time before using them? By default, the UIDatePickers are initialized to the date when they were created (this includes hours, minutes, seconds, etc, even if you are only showing year, month, and day in the picker).
Is it possible that you are introducing this discrepancy some other way? Perhaps you are doing some other calculations or manipulations on the date?
EDIT:
In the code that you added, you should reference enddate from startdate, rather than from another date object. I know that you said it didn't make a difference, but it is better form to use startdate rather than assume that today and startdate are going to have the same value.
Another thing to consider would be your setStartDate: and setEndDate: methods. In those setter methods, I would recommend rounding to 0 any precision that you don't need. If you don't allow the user to set anything more precise than the day, then set anything higher precision to 0. This way you won't have any left over hours, minutes, or seconds between any two dates.
I can't reproduce this on 10.7. What SDK are you building against?
2011-08-15 13:51:44.262 test[9420:707] Debug start date = 2011-08-15 15:55:07 +0000
2011-08-15 13:51:44.263 test[9420:707] Debug end date = 2021-08-15 15:55:07 +0000
2011-08-15 13:51:44.263 test[9420:707] Debug year = 10
2011-08-15 13:51:44.264 test[9420:707] Debug month = 0
2011-08-15 13:51:44.264 test[9420:707] Debug day = 0
2011-08-15 13:51:44.265 test[9420:707] Debug hours = 0
2011-08-15 13:51:44.265 test[9420:707] Debug minutes = 0
2011-08-15 13:51:44.266 test[9420:707] Debug seconds = 0
Note that there's no reason to extract day, hour, minute and second here. Also, if datecomponent isn't year, this is going to return 0 in your case. I assume you actually mean for it to return 120? Or are you using this a different way?
Here is my full program (for 10.7, command-line template):
int main (int argc, const char * argv[]) {
#autoreleasepool {
// Initialize variable to store calendar
NSDate *startDate = [NSDate dateWithString:#"2011-08-15 15:55:07 +0000"];
NSDate *endDate = [NSDate dateWithString:#"2021-08-15 15:55:07 +0000"];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
// Break out date to year component
NSDateComponents *components = [gregorian components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit
fromDate:startDate
toDate:endDate
options:0];
// Debugging code
NSLog(#"Debug start date = %#",startDate);
NSLog(#"Debug end date = %#", endDate);
NSLog(#"Debug year = %ld",[components year]);
NSLog(#"Debug month = %ld",[components month]);
NSLog(#"Debug day = %ld", [components day]);
NSLog(#"Debug hours = %ld",[components hour]);
NSLog(#"Debug minutes = %ld", [components minute]);
NSLog(#"Debug seconds = %ld", [components second]);
// [gregorian release];
return 0;
}
}
After quite a bit of tinkering with various scenarios, this is the way which I solved the issue.
1) Since the code to find the difference between the dates checks out ok by several independent parties, I have eliminated it as a source of problem.
2) I have removed any code that attempts to modify the dates in my app which should not have been there in the first place.
3) I now initialize the time as well when I create the start and end dates so as to ensure that they are not some arbitrary time based on when I create the entry in database.
Here's the code snippet to initialize the start date.
NSDate *now = [NSDate date];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *settomidnight = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:now];
[settomidnight setHour:0];
[settomidnight setMinute:0];
[settomidnight setSecond:0];
startdate = [calendar dateFromComponents:settomidnight];
With this, I see from the NSLog that the time is now set as 16:00 +0000 (which is midnight for me after converting to my timezone). The problem has now gone away and I'm no longer losing my 1 second.
Thanks to all who have helped guide me on the right path!

Time picker problem with OS4?

I have an app which I am updating to OS4. In this I use a time picker. From this I want to get an hour and minute as an integar. This is the code I used previously for this...
NSDate *selected = [timePicker date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit| NSMinuteCalendarUnit |NSSecondCalendarUnit;
NSDateComponents *compsDur = [gregorian components:unitFlags fromDate:selected];
int hDur = [compsDur hour];
int mDur = [compsDur minute];
int sDur = [compsDur second];
[gregorian release];
Now all was working fine here. My problem is that when I update to OS4 if I change my phones time settings say from the UK to the US I get a random value not what the user has entered.
To give you a specific example, on UK time I set the time for 1 minute, I get a return value for mDur as 1. If I then change my phone to New York time I get 19 hours and 6 minutes? What's going on?
I think this might be related to an undocumented change to the UIDatePicker in iOS4 where it defaults to (what I think is) GMT as the time zone.
You can set the time zone of the UIDatePicker to the system time zone so the user gets the expected experience.
yourDatePicker.timeZone = [NSTimeZone systemTimeZone];

Iphone SDK: How to Countdown from current time to a specific day in the future

I have was wondering how it would be possible to show the specific time remaining for a date in the future.
This is what i have so far. I can get the current time, and display that, and i used the minutes to midnight tutorial to figure out how to find out what time it will be midnight. But i am suck as to finding out how i would pick a day in the future and find out how much time is left there.
code:
NSDate* now = [NSDate date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComponents = [gregorian components:(NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit) fromDate:now];
NSInteger hour = 23 - [dateComponents hour];
NSInteger minute = 59 - [dateComponents minute];
NSInteger second = 59 - [dateComponents second];
[gregorian release];
countdownLabel.text = [NSString stringWithFormat:#"%02d:%02d:%02d", hour, minute, second];
Any possibility someone could take a look at this and change it up a bit? It would be easiest for me to follow if i could understand it from using some of this code, but if thats not possible or correct I would rather know the right way to do it.
Thanks.
Try one of these, it will be number of second between the two dates in double:
NSTimeInterval timeBetweenThenAndNow = [futureDate timeIntervalSinceNow];
NSTimeInterval timeBetweenThenAndMidnight = [futureDate timeIntervalSinceDate: myMidnightDate];