How to block days in UIDatePicker for iOS - iphone

I'm using a Date picker on my IOS app, and I want to know if is possible to block some days. For example, I need to block all mondays from a year, is this possible?
Thanks.

The only thing you can do is add a custom UIPickerView as described in the comments or implement a method that is called for the event UIControlEventValueChanged as described here
then check the new value for a valid weekday. you can get the weekday with:
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDateComponents* components = [calendar components:NSWeekdayCalendarUnit fromDate:[NSDate date]];
return [components weekday]; // 1 = Sunday, 2 = Monday, ...
There is no way to hide some days in the scrollwheel.

Here is the Swift 4 answer to this question (original answer by Nicholas Harlen):
let datePicker: UIDatePicker = UIDatePicker(frame: .zero)
datePicker.datePickerMode = .date
datePicker.addTarget(self, action: #selector(datePickerValueChanged(_:)), for: .valueChanged)
#objc func datePickerValueChanged(_ sender: UIDatePicker) {
let calender: Calendar = Calendar(identifier: .gregorian)
let weekday = calender.component(.weekday, from: sender.date)
//sunday
if weekday == 1 {
datePicker.setDate(Date(timeInterval: 60*60*24*1, since: sender.date), animated: true)
} else if weekday == 7 {
datePicker.setDate(Date(timeInterval: 60*60*24*(-1), since: sender.date), animated: true)
}
}

