Time coming in NSDate format - ios

I'm using ActionSheetDatePicker for time picker i change mode in picker as UIDatePickerModeCountDownTimer to take Hours and Minutes. It works well displayed Hours and min but when i getting Hrs and Min it return me NSDate init.
Ex. I selected 5 min to return me 300, so i get.
Here is code :
ActionSheetDatePicker *datePicker = [[ActionSheetDatePicker alloc] initWithTitle:#"Select a time" datePickerMode:UIDatePickerModeCountDownTimer selectedDate:self.selectedTime target:self action:#selector(ExtrTravelTimeWasSelected:element:) origin:sender];
datePicker.minuteInterval = minuteInterval;
[datePicker showActionSheetPicker];
and delegate
- (void)ExtrTravelTimeWasSelected:(NSDate *)selectedTime element:(id)element {
self.selectedTime = selectedTime;
}
So Is there any way to convert 300(NSDate) to Hr and Min ?

If I'm understanding correctly you're getting the value in seconds. try the following:
-(NSString *)getTimeStringFromSeconds:(double)seconds
{
NSDateComponentsFormatter *dcFormatter = [[NSDateComponentsFormatter alloc] init];
dcFormatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
dcFormatter.allowedUnits = NSCalendarUnitHour | NSCalendarUnitMinute;
dcFormatter.unitsStyle =
NSDateComponentsFormatterUnitsStylePositional;
return [dcFormatter stringFromTimeInterval:seconds];
}

Try this, I have updated your code,
- (void)ExtrTravelTimeWasSelected:(NSDate *)selectedTime element:(id)element {
self.selectedTime = selectedTime;
// Split up the date components
NSDateComponents *time = [[NSCalendar currentCalendar]
components:NSCalendarUnitHour | NSCalendarUnitMinute
fromDate:selectedTime];
NSLog(#"h:mm::%zd:%2zd",[time hour],[time minute]);
}

Related

How to fix "exc_bad_instruction (code=exc_i386_invop subcode=0x0)"

I want to make a timer in my app, but i find a problem like this:
My code run very well at viewController which contain the code below, but after i dismiss this viewController a few seconds, Xcode will report an erro: exc_bad_instruction (code=exc_i386_invop subcode=0x0
If i didn't use dispatch_suspend(self.timer) and dispatch_resume(self.timer), everything will be ok
otherwise, I can see the error will happen when Xcode deal with [viewController .cxx_destruct]
so, anyone can tell me how to fix it?
Thank you
Here is my code,
- (void)timeButtonClick:(UIButton *)button{
if (self.isTiming == YES){
self.isTiming = NO;
self.executeTime = [self.timeButton currentTitle];
dispatch_suspend(self.timer);
}else if (self.isTiming == NO){
self.isTiming = YES;
self.createDate = [NSDate date];
dispatch_resume(self.timer);
}
}
- (dispatch_object_t)timer{
if (_timer == nil) {
dispatch_queue_t timerQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, timerQueue);
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
__weak typeof (self)weakSelf = self;
dispatch_source_set_event_handler(timer, ^{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"HH:mm:ss";
NSCalendar *calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
NSCalendarUnit unit = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSDate *currentDate = [NSDate date];
NSDateComponents *currentCmps = [calendar components:unit fromDate:weakSelf.createDate toDate:currentDate options:NSCalendarWrapComponents];
NSDate *executeTime = [dateFormatter dateFromString:weakSelf.executeTime];
NSDateComponents *executeCmps = [calendar components:unit fromDate:executeTime];
NSString *newExecuteTime = [NSString stringWithFormat:#"%02ld:%02ld:%02ld", executeCmps.hour + currentCmps.hour, executeCmps.minute + currentCmps.minute, executeCmps.second + currentCmps.second];
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.timeButton setTitle:newExecuteTime forState:UIControlStateNormal];
});
});
_timer = timer;
}
return _timer;
}
Move your timer to another place like AppDelegate, you are getting this error because you are dispatching the timer to another thread, then when the VC is dismissed the timer wants to set the title to timeButton which wa salready destroyed when the VC was dismissed.
You could also try to make a strong weakself, but I really suggest you to move the timer code to another class.

