tags:

views:

95

answers:

4

Hi, I'm developing an app that requires the notion of opening times (A bit like a shop)

How best to represent these? They will be persisted in a DB later...

So far I have the following class:

public class OpeningTime
{
    public DayOfWeek DayOfWeek { get; set; }
    public DateTime OpeningTime { get; set; }
    public DateTime ClosingTime { get; set; }
}

So my fictional "shop" class would be something like this:

public class Shop
{
    public string Name { get; set; }
    public string Address { get; set; }
    public List<OpeningTime> OpeningTimes { get; set; }
}

Is DateTime still correct for representing something like:

Monday - 9:00 - 17:30

+2  A: 

Personally I'd use TimeSpan rather than DateTime, but I've heard others express the contrary view.

[Serializable()]
public class OpeningTime
{
    protected OpeningTime()
    {
    }

    public OpeningTime(DayOfWeek dayOfWeek, TimeSpan fromTime, TimeSpan toTime) 
        : this(dayOfWeek, fromTime.Hours, fromTime.Minutes, toTime.Hours, toTime.Minutes) { }

    public OpeningTime(DayOfWeek dayOfWeek, int fromHours, int fromMinutes, int toHours, int toMinutes)
    {
        if (fromHours < 0 || fromHours > 23)
        {
            throw new ArgumentOutOfRangeException("fromHours", "fromHours must be in the range 0 to 23 inclusive");
        }

        if (toHours < 0 || toHours > 23)
        {
            throw new ArgumentOutOfRangeException("toHours", "toHours must be in the range 0 to 23 inclusive");
        }

        if (fromMinutes < 0 || fromMinutes > 59)
        {
            throw new ArgumentOutOfRangeException("fromMinutes", "fromMinutes must be in the range 0 to 59 inclusive");
        }

        if (toMinutes < 0 || toMinutes > 59)
        {
            throw new ArgumentOutOfRangeException("toMinutes", "toMinutes must be in the range 0 to 59 inclusive");
        }

        this.FromTime = new TimeSpan(fromHours, fromMinutes, 0);
        this.ToTime = new TimeSpan(toHours, toMinutes, 0);

        if (this.FromTime >= this.ToTime)
        {
            throw new ArgumentException("From Time must be before To Time");
        }

        this.DayOfWeek = dayOfWeek;
    }

    public virtual DayOfWeek DayOfWeek { get; private set; }

    public virtual TimeSpan FromTime { get; private set; }

    public virtual TimeSpan ToTime { get; private set; }
}
Ian Nelson
+1  A: 

You can use TimeSpan to represent a time of day.

Peter
A: 

I think you could internally represent this as a DateTime for the start (which gives you the DayOfWeek) and then a TimeSpan for the end but expose them as three properties that are either DateTimes or TimeSpans

Now to switch on a more DDD hat... You "can" use a TimeSpan object to represent a time of day but if I were thinking along the DDD terms I would want to be thinking about what Time does the store open. TimeSpans can represent way more information and you have to specifically validate each time you are dealing with them that they are not more than a day long.

So one way is to create a simple class that represents what you are trying to represent (you could just wrap a TimeSpan object rather than using several ints).

public struct Time 
{
    private readonly int _hour;
    private readonly int _minute;
    private readonly int _second;

    public Time(int hour, int minute, int second)
    {
        if (hour < 0 || hour >= 24)
            throw new ArgumentOutOfRangeException("hour", "Hours must be between 0 and 23 inclusive");
        if (minute < 0 || minute > 59)
            throw new ArgumentOutOfRangeException("minute", "Minutes must be between 0 and 23 inclusive");
        if (second < 0 || second > 59)
            throw new ArgumentOutOfRangeException("second", "Seconds must be between 0 and 23 inclusive");
        _hour = hour;
        _minute = minute;
        _second = second;
    }

    public Time(Time time)
        : this(time.Hour, time.Minute, time.Second)
    {

    }

    public int Hour { get { return _hour; } }
    public int Minute { get { return _minute; } }
    public int Second { get { return _second; } }

    public override string ToString()
    {
        return ToString(true);
    }

    public string ToString(bool showSeconds)
    {
        if (showSeconds)
            return string.Format("{0:00}:{1:00}:{2:00}", Hour, Minute, Second);
        return string.Format("{0:00}:{1:00}:{2:00}", Hour, Minute);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (obj.GetType() != typeof (Time)) return false;
        return Equals((Time) obj);
    }

    public bool Equals(Time other)
    {
        return other._hour == _hour && other._minute == _minute && other._second == _second;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int result = _hour;
            result = (result*397) ^ _minute;
            result = (result*397) ^ _second;
            return result;
        }
    }

}

public class OpeningHours
{
    public DayOfWeek DayOfWeek { get; set; }
    public Time OpeningTime { get; set; }
    public Time ClosingTime { get; set; }
    public OpeningHours(DayOfWeek dayOfWeek, Time openingTime, Time closingTime)
    {
        DayOfWeek = dayOfWeek;
        OpeningTime = openingTime;
        ClosingTime = closingTime;
    }
}
Ian Johnson
Re-inventing datetimes, sweet!
John Buchanan
Should you be exposing embedded types in your domain? neither DateTime nor TimeSpan actually represent a generic time of day, they can be used to do so but that does not make it correct. You can just store everything as a string, why not just do that? It does not make conceptual sense
Ian Johnson
+1  A: 

Stick with DateTime

I would just use DateTime as it maps nicely to SQL (SQL uses DateTime also) and I find it's more readable than using TimeSpan. You can also use the same object and add a date to it to get Today's opening times for example.

Why not TimeSpan?

Because of it's name really and what that implies. TimeSpan represents a period of time as opposed to a point in time. Although it is used in the framework to represent an exact time (in TimeOfDay), but that property is defined in the docs as:

A TimeSpan that represents the fraction of the day elapsed since midnight.

But... doesn't really matter much

In the end it makes little difference, you can use TimeSpan, DateTime or your own struct. There is little overhead of either, it just comes down to what you find more readable and easier to maintain.

badbod99