4

I have a bunch of hours of operation I am I want to determine whether the store is open, closed or is closing in 30,29,28,27... minutes I am doing this in Xcode/ Objectic-C. Now I have to do this for lets say 50 different hours of operation. I have made a function that does this but it is not very efficient and involves a lot of if-else statements. Here is a sample hours of operation

Monday - Thursday
7:30am - Midnight
Friday
7:30am - 10:00pm
Saturday
9:00am - 10:00pm 
Sunday
9:00am - Midnight 

And here is my function and how I handle it

-(BOOL) dateAndTime:(NSDate*)date getStartDay:(NSInteger)startDay getStartHour:(NSInteger)startHour getStartMin:(NSInteger)startMin getEndDay:(NSInteger)endDay getEndHour:(NSInteger)endHour getEndMin:(NSInteger)endMin{

    NSCalendar *calendar = [NSCalendar currentCalendar];


    const NSCalendarUnit units = NSWeekdayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit;
    NSDateComponents *comps = [calendar components:units fromDate:date];

    if (comps.weekday == 1) {
        comps.weekday = 7;
    }
    else comps.weekday = comps.weekday - 2;

    NSDate *startOfToday;
    [[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit startDate:&startOfToday interval:NULL forDate:date];

    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    NSTimeZone *timeZone = [NSTimeZone localTimeZone];
    [dateFormatter setDateFormat:@"HH:mm"];
    [dateFormatter setTimeZone:timeZone];


    NSString *dateString = [dateFormatter stringFromDate:date];

    NSDate *startDate = [dateFormatter dateFromString:[NSString stringWithFormat:@"%ld:%ld", (long)startHour, (long)startMin]];
    NSDate *endDate = [dateFormatter dateFromString:[NSString stringWithFormat:@"%ld:%ld", (long)endHour, (long)endMin]];

    NSString *startDateString = [dateFormatter stringFromDate:startDate];
    NSString *endDateString = [dateFormatter stringFromDate:endDate];


    if ([startDateString compare:dateString] == NSOrderedAscending && [dateString compare:endDateString] == NSOrderedAscending && startDay <= comps.weekday && comps.weekday <= endDay) {
        return YES;
    }
    else return NO;

}

Now I pass in the day, from 0-6 (0 being monday) and then the time in 24-hour time. And then use it like this:

    if ([self dateAndTime:date getStartDay:0 getStartHour:7 getStartMin:30 getEndDay:3 getEndHour:23 getEndMin:30] == YES)
        text = @"Open";

    else if ([self dateAndTime:date getStartDay:0 getStartHour:23 getStartMin:30 getEndDay:3 getEndHour:24 getEndMin:0] == YES)
        text = [NSString stringWithFormat:@"Closes in %@ min", countdownNumber];
else text = @"Closed";

As you can see to do this for all the days of the week and hours on each day it requires a lot of if statements and is very bad. Just for this one example it requires 8 if-else statements (VERY TIME CONSUMING)

Now the basis of this question is how can I make this much more efficient/ what is a better way to do this, while sill being able to have the countdown for the last 30 minutes?

I have done some research and can't find anything that has a countdown to when something is closing and is efficient.

Here is the full if-else statements for the example if you need it or want to see https://gist.github.com/spennyf/b0b18e31c3e9deaa0455

Thanks for the help and/or advice in advance :)

EDIT

This is at the top of my .m file I have this

@interface HomeTableViewController () {

    ShopHours WeekSchedule[];
}

@end

But this gives me the compile error I talked about in the comments, how can I set this variable so it can be used throughout this one .m file? Right now I am just passing it is as an extra parapet for the function which should be fine. :)

And what would be the best way to set up the if statement for if the place is closing in 30 minutes once I have determined that it is open? And could you add the set up for if a place is open for parts of a day/ over midnight.

Thanks for all your help :)

3 Answers3

7

Try tackling the problem from a different direction. Take the following as a starting point, not everything is explained - if you say don't know what a dictionary is then you should research it.

Looking At The Problem

You have a table of opening and closing times, checking whether your shop is open should be a lookup into this table - just as you would in "real life". To know if the shop is open you need to know the weekday - which tells you which line of your table to consult, and the time - which you compare against the two times in that line of the table.

To represent a table in a program you typically use an array, to represent two associated times - such as the open & close times - you might use a record, object or dictionary etc.

