17

I'm currently writing a fairly simple app handling opening/closing times of businesses and running into serious difficulties trying to figure out how to properly store the info.

Most of our critical functionality is heavily dependent on getting times absolutely perfect, so obviously I want to get things done the best way possible to start off with!

Additionally, the data will be inputted by users, so if the underlying representation is slightly more complex (e.g. using TimeSpans to account for opening past midnight), this needs to be invisible to the user.

I need to store firstly, the business's opening hours, by day of week, with a timezone associated with them, e.g:

- M:  1000 - 2330
- T:  1000 - 0030
- W:  1900 - 0300
- Th: 2000 - 0300
- F:  2000 - 0800
- Sa: 1000 - 0500
- Su: 1000 - 2300

I'm currently thinking that the best way to store this is using a class like this:

public class OpeningHours
{
    ZonedDateTime OpeningTime { get; set; }
    Period durationOpen { get; set; }

    // TODO: add a method to calculate ClosingTime as a ZonedDateTime
}

However, there's 2 main complications here:

  • I don't want to store the Year, Month, or Date part of the ZonedDateTime - I just care about the DayOfWeek.

    Sure, I could just store each value as the first Monday/Tuesday etc after Jan 1 1970, but this seems hacky and pretty much plain wrong - as the author of NodaTime, very correctly, explains here when talking about the limitations of the BCL DateTime implementation. I also have a feeling this would probably end up with weird quirky bugs if later on we try and do any arithmetic with the dates.

  • The user is going to have to input the ClosingTime anyway. Client side I suppose I could do something simple like always assume the ClosingTime is the next day if it's before the OpeningTime, but again, it's not perfect, and also doesn't account for places that might be open for more than 24 hours (e.g. supermarkets)

Another thing I've considered is using a table with hours/days and letting people highlight the hours of the week to pick opening times, but you still run into the same problem with only wanting to store the DayOfWeek part of the OpeningTime.

Any suggestions would be appreciated, spending the last 6 hours reading about the hilariously silly ways we humans represent time has burnt me out a bit!

Josh P
  • 366
  • 1
  • 13

1 Answers1

20

I would strongly consider using LocalTime instead of ZonedDateTime, for a couple of reasons:

  • You're not trying to represent a single instant in time; these are naturally recurring patterns (there's no associated date)
  • You're not trying to cope with the situation where the store is in different time zones for different opening hours; you probably want to associate a time zone with each store once, and then you can apply that time zone whenever you want

So I would have something like this (showing just the data members; how you sort out the behaviour is a separate matter):

public class StoreOpeningPeriod
{
    IsoDayOfWeek openingDayOfWeek;
    LocalTime openingTime;
    LocalTime closingTime;
}

Note that this exactly follows your original data as you've shown it, which is always a good sign - you're neither adding nor losing information, and it's presumably in a convenient form.

If the closing time is earlier than the opening time, it's assumed that this crossed midnight - you might want to add a confirmation box for the user if this is relatively uncommon, but it's certainly easy to spot and handle in code.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Ah, this is perfect, thanks a lot! (and thanks for the quick reply too). I've also just realised that handling opening hours extending over multiple days (e.g. a 24h Tesco open from 9am Monday til 8pm Saturday) would simply require adding a `closingDayOfWeek` field, so that's that problem solved too! – Josh P Mar 31 '13 at 23:39
  • 4
    This is basically how we do it in the UK rail industry and it works very well: like you, we run on a weekly cycle and there are lots of activities that span midnight. The only thing to be careful of is the cases when the clocks change. This may not matter in your case, but it can be critical (e.g. train drivers must legally have a gap of at least 12 hours between finishing their shift and starting their next one, even if the clocks have gone forward). – Dave Turner Apr 01 '13 at 01:31
  • 2
    ... contd: The alternative representation is to represent everything as a Period from midnight at the start of the week (Sunday, for example). This is better for representing activities that doesn't naturally break up into days and has the advantage that you can represent things that last for more than 24 hours. Daylights savings are a pain here too, and it will likely cause a hell of a problem if the clocks ever change any time that isn't early on a Sunday morning. – Dave Turner Apr 01 '13 at 01:39
  • 3
    ... one final thing: If you use Jon's method, make sure you encapsulate the crossing-midnight logic very thoroughly. (This is called a 'time inversion'). If you don't, it gets *everywhere*! – Dave Turner Apr 01 '13 at 01:42
  • I like the logic, but how would you use this setup if your store is open, for example, from tuesday 8am all the way to sunday 11pm? Personally I think in that situation you're still better off by just listing the opening hours per day in the data-structure and do some front-end magic to make it look seamless. – gx. Apr 01 '13 at 11:15
  • @gx.: For that unusual situation, you'd pick a different solution. I gave a solution for the question asked :) But yes, you could use Tuesday 8am-midnight, Wednesday midnight-midnight, etc. It's not clear at the moment where this information will actually be used. – Jon Skeet Apr 01 '13 at 11:25
  • @Jon Skeet: Yeah, I realise that, and I wasn't delivering critique on the solution. I happen to know the reason this question is asked, so that's why I reflected on the other situation. The data-structure in this case is to be used for businesses that have long opening hours, assume (near) 24/7 supermarkets or the hotel and catering industry for a mental model. Nonetheless thanks for the thoughtful answer! – gx. Apr 01 '13 at 12:28