Skip to main content

Section 16.13 Case Study: Times

We realize that a calendar program we are helping build is going to is going to have to store and work with times of the day. We have been put in charge of writing the Time class to manage those.
Some tasks we expect to need them to do:
  • Store a time of day.
  • Present it in 12 hour (AM/PM) format or 24 hour format.
  • Figure out a new time by adding a number of minutes to a given time.
We do not need to worry about seconds.

Subsection 16.13.1 Making a Plan

We need to start by figuring out how to represent the data. There are many ways we could represent a time:
  • As the number of minutes since midnight. We could calculate the hour as needed (96 minutes is just another way of saying 1 hour and 36 minutes).
  • As an hours and minutes in 24 hour time.
  • As an hours and minutes in 12 hour time plus whether it is AM or PM.
Believe it or not, the first approach is a common approach for representing time in computers. But we will use the second option, which will make it easier to work in terms of hours and minutes.

Subsection 16.13.2 The Basics

As our first step, we need to define the variables, a constructor, and some getters. That way we have something to test:

Checkpoint 16.13.1.

Complete the constructor for the Time class.

Subsection 16.13.3 Handling Invalid Times

What should we do if someone asks to make Time t1(11, 95)? Should we turn that into 12:35? Or should we treat that as an error and throw an exception? This is a design choice. Assuming that we should try to fix the issue can make the class a little more flexibleβ€”it might be nice to be able to specify Time(0, 190) and get 3:10. But it could also lead to confusion. A value of 190 for minutes could be the result of a bug elsewhere and β€œfixing” it automatically in Time, could hide the existence of that bug.
Instead of trying to fix the issue, we could instead use an exception to indicate there was a problem and require the caller to deal with the issue. This stricter approach can help catch errors earlier.
To do so, we might might start our constructor with some checks:
Listing 16.13.1.
Time::Time(int hours, int minutes) {
    if(hours < 0 || hours > 23) {
        throw logic_error("Invalid value for hours");
    }
    if(minutes < 0 || minutes > 59) {
        throw logic_error("Invalid value for minutes");
    }
    m_hours = hours;
    m_minutes = minutes;
}
This would work just fine. But we will likely need similar code elsewhere. Maybe we should build a function that checks if a given time is valid. We could then use that function to help check the time we are trying to construct.
Listing 16.13.2.
void Time::validate(int hours, int minutes) {
    if(hours < 0 || hours > 23) {
        throw logic_error("Invalid hours value in time");
    }
    if(minutes < 0 || minutes > 59) {
        throw logic_error("Invalid minutes value in time");
    }
}
Because this function is only intended for internal use, we might want to place it in the private section:
Listing 16.13.3.
  ... public code ...
  private:
      void validate(int hours, int minutes);

      int m_hours = 0;
      int m_minutes = 0;
}; // end of class Time
We could then use that function in our constructor:
Listing 16.13.4.
Time::Time(int hours, int minutes) {
    // check the values, throw an exception if they are invalid
    validate(hours, minutes);
    // otherwise safe to use them
    m_hours = hours;
    m_minutes = minutes;
}

Subsection 16.13.4 Displaying Times

Next let’s tackle getting string representations of the time. We want to be able to generate either a 12 hour or 24 hour format. Before we write the functions, we will set up some tests:
Listing 16.13.5.
TEST_CASE("Time toString12Hour") {
    Time t(1, 2);
    CHECK(t.toString12Hour() == "1:02 AM");
    Time t2(14, 20);
    CHECK(t2.toString12Hour() == "2:20 PM");
    Time t3(12, 6);
    CHECK(t3.toString12Hour() == "12:06 PM");
}

TEST_CASE("Time toString24Hour") {
    Time t(1, 2);
    CHECK(t.toString24Hour() == "01:02");
    CHECK(t.toString12Hour() == "1:02 AM");
    Time t2(14, 20);
    CHECK(t2.toString24Hour() == "14:20");
    CHECK(t2.toString12Hour() == "2:20 PM");
}
Then we are ready to implement the functions. Here is toString24Hour:
Listing 16.13.6.
string Time::toString24Hour() {
    // minutes should always be 2 digits, use 0 to pad
    string tString = format("{}:{:0>2}", m_hours, m_minutes);
    return tString;
}
Now we can implement the other function:

Checkpoint 16.13.2.

Implement the toString12Hour function. It should calculate the 12-hour value to show along with the right am/pm designation.

Subsection 16.13.5 Modifiers

Next we need to decide how and if to support changing the time. One approach would be to make Time objects immutable, or unchangeable. If we do not provide setters, or any other methods to change the member variables of a Time, it will be impossible for other code to modify a Time. Instead, anytime we want to change a time, we would need to make a a new Time object. Making Time objects immutable can make it safer to work with Times. If they can’t be changed, we can pass around Time objects by reference without having to worry about them being changed.
But we decide we will support modification of Time objects. Then the question becomes how to do so. Do we provide setters for both hours and minutes? Do we provide a function that sets both to new values? We could just provide one or the other option, but it will be more convenient for users of the class if they can just chose to update either the hour or the minute, or both, with a single call.
Let’s provide functions to change both members as well as both at the same time. They can all use our existing validate function to make sure that the provided values make sense. When we are only given one value, we can pass validate a dummy value that we know is valid for the other one.
Listing 16.13.7.
void Time::setHour(int hour) {
    validate(hour, 1);  // valid dummy value for minutes
    m_hours = hour;
}

void Time::setMinute(int minute) {
    validate(1, minute); // valid dummy value for hours
    m_minutes = minute;
}

void Time::setTime(int hour, int minute) {
    validate(hour, minute);
    m_hours = hour;
    m_minutes = minute;
}

Subsection 16.13.6 Adding Minutes

If main wants to add 30 minutes to a Time, it should not have to getMinute, do math, and then do setTime (and possibly setHour) to do so. So let’s add a function to Time that does that work.

Checkpoint 16.13.3.

Build the addMinutes function.

Subsection 16.13.7 Wrapping up

We certainly could add more functionality to the Time class, such as support for different time zones or the ability to compare two times. And we need to add comments to our class. However, we will stop here. You can find the complete code for the Time class as a module and as a .h/.cpp file pair in the appendix for this chapter.
You have attempted of activities on this page.