How can you represent a time of day? Well general time & date calculations are complicated, but after the weekday all you need to know is the time in that day, and assuming you are not worried about leap seconds (you're not) you can assume there are 24 hours of 60 minutes each in a day so you can store the time as the number of minutes since midnight - giving you a single number. If you use the number of minutes since midnight to determine whether the shop is open or closed you can avoid complicated date comparisons.

Some Code

As your code already already shows you can use NSCalendar to obtain the weekeday, hours and minutes of any NSDate.

How to get the hours and minutes to minutes since midnight, well that is simple arithmetic but you'll want to do it a few times so maybe a simple macro to convert a time in hours and minutes:

#define TO_MINUTES(hour, min) (hour * 60 + min)

How to represent the table of opening times?

Well you could use an NSArray, indexed by the weekday, where each element is an NSDictionary containing two key/value pairs - for the open and closing times. However your times are just integers, the number of minutes since midnight, and storing integers in an NSDictionary requires wrapping them as a NSNumber objects. (If that doesn't make sense, time to do some research!)

Another approach would be to use a C-style array and structure for your table - this will work quite well as you are only storing integers.

Here is a C structure definition to represent the opening and closing times:

typedef struct
{
   NSInteger openTime;
   NSInteger closeTime;
} ShopHours;

and with that and the above macro you can easily define a constant array representing the shop hours:

ShopHours WeekSchedule[] =
{
   {0, 0}, // index 0 - ignore
   { TO_MINUTES(9, 0),  TO_MINUTES(24, 0) }, // index 1 - Sunday
   { TO_MINUTES(7, 30), TO_MINUTES(24, 0) }, // index 2 - Monday
   ...
   { TO_MINUTES(9, 0),  TO_MINUTES(22, 0) }, // index 7 - Saturday
};

In real code you might read this in from a data file, but the above global array will do for now.

(Note that index 0 is ignored - NSDateComponents number the days starting from 1 for Sunday and arrays (both C-style and NSArray) are indexed from 0, simply ignoring the zeroth element avoids do - 1's in your code.)

You already have code to break an NSDate into NSDateComponents, using that you can get the weekday and minutes since midnight easily:

NSInteger weekday = comps.weekday;
NSInteger minutes = TO_MINUTES(comps.hour, comps.minute);

use weekday to index the WeekSchedule table, and compare minutes to the two entries and you are done, e.g. is the shop open:

if (minutes >= WeekSchedule[weekday].openTime && minutes <= WeekSchedule[weekday].closeTime)
{
    // shop is open...
}
else
{
    // shop is closed...
}

You can wrap the above into a method which given a date tells you the state of the shop:

- (NSString *) shopState:(NSDate *)dateAndTime
{
   // break out the weekday, hours and minutes...
   // your code from the question

   NSInteger weekday = comps.weekday;
   NSInteger minutes = TO_MINUTES(comps.hour, comps.minute);

   if (minutes >= WeekSchedule[weekday].openTime && minutes <= WeekSchedule[weekday].closeTime)
   {
      // shop is open...
      // determine if its closing within 30 mins and return an appropriate string
   }
   else
   {
      // shop is closed...
      return @"Closed";
   }
}

As you'll see this solution is a lot shorter than the approach you took.

HTH

Addendum - Refinements

As Rob Napier has pointed out in comments and in case its not obvious, the above outline of a solution is just that and omits cases such as shops being open over midnight. Here are some things you might want to consider:

  1. Shops open for more than one period per day: Some shops close over lunch, restaurants may open for lunch and evenings, etc. To handle this you need a list of open/close times per day rather than just a single pair. Once you have determined the weekday testing is a matter of iterating over such a list.

  2. Shops open across midnight: This is just a special case of (1), think about it...

  3. Time zones: In your code in the question and the code in this answer it is assumed that the open/close times and the time being tested are all from the same time zone. If you wish to support, say, a person in Canada determining whether a shop in Germany is currently open and can be phoned you need to allow for the time difference.

  4. Daylight Saving Time: This is the corner case Rob mentions in the comments. When a DST change occurs an hour might be skipped or repeat. This is only an issue if you support shops which open/close in that hour - shops which are open right across the period of change need no special handling. NSCalendar will give the correct hour/min from the time you are testing, you need to handle any adjustments to open/close times. For example consider a shop which closes at 2am, a DST change jumps 2am back to 1am, is the shop open at 1:30am? Yes the first time it comes around, but what about the second? Deciding this is an issue beyond time calculations.

You need to decide whether and how to address these.

Some More Hints

OK, so its Christmas (take that how you like - over eating slowing brains, time for gifts, etc. ;-))

I see you've asked in another question how to create the table dynamically rather than using a static one, so you have that covered.

Let's consider the multiple opening times day & open over midnight:

  1. Arrays of arrays would work, but you could instead just keep an array of opening times for a whole week. E.g. change the TO_MINUTES macro to take the day number as well and store all the times as the number of minutes since Sunday 0000hrs. Now instead of indexing the array to find the day you iterate or search it - the array is ordered so you could binary search if you wish, but a simple iteration is probably fast enough (how many open/close periods are there in a week?)

  2. By setting the closing time to the next day (1) covers opening over midnight for everything but Sat -> Sun, including closing within 30 min calculations.

  3. To handle Sat -> Sun first split the period into Sat night and Sun morning parts. Add them to your array, they will be the first (early Sunday morning) and last (late Sat night) entries. Now when you go to determine the minutes till closing check if the closing time is Sat midnight (e.g. TO_MINUTES(7, 24, 0)), if so check if the first entry's opening time is Sunday 0000hrs, if so you want to adjust the closing time to do the 30 min check (add in the length of the first period).

That will handle multiple periods and open over midnight. It doesn't handle DST, shop holidays, etc. - you need to decided how much to handle. For DST use NSTimeZone to find out when and by how much the times changes (its not always by 1 hour) to figure out the "repeated" and "missing" times - but remember this is only an issue of your shop actually opens/closes during those times.

It's New Year ;-)

Seriously Rob decided to give almost the complete code but using Objective-C objects and a number of methods so I thought I'd add my code for comparison because it raises and interesting issue.

What should be noted first is the similarity, algorithmically the two solutions are close - the wraparound is handled differently but either approach could do it either way so that is not significant.

The difference comes to the choice of data structure - should you use C structures and arrays for something this simple or Objective-C objects? The frameworks themselves have plenty of structure types - e.g. NSRect et al - there is nothing wrong with using them in Objective-C code. The choice isn't black and white, there is a gray area where either might be suitable, and this problem probably falls in that gray area. So here's the multiple openings times/day solution:

// convenience macro
// day 1 = Sunday, ... 7 = Saturday
#define TO_MINUTES(day, hour, min) ((day * 24 + hour) * 60 + min)

#define WEEK_START TO_MINUTES(1, 0, 0)
#define WEEK_FINISH TO_MINUTES(7, 24, 0)

typedef struct
{  NSInteger openTime;
   NSInteger closeTime;
} ShopHours;

// Opening hours
ShopHours WeekSchedule[] =
{  { TO_MINUTES(1, 0, 0),  TO_MINUTES(1, 0, 15) }, // Sat night special, part of Sat 11:30pm - Sun 0:15am
   { TO_MINUTES(1, 9, 0),  TO_MINUTES(1, 24, 0) }, // Sun 9am - Midnight
   { TO_MINUTES(2, 7, 30), TO_MINUTES(2, 24, 0) }, // Mon 7:30am - Midnight
   { TO_MINUTES(3, 7, 30), TO_MINUTES(3, 24, 0) },
   { TO_MINUTES(4, 7, 30), TO_MINUTES(5, 2, 0)  }, // Midweek madness, Wed 7:30am - Thursday 2am
   { TO_MINUTES(5, 7, 30), TO_MINUTES(5, 24, 0) },
   { TO_MINUTES(6, 7, 30), TO_MINUTES(6, 22, 0) }, // Fri 7:30am - 10pm
   { TO_MINUTES(7, 9, 0),  TO_MINUTES(7, 22, 0) }, // Sat 9am - 10pm
   { TO_MINUTES(7, 23, 30),TO_MINUTES(7, 24, 0) }, // Sat night special, part of Sat 11:30pm - Sun 0:15am
};

- (NSString *) shopState:(NSDate *)dateAndTime
{  NSCalendar *calendar = [NSCalendar currentCalendar];

   const NSCalendarUnit units = NSWeekdayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit;
   NSDateComponents *comps = [calendar components:units fromDate:dateAndTime];

   NSInteger minutes = TO_MINUTES(comps.weekday, comps.hour, comps.minute);
   NSLog(@"%ld (%ld, %ld, %ld)", minutes, comps.weekday, comps.hour, comps.minute);

   unsigned periods = sizeof(WeekSchedule)/sizeof(ShopHours);
   for (unsigned ix = 0; ix < periods; ix++)
   {  if (minutes >= WeekSchedule[ix].openTime)
      {  if (minutes < WeekSchedule[ix].closeTime)
         {
            // shop is open, how long till close time?
            NSInteger closeTime = WeekSchedule[ix].closeTime;
            // handle Sat -> Sun wraparound
            if (closeTime == WEEK_FINISH && WeekSchedule[0].openTime == WEEK_START)
               closeTime += WeekSchedule[0].closeTime - WEEK_START;
            NSInteger closingIn = closeTime - minutes;

            if (closingIn <= 30)
               return [NSString stringWithFormat:@"Closes in %ld min", closingIn];
            else
               return @"Open";
         }
      }
      else // minutes < WeekSchedule[ix].openTime
         break;
   }
   return @"Closed";
}

The DST Issue

I first though this was a non-issue, Rob raised it in comments, and now he thinks its a non-issue, but it isn't - though its somewhat academic maybe.

It is a non-issue if you don't use NSDate to represent the time being queried, and Rob's solution takes that route.

The original question and the code above does use NSDate and breaks out the weekday, hour and minute from it as NSDateComponents. Consider the situation where 2am becomes 1am due to DST change and the shop usually closes at 1:30am. If you start with an NSDate value before 1am and increment, say by 10min each time, until you get a hour value from components:fromDate: greater than 2 you'll see values like: 00:50, 01:00, 01:10, ..., 01:50, 01:00, 01:10, ..., 01:50, 02:00, 02:10. Testing each of these times will report the shop as closed for 30 mins after the first 01:30 is passed, then it will re-open for 30 mins until the next one is passed!

This issue only occurs if you start with an NSDate, if you simple take a weekday/hour/min as your input then it does not occur. Either approach (struct or object) can operate either way, you just have to decide whether it is an issue you wish to address.

CRD
  • 52,522
  • 5
  • 70
  • 86
  • "you can assume there are 24 hours of 60 minutes each in a day." This isn't quite right. There are 23, 24, or 25 hours in a day, depending on DST transitions. I think all your math works out because you're using `NSDateComponents` (though be careful if the local time zone is different than the time zone you're calculating for). It's a good answer; readers just need to be very careful not to inadvertently exchange nominal and absolute times, or assume there are 24 hours in a day. And make sure to include tests around DST transitions. – Rob Napier Dec 26 '14 at 23:17
  • The particular test case you always want is a store that opens at 7p and closes at 4a, across a DST change. In this particular case, most stores don't open at 2:30a, but that's a particularly challenging case, since it may never actually happen, or happen twice in a given day (yet, real stores of course will open and close even across DST :D). – Rob Napier Dec 26 '14 at 23:20
  • 1
    @RobNapier - Yes I've simplified and not handled the corner cases, the idea is to provide a good starting point, anything more is left as an exercise, we're not here to do all the work! ;-) Added an addendum to make that clear. – CRD Dec 27 '14 at 08:51
  • Hey CRD this all looks good just trying to implement it, I have run into one problem I am trying to make WeekSchedule global so it can be used in the method and have declared it like this `ShopHours WeekSchedule[];` in the @interface and get compile error `field has incompatible type ShopHours[]` –  Dec 27 '14 at 21:27
  • Also for now just to test it out I put my WeekSchedule in the method and then I call it in `viewWillAppear` and for some reason it is being called twice? –  Dec 27 '14 at 21:37
  • @daveneedleman - global variables are declared outside of the instance variable `{ ... }` section of `@implementation` (and `@interface`). If you need them to be visible in another file, and consider carefully whether you do, they should be declared as `extern` in the header file. See a C language reference on globals. – CRD Dec 27 '14 at 22:51
  • I only need to use it in my one .m file, and I set the value of it in `viewWillAppear` for the method to use it in the if statement where can I initialize the variable? See edit for what I tried... –  Dec 27 '14 at 22:56
  • @daveneedleman - You've declared an instance variable, just put the declaration as shown in the answer outside if the `{ ... }`'s in your file - at the same level as you would declare a method or a function. – CRD Dec 28 '14 at 00:27
  • okay and then the last thing is could you provide some support for setting up `ShopHors` / `WeekSchedule` for if the store is open for lunch and dinner but not in-between –  Dec 28 '14 at 05:07
  • @daveneedleman - well that is another data structure design question, there is more than one answer, and you really should try your own design. You might find an array of array of structure suits you. If you stick with C you probably want an array of pointers as covered in the answer to [this question](http://stackoverflow.com/questions/5495983/c-compound-literals-pointer-to-arrays). Or you might switch to `NSArray` and use nested [array literals](http://clang.llvm.org/docs/ObjectiveCLiterals.html), if so you'll need to decide how to replace the `struct` - `NSDictionary` or custom object. – CRD Dec 28 '14 at 06:42
  • The "30 minutes till closing" requirement unfortunately will break your idea of treating "crossing midnight" as two separate time regions. That will cause the app to claim that a shop open until 2a is "closing in 30m" at 11:30p. This really is a rather interesting question… – Rob Napier Dec 28 '14 at 14:32
  • @RobNapier yes it is quite difficult, do u have any suggestions for the closing in 30min part? or have multiple time open in one day part? And thanks CRD Ill take a look at those links you suggested –  Dec 28 '14 at 16:29
  • I've been mulling about it; I'll try to write up an answer of my own in the next couple of days. @CRD hits many useful cases, but I think it's possible to do even better. Of course that's quite easy for me to say before having written any code... :) – Rob Napier Dec 28 '14 at 18:25
  • "Is the shop open? Yes, is the time after 2330? Yes, special case, check next day opening time..." That will insert one extra integer check (> 2330) in the main path. Whatever basic unit you choose (day, week, year) there will always be a boundary at the point it repeats, you just have to test. You could also check your table once at the start to see if you'll ever need to test (midnight, DST) and make choices at that point as well. You're following a standard, and important, pattern: analyse, consider the boundary cases, design an algorithm, check, maybe repeat... You'll get there! – CRD Dec 28 '14 at 18:26
  • @RobNapier - I've been purposely not giving a more complete answer ;-) Don't let that stop you of course, but really I don't think there are a lot of boundaries and figuring it out oneself is good. A week might be a good unit... – CRD Dec 28 '14 at 18:33
  • @RobNapier whenever you get some time that would be great if you could make a answer post. I keep trying to build off of CRD's answer and post any progress I have made as an edit to the question. Thanks :) –  Dec 28 '14 at 20:48
  • @daveneedleman - OK, so its Christmas... Added a few more hints. – CRD Dec 28 '14 at 21:25
3

Storing your times as C structures creates a number of memory management headaches. I'd avoid that if you can (and I think you can). Instead, I recommend creating some new classes to help us out here.

First, we should think about these times as "nominal times within a week." By making these nominal times, we can explicitly get rid of DST concerns by saying that "1:30am on Sunday" means "the first instant that we would call 1:30am, no matter what DST transitions might happen to create another 1:30a." That's how shops usually work anyway, but it's important to be precise when thinking about time functions.

The great thing about "nominal times within a week" is that we can start counting minutes from Sunday at midnight, and we know that there will be exactly 60*24*7 (10,080) minutes in the week. We only really care here about minutes, so we just need to keep track of a number between 0 and 10,079. But we must remember that these numbers are subject to modular arithmetic (they "wrap around"). It is meaningless to ask whether Tuesday is before or after Wednesday. Tuesday is before and after Wednesday. But it's meaningful to ask whether Tuesday is between Monday (as a starting point) and Wednesday (as an ending point). We can determine if moving forward from Tuesday we will encounter Wednesday before we encounter Monday. If that's true, it's between. That's how you have to think about modular time.

OK, way too much theory. Let's look at some code (all the code is here). First we'd like a WeekTime to represent some time within a week:

typedef enum {
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
} Weekday;

@interface WeekTime : NSObject   
@property (nonatomic, readonly) NSInteger minutesSinceStartOfWeek;

- (instancetype)initWithWeekday:(Weekday)weekday hour:(NSInteger)hour minute:(NSInteger)minute;
@end

The impl of this should be obvious so I won't paste it here.

Now we want to think about ranges of nominal week times. We'll make these open ranges, so they include their start time but not their end time. If a place closes at 9p, you want to return "Closed" at 9p, not "Closing in 1m."

@interface WeekTimeRange : NSObject
@property (nonatomic, readonly) WeekTime *start;
@property (nonatomic, readonly) WeekTime *end;

- (instancetype)initWithStart:(WeekTime *)start end:(WeekTime *)end;
- (BOOL)contains:(WeekTime *)time;
@end

Most of this should be obvious, but contains: has one little trick to it. Since we're in modular arithmetic, it's legal for start to be "greater" than end (remembering that "greater" doesn't mean anything). That's easy to deal with:

- (BOOL)contains:(WeekTime *)time {
    NSInteger queryMinutes = time.minutesSinceStartOfWeek;
    NSInteger startMinutes = self.start.minutesSinceStartOfWeek;
    NSInteger endMinutes = self.end.minutesSinceStartOfWeek;

    if (startMinutes < endMinutes) {
        return (startMinutes <= queryMinutes &&
                queryMinutes < endMinutes);
    } else {
        return (queryMinutes < endMinutes ||
                queryMinutes >= startMinutes);
    }
}

(In my example I've chosen to make the range where start==end be undefined. If you want to assign some meaning to that, either meaning "all times" or "no times," then you may need to tweak this. Be careful of "all times," though. That really would mean the shop closes and immediately reopens, so you'll get "closing in..." messages. That's why I just made start==end be undefined.)

OK, finally we'll want a few helpers:

WeekTimeRange *WTMakeRange(Weekday startDay, NSInteger startHour, NSInteger startMinute,
                           Weekday endDay, NSInteger endHour, NSInteger endMinute) {
    return [[WeekTimeRange alloc]
            initWithStart:[[WeekTime alloc] initWithWeekday:startDay hour:startHour minute:startMinute]
            end:[[WeekTime alloc] initWithWeekday:endDay hour:endHour minute:endMinute]];
}

WeekTimeRange *WTFindOpenRangeIncluding(NSArray *ranges, WeekTime *time) {
    for (WeekTimeRange *range in ranges) {
        if ([range contains:time]) {
            return range;
        }
    }
    return nil;
}

NSString *WTStatus(WeekTimeRange *range, WeekTime *requestedTime) {
    if (range != nil) {
        NSInteger minutesLeft = range.end.minutesSinceStartOfWeek - requestedTime.minutesSinceStartOfWeek;
        if (minutesLeft < 0) {
            minutesLeft += [WeekTime maxMinute];
        }
        if (minutesLeft <= 30) {
            return [NSString stringWithFormat:@"Closing in %ld minutes", (long)minutesLeft];
        } else {
            return @"Open";
        }
    } else {
        return @"Closed";
    }
}

These shouldn't require much explanation. Let's see it in practice. You already seem to know how to turn dates into their components, so I'm going to just create a WeekTime directly and not worry about converting it from an NSDate.

    NSArray *shopHours = @[WTMakeRange(Monday, 7, 30, Tuesday, 0, 0),
                           WTMakeRange(Tuesday, 7, 30, Wednesday, 0, 0),
                           WTMakeRange(Wednesday, 7, 30, Thursday, 0, 0),
                           WTMakeRange(Thursday, 7, 30, Friday, 0, 0),
                           WTMakeRange(Friday, 7, 30, Friday, 22, 0),
                           WTMakeRange(Saturday, 9, 0, Saturday, 22, 0),
                           WTMakeRange(Sunday, 9, 0, Monday, 2, 0)
                           ];

    WeekTime *mondayElevenPM = [[WeekTime alloc] initWithWeekday:Monday hour:23 minute:00];
    WeekTimeRange *openRange = WTFindOpenRangeIncluding(shopHours, mondayElevenPM);
    NSString *result = WTStatus(openRange, mondayElevenPM);
    NSLog(@"%@", result);

I haven't tried tons of test cases, but my ad hoc tests seem to work. All the code is in the gist. One case I don't test for is whether your ranges overlap. The above algorithm doesn't care if the ranges are in order, but you may get incorrect "closing in..." messages if the ranges overlap. Resolving that (either by throwing an error, or by merging ranges) should be an pretty easy enhancement.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Hey Rob, I saw this and was interested and was thinking what if like int he above example monday - thursday have the same hours, would there be and easy way to edit this so you would have to rewrite that each time? Do you know what I mean? – iqueqiorio Dec 31 '14 at 00:15
  • You wouldn't edit this. You'd just make an additional function that transforms whatever data structure you like into a list of ranges. By making each function small, you can keep the pieces simple (such as how `WTStatus` deals with the "30 minutes till closing" problem, separate from the main algorithm). If you like, you could go so far as building a very simple compiler that took the example text input and parsed it into a data structure and then converted that to ranges. But each piece of that should be its own independent function. – Rob Napier Dec 31 '14 at 00:44
  • @RobNapier - I originally thought you could ignore DST as well, but then you raised it in comments and it made me think at bit more and I'm not so sure if you start with an `NSDate` as the OP does. Consider 2am -> 1am jump and closing at 0130, you get open/closed/open due to the `NSDate` -> hour/min translation. But that's probably pretty academic ;-) – CRD Jan 01 '15 at 17:49
  • hey @RobNapier just ran into a case where the store hours overlap midnight on sunday, the hours are Monday-Sunday: 10:00-1am the next day, and am running into an error where I am getting "closing in -time" could you guide me in the right direction for fixing this? –  Jan 13 '15 at 23:25
  • the problem occurs with `WTMakeRange(Saturday, 10, 0, Sunday, 1, 0)`, it looses track of the start time or something I think –  Jan 13 '15 at 23:30
  • sorry for kinda spamming comments, but I been working at this now for a few hours and I am wondering why in the function `contains` why does the else statement mean that it is contained in the range? wouldn't it mean it is not contained? –  Jan 14 '15 at 03:50
  • Bug in the logic. Needed to be "return (queryMinutes < endMinutes || queryMinutes >= startMinutes)" in the inversion case. The reason for this code is in cases where start time is later than end time because of wrap around. Then the range is the union of times greater than start and less than end rather than the intersection. – Rob Napier Jan 14 '15 at 17:27
  • @RobNapier thanks for the reply I added in one thing to WTStatus for when the time goes from saturday to sunday, I was wondering if you could give it a quick look its 5 lines, so that the transition from sat-sun will work, here is the gist: https://gist.github.com/anonymous/8e1dbf90c7dd07d885c0 , Thanks :) –  Jan 14 '15 at 20:27
  • I wouldn't do it that way. I would add a +maxMinute value, and add it when appropriate. if (range.start.minutesSinceStartOfWeek > range.end.minutesSinceStartOfWeek) { minutesLeft += [WeekTime maxMinute]; } – Rob Napier Jan 14 '15 at 20:45
  • okay, but I think I found a problem, if it is sunday morning at 00:35, and the place closes at 01:00 sunday morning, then it still adds a extra week of time on, which makes it be open instead of closing, you only want the week of time to be added when it is still saturday? Right @RobNapier. –  Jan 14 '15 at 21:00
  • 1
    Yes; you're right. It should be when it's negative. – Rob Napier Jan 14 '15 at 21:03
1

Instead of having the method dateAndTime with all of those arguments create a custom date object and set the properties you create on that object with values like so:

@interface MyDateModelObject : NSObject
@property (nonatomic, strong) NSDate *startDay;
@property (nonatomic, assign) NSInteger startHour;
And so on...

Turning the dateAndTime method to:

- (BOOL)dateAndTime:(MyDateModelObject *)myDateModelObject

Now getting into dateAndTime's code. This method is doing too much. To follow the Single Responsibility Principle I would remove code like the NSDateFormatter and the date comparison code into their own methods into a utility methods.

With that said I would go further and rename dateAndTime to the following and add your gist code inside it:

- (NSString *)retrieveStoreState:(MyDateModelObject *)date

Now in your code you can create a model object and set it's properties, pass it to retrieveStoreState and make it return just the state based on the logic you have in your gist.

Hopefully this gives you a good direction to go in. If you follow the SRP you should be able to clean your code and spruce your gist up a bit making it more readable.

Noah Witherspoon
  • 57,021
  • 16
  • 130
  • 131
Eric Anderson
  • 351
  • 3
  • 8