I had to do something similar and couldn't find a way of removing dates. However what I ended up doing was if an invalid date is selected it would bounce away to the nearest day that was valid, similar to how the control handles if you set a minimum date and try and choose a date before that.
-(void)init
{
datePicker = [[UIDatePicker alloc] initWithFrame:CGRectZero];
[datePicker setDatePickerMode:UIDatePickerModeDate];
[datePicker setMinimumDate: [NSDate date]];
[datePicker addTarget:self action:#selector(onDatePickerValueChanged:) forControlEvents:UIControlEventValueChanged];
}
-(void)onDatePickerValueChanged:(UIDatePicker *)picker
{
// Work out which day of the week is currently selected.
NSCalendar *gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
NSDateComponents *comps = [gregorian components:NSWeekdayCalendarUnit fromDate:picker.date];
int weekday = [comps weekday];
// Bounce Sundays (weekday=1) forward a day
if (weekday == 1)
{
[datePicker setDate:[NSDate dateWithTimeInterval:60*60*24*1 sinceDate:picker.date] animated: true];
}
// Bounce Saturdays (weekday=7) back a day
else if (weekday == 7)
{
[datePicker setDate:[NSDate dateWithTimeInterval:60*60*24*-1 sinceDate:picker.date] animated: true];
}
}

Related

Set UIDatepicker programmatically

I have a datepicker and 2 buttons. And I want to set the datepicker value programmatically when I click the button. Like when I press the first button the datepicker value becomes today's date and 00:00:00: time and when I press the second button the datepicker value becomes today's date and 23:59:59 time.
How to do that?
Simple task, assuming that you already added two functions beging invoked on button pressing you need something like this:
-(IBAction)beginPressed:(id)sender
{
NSDate * now = [[NSDate alloc] init];
NSCalendar *cal = [NSCalendar currentCalendar];
NSDateComponents * comps = [cal components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:now];
[comps setHour:0];
[comps setMinute:0];
[comps setSecond:0];
NSDate * date = [cal dateFromComponents:comps];
[self.datePicker setDate:date animated:TRUE];
}
-(IBAction)endPressed:(id)sender
{
NSDate * now = [[NSDate alloc] init];
NSCalendar *cal = [NSCalendar currentCalendar];
NSDateComponents * comps = [cal components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:now];
[comps setHour:23];
[comps setMinute:59];
[comps setSecond:59];
NSDate * date = [cal dateFromComponents:comps];
[self.datePicker setDate:date animated:TRUE];
}
I think the part with setting the components wasn't clear to you, check the API of the above classes and you will find more.
Edit: I changed the calendar to the current default calendar and initialized the calendar with now.
NSDateFormatter *dateFormat;
dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"h:mm a"];
NSDate *exampleDateFromString =[dateFormat dateFromString:#"8:48 AM"];
date-picker.minimumDate=exampleDateFromString;
[date-picker setDate:exampleDateFromString];
For those wondering how to do this in Swift:
let now = NSDate()
let cal = NSCalendar.currentCalendar()
let comp = cal.components([.Year, .Month, .Day], fromDate: now)
comp.hour = 0 // or whatever you need
comp.minute = 0
let date = cal.dateFromComponents(comp)!
picker.setDate(date, animated: true)

Unable to unlock the previous months and dates in UIDatepicker And also showing the Time in ios

In my project I need to show the date 18 back and lock the 100 years I tried like this I got it but here am locking days and months for that year(1912) also I need to show all the days and months for that year(1912) and one more thing when I select date it is showing along with time i Don't wanna show the time can any one solve this issue
-(IBAction)donePickerView:(id)sender
{
NSDate *minDate1 =[datePickerView date];
NSString *theDate = [NSString stringWithFormat:#"%#",minDate1];
self.dob.text = theDate;
DatePicView.hidden = TRUE;
datePickerView.hidden =TRUE;
}
-(void)textFieldDidBeginEditing:(UITextField *)textField {
[self scrollViewToCenterOfScreen:textField];
scrlView.scrollEnabled = FALSE;
if (textField == dob)
{
DatePicView.hidden = FALSE;
datePickerView.hidden =FALSE;
NSCalendar * gregorian = [[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar];
NSDate * currentDate = [NSDate date];
NSDateComponents * comps = [[NSDateComponents alloc] init];
[comps setYear: -18];
NSDate * maxDate = [gregorian dateByAddingComponents: comps toDate: currentDate options: 0];
[comps setYear: -100];
NSDate * minDate = [gregorian dateByAddingComponents: comps toDate: currentDate options: 0];
[datePickerView setMinimumDate:minDate];
[datePickerView setMaximumDate:maxDate];
[datePickerView setDate:minDate];
[dob resignFirstResponder];
}
}
You set the maxDate to a -100 years.
So the maxDate is lower then the minDate, should these be the other way arround?
NSLog(#"Now: %#", currentDate);
NSLog(#"Max: %#", maxDate);
NSLog(#"Min: %#", minDate);
Now: 2012-10-18 10:04:43 +0000
Max: 1994-10-18 11:04:43 +0000
Min: 1912-10-18 11:45:11 +0000
This if you select 1980-12-12 this is lower then the max date and higher then the minimal date.
UIDatePicker *pickerView = [[UIDatePicker alloc] initWithFrame:CGRectMake(0, 0, 320, 300)];
// add this to display date only.
[pickerView setDatePickerMode:UIDatePickerModeDate];
[pickerView setMinimumDate:minDate];
[pickerView setMaximumDate:maxDate];
[pickerView setDate:minDate];
[[self view] addSubview:pickerView];
[pickerView setDatePickerMode:UIDatePickerModeDate];

How to grab the NEXT fire date from a UILocalNotification object

I have a UILocalNotification object that I have setup with repeat intervals day, week, and month. I am having no troubles at all accessing the fire date of the object:
[cell.detailTextLabel setText:[notification1.fireDate description]];
But I am having troubles getting the next fire date. If I print out the above notification1 object to the console, I get this:
<UIConcreteLocalNotification: 0x613e060>{fire date = 2010-11-29 03:53:52 GMT, time zone = America/Denver (MST) offset -25200, repeat interval = 16, next fire date = 2010-11-30 03:53:52 GMT}
This object contains somewhere the value or data I need to display the next fire date...but I can't find it! Does anybody know where I can get it programmatically?
Thanks
To calculate the next fire date for a repeating UILocalNotification, you have to:
Figure out the amount of repeatInterval there's been between the notification's original fire date (i.e. its fireDate property) and now.
Add them to the notification's fireDate.
Here's one approach:
NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
NSDateComponents *difference = [calendar components:notif.repeatInterval
fromDate:notif.fireDate
toDate:[NSDate date]
options:0];
NSDate *nextFireDate = [calendar dateByAddingComponents:difference
toDate:notif.fireDate
options:0];
This works in many scenarios, but here's a scenario where it won't work:
Suppose that:
the notification's `fireDate is 01/01 at 2:00pm
the notification's repeatInterval is NSDayCalendaryUnit (i.e. repeat daily)
The date now is 08/01 at 3:00pm
The above code will calculate the difference to 7 days (01/01 + 7 days = 08/01), add them to fireDate, and thus set nextFireDate to 08/01 at 2pm. But that's in the past, we want nextFireDate to be 09/01 at 2pm!
So if using the above code and your repeatInterval is NSDayCalendaryUnit, then add these lines:
if ([nextFireDate timeIntervalSinceDate:[NSDate date]] < 0) {
//next fire date should be tomorrow!
NSDateComponents *extraDay = [[NSDateComponents alloc] init];
extraDay.day = 1;
nextFireDate = [calendar dateByAddingComponents:extraDay toDate:nextFireDate options:0];
}
I marked this answer as community wiki, feel free to edit it if you have found a better way to do the calculation!
I don't think the next fire date is available as a property but rather calculated from fireDate and repeatInterval. Date calculating can be tricky with different time zones and other nasty things. In your example you have chosen a daily repeat and to calculate the next fire date you can do something along the lines of:
NSCalendar *calendar = localNotif.repeatCalendar;
if (!calendar) {
calendar = [NSCalendar currentCalendar];
}
NSDateComponents *components = [[[NSDateComponents alloc] init] autorelease];
components.day = 1;
NSDate *nextFireDate = [calendar dateByAddingComponents:components toDate:localnotif.fireDate options:0];
If you use some other repeat interval you would have to change the code accordingly. If you were to use NSMonthCalendarUnit you would have to use components.month = 1 instead.
i would just add the repeatInterval until the date lies in the future:
-(NSDate*)nextFireDateForNotification:(UILocalNotification*)notification {
NSCalendar *calendar = notification.repeatCalendar;
if (!calendar) {
calendar = [NSCalendar currentCalendar];
}
NSDate* date = [notification.fireDate copy];
while (date.timeIntervalSinceNow > 0) {
date = [calendar dateByAddingUnit:notification.repeatInterval value:1 toDate:date options:0];
}
return date;
}
This is in Swift 4 and using calendar's nextDate func.
extension UILocalNotification {
var nextFireDate: Date? {
guard let fireDate = fireDate else { return nil }
let today = Date()
let cal = Calendar.current
if fireDate.compare(today) == .orderedDescending {
return fireDate
}
let s: Set<Calendar.Component>
switch repeatInterval {
case .year: s = [.month, .day, .hour, .minute, .second]
case .month: s = [.day, .hour, .minute, .second]
case .day: s = [.hour, .minute, .second]
case .hour: s = [.minute, .second]
case .minute: s = [.second]
default: return nil // Not supporting other intervals
}
let components = cal.dateComponents(s, from: fireDate)
return cal.nextDate(after: today, matching: components, matchingPolicy: .nextTimePreservingSmallerComponents)
}
}

How to avoid user selecting some date in UIDatePicker?

I have to prompt users for choosing a date from a CoCoa UIDatePicker but avoiding him to select sundays ans saturdays, since my goal is to make them select a date for an appointment
The best way should be disabling that dates, in the same way of minimumDate property, but I was not able to find how to do that
You could do something like this:
UIDatePicker *datePicker = [[UIDatePicker alloc] init];
[datePicker addTarget:self action:#selector(dateChanged:) forControlEvent:UIControlEventValueChanged];
Implementation for dateChanged:
- (void)dateChanged:(id)sender {
UIDatePicker *datePicker = (UIDatePicker *)sender;
NSDate *pickedDate = datePicker.date;
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *weekdayComponents = [gregorian components:NSWeekdayCalendarUnit fromDate:pickedDate];
NSInteger weekday = [weekdayComponents weekday];
[gregorian release];
if (weekday == 1 || weekday == 7) { // Sunday or Saturday
NSDate *nextMonday = nil;
if (weekday == 1)
nextMonday = [pickedDate dateByAddingTimeInterval:24 * 60 * 60]; // Add 24 hours
else
nextMonday = [pickedDate dateByAddingTimeInterval:2 * 24 * 60 * 60]; // Add two days
[datePicker setDate:nextMonday animated:YES];
return;
}
// Do something else if the picked date was NOT on Saturday or Sunday.
}
This way, when a date is picked that is either on a Saturday or Sunday, the date picker automatically selects the Monday after the weekend.
(Code untested!)

Comparing two NSDates and ignoring the time component

What is the most efficient/recommended way of comparing two NSDates? I would like to be able to see if both dates are on the same day, irrespective of the time and have started writing some code that uses the timeIntervalSinceDate: method within the NSDate class and gets the integer of this value divided by the number of seconds in a day. This seems long winded and I feel like I am missing something obvious.
The code I am trying to fix is:
if (!([key compare:todaysDate] == NSOrderedDescending))
{
todaysDateSection = [eventSectionsArray count] - 1;
}
where key and todaysDate are NSDate objects and todaysDate is creating using:
NSDate *todaysDate = [[NSDate alloc] init];
Regards
Dave
I'm surprised that no other answers have this option for getting the "beginning of day" date for the objects:
[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitDay startDate:&date1 interval:NULL forDate:date1];
[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitDay startDate:&date2 interval:NULL forDate:date2];
Which sets date1 and date2 to the beginning of their respective days. If they are equal, they are on the same day.
Or this option:
NSUInteger day1 = [[NSCalendar currentCalendar] ordinalityOfUnit:NSDayCalendarUnit inUnit: forDate:date1];
NSUInteger day2 = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitEra forDate:date2];
Which sets day1 and day2 to somewhat arbitrary values that can be compared. If they are equal, they are on the same day.
You set the time in the date to 00:00:00 before doing the comparison:
unsigned int flags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDateComponents* components = [calendar components:flags fromDate:date];
NSDate* dateOnly = [calendar dateFromComponents:components];
// ... necessary cleanup
Then you can compare the date values. See the overview in reference documentation.
There's a new method that was introduced to NSCalendar with iOS 8 that makes this much easier.
- (NSComparisonResult)compareDate:(NSDate *)date1 toDate:(NSDate *)date2 toUnitGranularity:(NSCalendarUnit)unit NS_AVAILABLE(10_9, 8_0);
You set the granularity to the unit(s) that matter. This disregards all other units and limits comparison to the ones selected.
For iOS8 and later, checking if two dates occur on the same day is as simple as:
[[NSCalendar currentCalendar] isDate:date1 inSameDayAsDate:date2]
See documentation
This is a shorthand of all the answers:
NSInteger interval = [[[NSCalendar currentCalendar] components: NSDayCalendarUnit
fromDate: date1
toDate: date2
options: 0] day];
if(interval<0){
//date1<date2
}else if (interval>0){
//date2<date1
}else{
//date1=date2
}
I used the Duncan C approach, I have fixed some mistakes he made
-(NSInteger) daysBetweenDate:(NSDate *)firstDate andDate:(NSDate *)secondDate {
NSCalendar *currentCalendar = [NSCalendar currentCalendar];
NSDateComponents *components = [currentCalendar components: NSDayCalendarUnit fromDate: firstDate toDate: secondDate options: 0];
NSInteger days = [components day];
return days;
}
I use this little util method:
-(NSDate*)normalizedDateWithDate:(NSDate*)date
{
NSDateComponents* components = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit)
fromDate: date];
return [calendar_ dateFromComponents:components]; // NB calendar_ must be initialized
}
(You obviously need to have an ivar called calendar_ containing an NSCalendar.)
Using this, it is easy to check if a date is today like this:
[[self normalizeDate:aDate] isEqualToDate:[self normalizeDate:[NSDate date]]];
([NSDate date] returns the current date and time.)
This is of course very similar to what Gregory suggests. The drawback of this approach is that it tends to create lots of temporary NSDate objects. If you're going to process a lot of dates, I would recommend using some other method, such as comparing the components directly, or working with NSDateComponents objects instead of NSDates.
The answer is simpler than everybody makes it out to be. NSCalendar has a method
components:fromDate:toDate:options
That method lets you calculate the difference between two dates using whatever units you want.
So write a method like this:
-(NSInteger) daysBetweenDate: (NSDate *firstDate) andDate: (NSDate *secondDate)
{
NSCalendar *currentCalendar = [NSCalendar currentCalendar];
NSDateComponents components* = [currentCalendar components: NSDayCalendarUnit
fromDate: firstDate
toDate: secondDate
options: 0];
NSInteger days = [components days];
return days;
}
If the above method returns zero, the two dates are on the same day.
From iOS 8.0 onwards, you can use:
NSCalendar *calendar = [NSCalendar currentCalendar];
NSComparisonResult dateComparison = [calendar compareDate:[NSDate date] toDate:otherNSDate toUnitGranularity:NSCalendarUnitDay];
If the result is e.g. NSOrderedDescending, otherDate is before [NSDate date] in terms of days.
I do not see this method in the NSCalendar documentation but it is in the iOS 7.1 to iOS 8.0 API Differences
For developers coding in Swift 3
if(NSCalendar.current.isDate(selectedDate, inSameDayAs: NSDate() as Date)){
// Do something
}
With Swift 3, according to your needs, you can choose one of the two following patterns in order to solve your problem.
#1. Using compare(_:to:toGranularity:) method
Calendar has a method called compare(_:​to:​to​Granularity:​). compare(_:​to:​to​Granularity:​) has the following declaration:
func compare(_ date1: Date, to date2: Date, toGranularity component: Calendar.Component) -> ComparisonResult
Compares the given dates down to the given component, reporting them ordered​Same if they are the same in the given component and all larger components, otherwise either ordered​Ascending or ordered​Descending.
The Playground code below shows hot to use it:
import Foundation
let calendar = Calendar.current
let date1 = Date() // "Mar 31, 2017, 2:01 PM"
let date2 = calendar.date(byAdding: .day, value: -1, to: date1)! // "Mar 30, 2017, 2:01 PM"
let date3 = calendar.date(byAdding: .hour, value: 1, to: date1)! // "Mar 31, 2017, 3:01 PM"
/* Compare date1 and date2 */
do {
let comparisonResult = calendar.compare(date1, to: date2, toGranularity: .day)
switch comparisonResult {
case ComparisonResult.orderedSame:
print("Same day")
default:
print("Not the same day")
}
// Prints: "Not the same day"
}
/* Compare date1 and date3 */
do {
let comparisonResult = calendar.compare(date1, to: date3, toGranularity: .day)
if case ComparisonResult.orderedSame = comparisonResult {
print("Same day")
} else {
print("Not the same day")
}
// Prints: "Same day"
}
#2. Using dateComponents(_:from:to:)
Calendar has a method called dateComponents(_:from:to:). dateComponents(_:from:to:) has the following declaration:
func dateComponents(_ components: Set<Calendar.Component>, from start: Date, to end: Date) -> DateComponents
Returns the difference between two dates.
The Playground code below shows hot to use it:
import Foundation
let calendar = Calendar.current
let date1 = Date() // "Mar 31, 2017, 2:01 PM"
let date2 = calendar.date(byAdding: .day, value: -1, to: date1)! // "Mar 30, 2017, 2:01 PM"
let date3 = calendar.date(byAdding: .hour, value: 1, to: date1)! // "Mar 31, 2017, 3:01 PM"
/* Compare date1 and date2 */
do {
let dateComponents = calendar.dateComponents([.day], from: date1, to: date2)
switch dateComponents.day {
case let value? where value < 0:
print("date2 is before date1")
case let value? where value > 0:
print("date2 is after date1")
case let value? where value == 0:
print("date2 equals date1")
default:
print("Could not compare dates")
}
// Prints: date2 is before date1
}
/* Compare date1 and date3 */
do {
let dateComponents = calendar.dateComponents([.day], from: date1, to: date3)
switch dateComponents.day {
case let value? where value < 0:
print("date2 is before date1")
case let value? where value > 0:
print("date2 is after date1")
case let value? where value == 0:
print("date2 equals date1")
default:
print("Could not compare dates")
}
// Prints: date2 equals date1
}
int interval = (int)[firstTime timeIntervalSinceDate:secondTime]/(60*60*24);
if (interval!=0){
//not the same day;
}
my solution was two conversions with NSDateFormatter:
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyyMMdd"];
[dateFormat setTimeZone:[NSTimeZone timeZoneWithName:#"GMT"]];
NSDate *today = [NSDate dateWithTimeIntervalSinceNow:0];
NSString *todayString=[dateFormat stringFromDate:today];
NSDate *todayWithoutHour=[dateFormat dateFromString:todayString];
if ([today compare:exprDate] == NSOrderedDescending)
{
//do
}
The documentation regarding NSDate indicates that the compare: and isEqual: methods will both perform a basic comparison and order the results, albeit they still factor in time.
Probably the simplest way to manage the task would be to create a new isToday method to the effect of the following:
- (bool)isToday:(NSDate *)otherDate
{
currentTime = [however current time is retrieved]; // Pardon the bit of pseudo-code
if (currentTime < [otherDate timeIntervalSinceNow])
{
return YES;
}
else
{
return NO;
}
}
This is a particularly ugly cat to skin, but here's another way to do it. I don't say it's elegant, but it's probably as close as you can get with the date/time support in iOS.
bool isToday = [[NSDateFormatter localizedStringFromDate:date dateStyle:NSDateFormatterFullStyle timeStyle:NSDateFormatterNoStyle] isEqualToString:[NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterFullStyle timeStyle:NSDateFormatterNoStyle]];
NSUInteger unit = NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit;
NSDateComponents *comp = [cal components:unit
fromDate:nowDate
toDate:setDate
options:0];
NSString *dMonth;
dMonth = [NSString stringWithFormat:#"%02ld",comp.month];
NSString *dDay;
dDay = [NSString stringWithFormat:#"%02ld",comp.day + (comp.hour > 0 ? 1 : 0)];
compare hour as well to fix 1day difference