Handle Foursquare hours API to find out if the venue is opened or closed

I have a foursquare hours array (Foursquare API) that stores segments of hours when a specific venue is open. It looks something like this:
[{
"days":[1,2,3,4,7],
"includesToday":true,
"open":[
{"end":"+0200","start":"1000"}],
"segments":[]},
{
"days":[5,6]
,"open":[
{"end":"+0300","start":"1000"}],
"segments":[]}
]
How do I find out if the venue is opened or closed at current time?
I handle it like this: 4sq hours API gist
-(NSDictionary*)isVenueOpenDictionaryForHours:(NSArray*)hours{
// defaults and inits
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDictionary *lastSegmentYesterday = [[NSDictionary alloc] init];
NSDate *dateNow = [NSDate date];
NSString *venueOpenText = [[NSString alloc] init];
NSString *venueOpen = #"no";
// get components for today
NSDateComponents *compsNow = [gregorian components:NSWeekdayCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit|NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit fromDate:dateNow];
// get weekday for today and yesterday so we can lookup 4sq API
NSInteger weekday = [compsNow weekday];
NSInteger weekdayYesterday = (weekday>1)?weekday-1:7;
// look for todays' segment
NSMutableArray *venueOpenSegments = [[NSMutableArray alloc] init]; // stores all the segments when the venue is open
for (NSDictionary *segment in hours){
// get today's segment (if it exists)
if ([segment[#"days"] containsObject:[NSNumber numberWithInteger:weekday]]){
for (NSDictionary *dictOpen in segment[#"open"])
[venueOpenSegments insertObject:#{#"end": [dictOpen[#"end"] mutableCopy], #"start":[dictOpen[#"start"] mutableCopy]}.mutableCopy atIndex:venueOpenSegments.count];
}
// check the day before if the venue is open past midnight
if (([segment[#"days"] containsObject:[NSNumber numberWithInteger:weekdayYesterday]] && [segment[#"open"] count])){
// get the last segment (that should be the one passing midnight)
NSDictionary *tempSegment = [segment[#"open"] lastObject];
// if it has more than 4 characters it's after midnight ("+02:00"), also, ignore if it closes at midnight
if ([tempSegment[#"end"] length] > 4 && ![tempSegment[#"end"]isEqualToString:#"+0000"]){
// create a new segment that starts at midnight and lasts till the time it closes (early AMs usually)
lastSegmentYesterday = #{#"start":#"0000", #"end":[tempSegment[#"end"] substringFromIndex:1]};
}
}
}
// add last night segment that passes midnight as the first segment of today
if (lastSegmentYesterday.count){
[venueOpenSegments insertObject:lastSegmentYesterday atIndex:0];
}
// go through all the segments and find out if the venue is closed or open
if (venueOpenSegments.count){
NSDateComponents *comps = [[NSDateComponents alloc] init];
NSDateFormatter *timeFormatter = [[NSDateFormatter alloc]init];
timeFormatter.dateFormat = #"HH:mm"; // set time output format
int segmentNumber = 0;
for (NSMutableDictionary *segment in venueOpenSegments){
segmentNumber++;
// confing start date
[comps setDay:compsNow.day];
[comps setMonth:compsNow.month];
[comps setYear:compsNow.year];
[comps setHour:[[segment[#"start"] substringToIndex:2] intValue]];
[comps setMinute:[[segment[#"start"] substringFromIndex:2] intValue]];
NSDate *dateStart = [[[NSCalendar currentCalendar] dateFromComponents:comps] copy];
// config end date
// check if the segment goes to next day
BOOL closesTomorrow = NO;
if ( [segment[#"end"] length]==5 ){
segment[#"end"] = [segment[#"end"] substringFromIndex:1];
closesTomorrow = YES;
}
[comps setHour:[[segment[#"end"] substringToIndex:2] intValue]];
[comps setMinute:[[segment[#"end"] substringFromIndex:2] intValue]];
NSDate *dateEnd = [[[NSCalendar currentCalendar] dateFromComponents:comps] copy];
// add a day if it closes tomorrow
if (closesTomorrow){
NSDateComponents *nextDayComponent = [[NSDateComponents alloc] init];
nextDayComponent.day = 1;
dateEnd = [gregorian dateByAddingComponents:nextDayComponent toDate:dateEnd options:0];
}
// start checking if it's open or closed
// now < segment start
if ([dateNow compare:dateStart] == NSOrderedAscending){
venueOpenText = [NSString stringWithFormat:#"opens at %#",[timeFormatter stringFromDate: dateStart]];
venueOpen = #"later";
break;
}
// segment end < now
else if ([dateEnd compare:dateNow] == NSOrderedAscending){
if (segmentNumber == venueOpenSegments.count){
venueOpenText = [NSString stringWithFormat:#"closed since %#",[timeFormatter stringFromDate: dateEnd]];
break;
}
continue;
}
// segment start < now < segment end
else if ([dateStart compare:dateNow] == NSOrderedAscending && [dateNow compare:dateEnd] == NSOrderedAscending){
venueOpenText = [NSString stringWithFormat:#"open till %#",[timeFormatter stringFromDate: dateEnd]];
venueOpen = #"yes";
break;
}
// rare but possible... last minute of the venue being open (I treat it as closed)
else {
venueOpenText = #"closing right now";
}
}
}
else venueOpen = #"closed today"; // no segments for today, so it's closed for the dayƦ
// return results
return #{#"open":venueOpen, #"string":venueOpenText};
}
and I update my UILabel like this:
NSDictionary *venueOpen = [self isVenueOpenDictionaryForHours:_arrayVenues[indexPath.row][#"hours"]];
label.text = venueOpen[#"string"];
if ([venueOpen[#"open"] isEqualToString:#"no"]){
label.textColor = [UIColor colorWithHexString:#"b91d47" alpha:1]; // red
} else if ([venueOpen[#"open"] isEqualToString:#"yes"]) {
label.textColor = [UIColor colorWithHexString:#"1e7145" alpha:1]; // green
} else if ([venueOpen[#"open"] isEqualToString:#"later"]) {
label.textColor = [UIColor colorWithHexString:#"e3a21a" alpha:1]; // yellow
}
BTW, I use pod 'HexColors' for colorWithHexString methods

Setting Reminder with a dueDate with Recurrence Rule iOS

Question: How do I properly set my reminder due date since I have a recurrence rule?
Here is what the reminder object looks like:
EKReminder <0x1700cf490> {title = Dickens's CANINE GOLD WELLNESS doses[1.00]; **dueDate = (null)**; **completionDate = (null)**; priority = 0; calendarItemIdentifier = D1D99FEA-2BFA-4DB1-9D86-7FB26246B50A; alarms = (
"EKAlarm <0x1780a9420> {triggerInterval = -79200.000000}"
)}
The error I am getting is:
Reminder Error=[A repeating reminder must have a due date.]
You can see in the code that I am fooling around with NSDateComponents as a solution since startDateComponents which I just set the month/day/year and local timezone of the reminder which will produce an all day reminder, which in this case is fine. I will probably move the date components and setting of the due date inside the recurrence section when it is done.
Here is my code:
-(void)setReminders:(NSString *)reminderText
andDate:(NSString *)reminderdate
andPetName:(NSString*)petName
andDose:(NSNumber *)dose {
EKEventStore *store = [[EKEventStore alloc] init];
NSDate * reminderNewDate = [self getDateFromString:reminderdate];
petName = [ConfigOps readProperty:kConfigOpsPetKey];
NSString *reminderTitle = [NSString stringWithFormat:#"%#'s %#", petName, reminderText];
NSUInteger doseCount = 0;
if ([dose integerValue] != 0 || dose != nil) {
doseCount = [dose integerValue];
}
else{
doseCount = 0;//NOTE: looks like purchases will have doses not reminders so set to 0 for now.
}
[store requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {
// access code here
EKReminder *new_reminder = [EKReminder reminderWithEventStore:store];
new_reminder.title = reminderTitle;
new_reminder.calendar = store.defaultCalendarForNewEvents;
//get the date components
NSDateComponents *comp = [[NSDateComponents alloc]init];
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *weekdayComponents =
[gregorian components:(NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit |
NSWeekdayCalendarUnit) fromDate:reminderNewDate];
NSInteger day = [weekdayComponents day];
NSInteger month = [weekdayComponents month];
//NSInteger weekday = [weekdayComponents weekday];//future reference
NSInteger year = [weekdayComponents yearForWeekOfYear];
//Month is dose+month = end of reccurence
month = month + doseCount;
[comp setYear:year];
[comp setMonth:month];
[comp setDay:day];
NSDate *date = [gregorian dateFromComponents:comp];
NSTimeZone *myNSTimeZone = gregorian.timeZone;
NSDateComponents *start = new_reminder.startDateComponents;
start.timeZone = myNSTimeZone;
start.month = [weekdayComponents month];
start.day = [weekdayComponents day];
start.year = [weekdayComponents yearForWeekOfYear];
new_reminder.startDateComponents = start;
new_reminder.dueDateComponents = start;
new_reminder.completed = NO;
//Create alarm 22 hours before
double alarmAmountInSeconds = 60.0*60.0*22.0;
EKAlarm *alarm = [EKAlarm alarmWithRelativeOffset:(-1.0*alarmAmountInSeconds)];
[new_reminder addAlarm:alarm];
//new_reminder.alarms = [NSArray arrayWithObject:alarm];
//create nice text for note.
//Hey there! petName needs remindertext from your friendly clinic, clinicName!
new_reminder.notes = reminderText;
if (doseCount != 0) {
EKRecurrenceRule *recurranceRule = [[EKRecurrenceRule alloc] initRecurrenceWithFrequency:EKRecurrenceFrequencyMonthly
interval:1
end:[EKRecurrenceEnd recurrenceEndWithOccurrenceCount:doseCount]
];
new_reminder.calendar = [store defaultCalendarForNewReminders];
[new_reminder addRecurrenceRule:recurranceRule];
}
NSError *er;
//EKEventEditViewController
BOOL success = [store saveReminder:new_reminder commit:YES error:&er];
if (success) {
// Handle here
NSString *alertMessage = [NSString stringWithFormat:#"Reminder Created for\n%#", reminderTitle];
NSString *alertTitle = #"Please check your Reminders";
UIAlertView *alertR = [[UIAlertView alloc]initWithTitle: alertTitle
message: alertMessage
delegate: self
cancelButtonTitle:nil
otherButtonTitles:#"OK",nil];
[alertR show];
}
else{
//log error
NSLog(#" Reminder Error=[%#]", [er localizedDescription]);
//log to error table in database &inform Flurry?
}
}];
}
The method works if there is no recurrence set since it doesn't require a start date/due date.
After some fixing of the date (I found was returning nil), I found that I have to set and end recurrence rule when adding a dose amount.
Here is the code which gets rid of the error (which is pretty funny of Apple to have - kudos Apple!).
if (doseCount != 0) {
EKRecurrenceRule *recurranceRule = [[EKRecurrenceRule alloc] initRecurrenceWithFrequency:EKRecurrenceFrequencyMonthly
interval:1
end:[EKRecurrenceEnd recurrenceEndWithOccurrenceCount:doseCount]
];
new_reminder.calendar = [store defaultCalendarForNewReminders];
//FIX for : recuurence end - Reminder Error = [A repeating reminder must have a due date.]
EKRecurrenceEnd *endRec = [EKRecurrenceEnd recurrenceEndWithEndDate:date];
EKRecurrenceRule *recur = [[EKRecurrenceRule alloc]initRecurrenceWithFrequency:EKRecurrenceFrequencyDaily interval: 1 end:endRec];
unsigned unitFlags= NSYearCalendarUnit|NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit |NSMinuteCalendarUnit|NSSecondCalendarUnit|NSTimeZoneCalendarUnit;
NSDateComponents *dailyComponents=[gregorian components:unitFlags fromDate:date];
[new_reminder setDueDateComponents:dailyComponents];
[new_reminder addRecurrenceRule:recur];
//add it.
[new_reminder addRecurrenceRule:recurranceRule];
}
Hope this helps someone get through this.
For those looking for Swift version:
func editReminder(r: EKReminder) -> Bool {
if(r.recurrenceRules.count > 0 && r.dueDateComponents == nil) {
let startDate = NSDate()
let dueDate: NSDate = NSDate(timeIntervalSinceNow: 31536000) // 1 year from now
let gregorian = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
let unitFlags = NSCalendarUnit(UInt.max)
r.dueDateComponents = gregorian?.components(unitFlags, fromDate: dueDate)
}
var error: NSError?
return eventStore.saveReminder(r, commit: true, error: &error)
}

Setting the minimum and maximum date for a UIDatepicker which is put inside a Action sheet

I am using this class for displaying the date picker called Action sheet picker,
The problem which I am having is that I am not able to set the max and min date of the picker.
The code which i am using is this:
In tableview didSelectRow method:
else if (indexPath.row == 2){
// here I am using a library which successfully shows a date picker from where we can pick the dates on which we want to request the appt.
_actionSheetPicker = [[ActionSheetDatePicker alloc] initWithTitle:#""
datePickerMode:UIDatePickerModeTime selectedDate:self.selectedDateandTime
target:self action:#selector(dateWasSelected:element:) origin:[self.tableView
cellForRowAtIndexPath:indexPath]];
[self.actionSheetPicker addCustomButtonWithTitle:[self.selectedDateandTime
getDateStringInFormat:#"dd MMM yyyy"] value:Nil];
UIDatePicker * picker = (UIDatePicker *)[self.actionSheetPicker
configuredPickerView];
[picker setMinimumDate:self.selectedDateandTime];
self.actionSheetPicker.hideCancel = NO;
[self.actionSheetPicker showActionSheetPicker];
}
the configuredPickerView method is in the library which is as follows:
- (UIView *)configuredPickerView {
CGRect datePickerFrame = CGRectMake(0, 40, self.viewSize.width, 216);
UIDatePicker *datePicker = [[UIDatePicker alloc] initWithFrame:datePickerFrame];
datePicker.datePickerMode = self.datePickerMode;
[datePicker setDate:self.selectedDate animated:NO];
[datePicker addTarget:self action:#selector(eventForDatePicker:) forControlEvents:UIControlEventValueChanged];
//need to keep a reference to the picker so we can clear the DataSource / Delegate when dismissing (not used in this picker, but just in case somebody uses this as a template for another picker)
self.pickerView = datePicker;
return datePicker;
}
Please if somebody can help me out on this matter. if any extra help is required to understand the architecture of the class then please feel free to ask me.
I have faced the same problem , so i wrote a method in ActionSheetPicker-3.0
Add This in your ActionSheetPicker-3.0 Library.
// ActionSheetDatePicker.h
+ (id)showPickerWithTitle:(NSString *)title datePickerMode:(UIDatePickerMode)datePickerMode selectedDate:(NSDate *)selectedDate minimumDate:(NSDate *)minimumDate maximumDate:(NSDate *)maximumDate target:(id)target action:(SEL)action origin:(id)origin;
// ActionSheetDatePicker.m
+ (id)showPickerWithTitle:(NSString *)title datePickerMode:(UIDatePickerMode)datePickerMode selectedDate:(NSDate *)selectedDate minimumDate:(NSDate *)minimumDate maximumDate:(NSDate *)maximumDate target:(id)target action:(SEL)action origin:(id)origin;
{
ActionSheetDatePicker *picker = [[ActionSheetDatePicker alloc] initWithTitle:title datePickerMode:datePickerMode selectedDate:selectedDate target:target action:action origin:origin];
picker.minimumDate = minimumDate;
picker.maximumDate = maximumDate;
[picker showActionSheetPicker];
return picker;
}
write this code where you want to use ActionSheetDatePicker
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *maximumDateComponents = [calendar components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:[NSDate date]];
[maximumDateComponents setYear:2050];
NSDate *minDate = [NSDate date];
NSDate *maxDate = [calendar dateFromComponents:maximumDateComponents];
[ActionSheetDatePicker showPickerWithTitle:#"Select Start Date" datePickerMode:UIDatePickerModeDate selectedDate:[NSDate date] minimumDate:minDate maximumDate:maxDate target:self action:#selector(dateSelected:element:) origin:sender];
You can use UIDatePicker properties:-
#property (nonatomic, retain) NSDate *minimumDate; //
#property (nonatomic, retain) NSDate *maximumDate; // default is nil

UIDatePicker odd behavior when setting minuteInterval

The following code displays an odd behavior under iOS 4.3 (maybe others version too). In this example, a UIDatePicker whose date is set to 4 Aug 2011 2:31 PM is displayed. The UILabel below the UIDatePicker displays the date for reference. The three UIButtons below, labeled 1, 5, 10 set the minuteInterval on the UIDatePicker.
Tapping 1 - shows the selected date in the UIDatePicker to be 4 Aug 2011 2:31 PM, and the minute interval is 1, which is to be expected.
Tapping 5 - shows the selected date in the UIDatePicker to be 4 Aug 2011 2:35 PM, and the minute interval is 5, which is to be expected (one could argue the time should round down, but that is not a huge issue).
Tapping 10 - shows the selected date in the UIDatePicker to be 4 Aug 2011 2:10 PM, and the minute interval is 10. Okay the minute interval is correct, but the selected time is 2:10? One would have expected 2:40 (if rounded up) or 2:30 (if rounded down).
BugDatePickerVC.h
#import <UIKit/UIKit.h>
#interface BugDatePickerVC : UIViewController {
NSDateFormatter *dateFormatter;
NSDate *date;
UIDatePicker *datePicker;
UILabel *dateL;
UIButton *oneB;
UIButton *fiveB;
UIButton *tenB;
}
- (void) buttonEventTouchDown:(id)sender;
#end
BugDatePickerVC.m
import "BugDatePickerVC.h"
#implementation BugDatePickerVC
- (id) init
{
if ( !(self = [super init]) )
{
return self;
}
dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"d MMM yyyy h:mm a";
date = [[dateFormatter dateFromString:#"4 Aug 2011 2:31 PM"] retain];
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Date picker
datePicker = [[UIDatePicker alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 216.0f)];
datePicker.date = date;
datePicker.minuteInterval = 1;
[self.view addSubview:datePicker];
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Label with the date.
dateL = [[UILabel alloc] initWithFrame:CGRectMake(10.0f, 230.0f, 300.0f, 32.0f)];
dateL.text = [dateFormatter stringFromDate:date];
[self.view addSubview:dateL];
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Button that set the date picker's minute interval to 1.
oneB = [UIButton buttonWithType:UIButtonTypeRoundedRect];
oneB.frame = CGRectMake(10.0f, 270.0f, 100.0f, 32.0f);
oneB.tag = 1;
[oneB setTitle:#"1" forState:UIControlStateNormal];
[oneB addTarget:self
action:#selector(buttonEventTouchDown:)
forControlEvents:UIControlEventTouchDown];
[self.view addSubview:oneB];
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Button that set the date picker's minute interval to 5.
fiveB = [UIButton buttonWithType:UIButtonTypeRoundedRect];
fiveB.frame = CGRectMake(10.0f, 310.0f, 100.0f, 32.0f);
fiveB.tag = 5;
[fiveB setTitle:#"5" forState:UIControlStateNormal];
[fiveB addTarget:self
action:#selector(buttonEventTouchDown:)
forControlEvents:UIControlEventTouchDown];
[self.view addSubview:fiveB];
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Button that set the date picker's minute interval to 10.
tenB = [UIButton buttonWithType:UIButtonTypeRoundedRect];
tenB.frame = CGRectMake(10.0f, 350.0f, 100.0f, 32.0f);
tenB.tag = 10;
[tenB setTitle:#"10" forState:UIControlStateNormal];
[tenB addTarget:self
action:#selector(buttonEventTouchDown:)
forControlEvents:UIControlEventTouchDown];
[self.view addSubview:tenB];
return self;
}
- (void) dealloc
{
[dateFormatter release];
[date release];
[datePicker release];
[dateL release];
[oneB release];
[fiveB release];
[tenB release];
[super dealloc];
}
- (void) buttonEventTouchDown:(id)sender
{
datePicker.minuteInterval = [sender tag];
}
Okay so I was able to change the behavior by explicitly setting the UIDatePicker date value to the date rounded to the minute interval using the following code:
- (void) handleUIControlEventTouchDown:(id)sender
{
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Set the date picker's minute interval.
NSInteger minuteInterval = [sender tag];
// Setting the date picker's minute interval can change what is selected on
// the date picker's UI to a wrong date, it does not effect the date
// picker's date value.
//
// For example the date picker's date value is 2:31 and then minute interval
// is set to 10. The date value is still 2:31, but 2:10 is selected on the
// UI, not 2:40 (rounded up) or 2:30 (rounded down).
//
// The code that follow's setting the date picker's minute interval
// addresses fixing the date value (and the selected date on the UI display)
// . In the example above both would be 2:30.
datePicker.minuteInterval = minuteInterval;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Calculate the proper date value (and the date to be selected on the UI
// display) by rounding down to the nearest minute interval.
NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSMinuteCalendarUnit fromDate:date];
NSInteger minutes = [dateComponents minute];
NSInteger minutesRounded = ( (NSInteger)(minutes / minuteInterval) ) * minuteInterval;
NSDate *roundedDate = [[NSDate alloc] initWithTimeInterval:60.0 * (minutesRounded - minutes) sinceDate:date];
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Set the date picker's value (and the selected date on the UI display) to
// the rounded date.
if ([roundedDate isEqualToDate:datePicker.date])
{
// We need to set the date picker's value to something different than
// the rounded date, because the second call to set the date picker's
// date with the same value is ignored. Which could be bad since the
// call above to set the date picker's minute interval can leave the
// date picker with the wrong selected date (the whole reason why we are
// doing this).
NSDate *diffrentDate = [[NSDate alloc] initWithTimeInterval:60 sinceDate:roundedDate];
datePicker.date = diffrentDate;
[diffrentDate release];
}
datePicker.date = roundedDate;
[roundedDate release];
}
Pay attention to the part where the UIDatePicker's date is set twice. It was fun figuring that out.
Anyone know how to turn the animation off for the call to minuteInterval? The phantom scrolling when clicking 5 then 10 is a little unsightly.
I used the above solution by mmoris and created a method that returns rounded date.. (for ARC)
- (NSDate *)getRoundedDate:(NSDate *)inDate{
NSDate *returnDate;
NSInteger minuteInterval = 10;
NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSMinuteCalendarUnit fromDate:inDate];
NSInteger minutes = [dateComponents minute];
NSInteger minutesRounded = ( (NSInteger)(minutes / minuteInterval) ) * minuteInterval;
NSDate *roundedDate = [[NSDate alloc] initWithTimeInterval:60.0 * (minutesRounded - minutes) sinceDate:inDate];
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Set the date picker's value (and the selected date on the UI display) to
// the rounded date.
if ([roundedDate isEqualToDate:inDate])
{
// We need to set the date picker's value to something different than
// the rounded date, because the second call to set the date picker's
// date with the same value is ignored. Which could be bad since the
// call above to set the date picker's minute interval can leave the
// date picker with the wrong selected date (the whole reason why we are
// doing this).
NSDate *diffrentDate = [[NSDate alloc] initWithTimeInterval:60 sinceDate:roundedDate];
returnDate = diffrentDate;
//[diffrentDate release];
}
returnDate = roundedDate;
return returnDate;
}
Here's yet another approach, with an Objective-C category!
I took the spirit of #zurbergram's rounding behavior (up/down to closest) and #mmorris's overall answer and came up with this category:
#import <UIKit/UIKit.h>
#interface UIDatePicker (SetDateRounded)
-(void)setMinimumDateRoundedByMinuteInterval:(NSDate *)minimumDate;
-(void)setDateRoundedByMinuteInterval:(NSDate *)date animated:(BOOL)animatedYesNo;
#end
#implementation UIDatePicker (SetDateRounded)
-(void)setDateRoundedByMinuteInterval:(NSDate *)date animated:(BOOL)animatedYesNo
{
NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSMinuteCalendarUnit fromDate:date];
NSInteger minutes = [dateComponents minute];
NSInteger minutesRounded = roundf((float)minutes / (float)[self minuteInterval]) * self.minuteInterval;
NSDate *roundedDate = [[NSDate alloc] initWithTimeInterval:60.0 * (minutesRounded - minutes) sinceDate:date];
[self setDate:roundedDate animated:animatedYesNo];
}
-(void)setMinimumDateRoundedByMinuteInterval:(NSDate *)date
{
NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSMinuteCalendarUnit fromDate:date];
NSInteger minutes = [dateComponents minute];
NSInteger minutesRounded = roundf((float)minutes / (float)[self minuteInterval]) * self.minuteInterval;
NSDate *roundedDate = [[NSDate alloc] initWithTimeInterval:60.0 * (minutesRounded - minutes) sinceDate:date];
[self setMinimumDate:roundedDate];
}
#end
Then in your implementation, you can do something like this:
#import "UIDatePicker+SetDateRounded.h"
...
- (void)viewDidLoad
{
[super viewDidLoad];
_datePicker.minuteInterval = 15;
[_datePicker setMinimumDateRoundedByMinuteInterval:[NSDate date]];
[_datePicker setDateRoundedByMinuteInterval:[NSDate date] animated:YES];
}
...
Bonus method: setMinimumDateRoundedByMinuteInterval: lets you set the picker's initial minimum to match the same behavior. One refactor would be to abstract the actual calculation part out into its own method, instead of the copy pasta, but I'm sure folks can optimize that for themselves.
Here is an update version of getRoundedDate: that rounds up or down so that 1:03 pm rounds down to 1:00 pm and 1:12 pm rounds up to 1:15pm
-(NSDate *)getRoundedDate:(NSDate *)inDate
{
NSInteger minuteInterval = 15;
NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSMinuteCalendarUnit fromDate:inDate];
NSInteger minutes = [dateComponents minute];
float minutesF = [[NSNumber numberWithInteger:minutes] floatValue];
float minuteIntervalF = [[NSNumber numberWithInteger:minuteInterval] floatValue];
// Determine whether to add 0 or the minuteInterval to time found by rounding down
NSInteger roundingAmount = (fmodf(minutesF, minuteIntervalF)) > minuteIntervalF/2.0 ? minuteInterval : 0;
NSInteger minutesRounded = ( (NSInteger)(minutes / minuteInterval) ) * minuteInterval;
NSDate *roundedDate = [[NSDate alloc] initWithTimeInterval:60.0 * (minutesRounded + roundingAmount - minutes) sinceDate:inDate];
return roundedDate;
}
I had the same problem with a UIDatePicker only with hours and minutes, every time I selected a time the picker add 20 minutes in the UI, not in the selected time. The solution was quite easy in my case, set the picker.minuteInterval=5 before setting the value of the picker.
Hope this will help to other people.
zurbergram code in Swift :
func getRoundedDate(inDate: NSDate) -> NSDate {
let minuteInterval = 15
let dateComponents = NSCalendar.currentCalendar().components(NSCalendarUnit.MinuteCalendarUnit, fromDate: inDate)
let minutes = dateComponents.minute
let minutesF = NSNumber(integer: minutes).floatValue
let minuteIntervalF = NSNumber(integer: minuteInterval).floatValue
// Determine whether to add 0 or the minuteInterval to time found by rounding down
let roundingAmount = (fmodf(minutesF, minuteIntervalF)) > minuteIntervalF/2.0 ? minuteInterval : 0
let minutesRounded = (minutes / minuteInterval) * minuteInterval
let timeInterval = NSNumber(integer: (60 * (minutesRounded + roundingAmount - minutes))).doubleValue
let roundedDate = NSDate(timeInterval: timeInterval, sinceDate: inDate )
return roundedDate
}
Swift 4 version
func round(date: Date, for minuteInterval: Int) -> Date {
let dateComponents = Calendar.current.dateComponents([.minute], from: date)
let minutes = dateComponents.minute!
// Determine whether to add 0 or the minuteInterval to time found by rounding down
let intervalRemainder = Double(minutes).truncatingRemainder(
dividingBy: Double(minuteInterval)
)
let halfInterval = Double(minuteInterval) / 2.0
let roundingAmount: Int
if intervalRemainder > halfInterval {
roundingAmount = minuteInterval
} else {
roundingAmount = 0
}
let minutesRounded = minutes / minuteInterval * minuteInterval
let timeInterval = TimeInterval(
60 * (minutesRounded + roundingAmount - minutes)
)
let roundedDate = Date(timeInterval: timeInterval, since: date)
return roundedDate
}

Resources