Data Structures and Algorithms using Python
Copyright © 2023 by Rance Necaise
Table Of Contents
Data Structures and Algorithms using Python
Copyright © 2023
by Rance Necaise

1.6 Representing a Time Value

There are two common approaches for representing or storing a time value in an object. One approach stores the three components—hours, minutes, and seconds—as three separate fields. To distinguish between AM and PM, either the hours are stored in 24-hour format or a Boolean flag is stored in a fourth field. With this representation, it is easy to access the individual components, but it's more difficult to compare two times or to compute the elapsed number of seconds between two times.

The second approach stores the time as a positive integer value representing the number of seconds that have elapsed since midnight. For example, the time 7:38:05 AM would be represented as 27,485 seconds, the number of seconds that have elapsed between midnight and 7:38:05 AM. Given the time in elapsed seconds, we can compute any of the three time components and simply subtract the two integer values to determine which occurs first or how many seconds separate the two times. We are going to use the latter approach as it is very common for storing times in computer applications and provides for an easy implementation.

Constructing the Time

We begin our discussion of the implementation with the class constructor. Remember, the purpose of a constructor is to define and initialize the instance variables necessary to store the data for an instance of the class. In this case, our constructor must define and initialize a single instance variable that stores the number of seconds that have elapsed since midnight. Here, we use the name numSeconds:

class Time :
  def __init__(self, hours, minutes, seconds) :
    self._numSeconds = hours * 3600 + minutes * 60 + seconds

the value for which is computed from the three time components using the formula:

   elapsed seconds = (hours x seconds per hour) +
                     (minutes x seconds per minute) + seconds
NOTE
NOTE

Private Attributes. Note the use of the underscore in the name of the _numSeconds instance variable. This indicates that the variable is suppose to be private to the class and should not be accessed by code outside of the class definition. See the Special Topic below on "Protected Attributes and Helper Methods" for more information related to the use of protected attributes and methods.

Figure 1.6.1 illustrates the abstract and physical views of our Time ADT. The definition of the ADT provides an abstract view of the new data type, while the implementation provides the real or physical view.

Figure 1.6.1: The abstract and physical views of the Time ADT.
Special Topic
Protected Attributes and Helper Methods

The Time Components

To access the individual time components, each component must be computed from the number of seconds that have elapsed since midnight. The hours component represents the number of full hours that have elapsed since midnight. Since each hour is comprised of 3600 seconds, we divide the elapsed seconds by 3600 using integer division to determine the hour:

def hours(self) :
  return self._numSeconds // 3600

The minutes component is computed from the number of elapsed seconds remaining after determining the number of seconds that comprise full hours. The number of remaining elapsed seconds can be determined using the integer remainder operator. After computing that value, we divide by 60, the number of seconds per minute:

def minutes(self) :
  return (self._numSeconds % 3600) // 60

The seconds component of the time is the number of elapsed seconds remaining that are not part of the hour and minute components.

def seconds(self) :
  return (self._numSeconds % 3600) % 60

To determine if the time is AM or PM, we need to examine the time and compare it to the number of elapsed seconds between midnight and 12 noon.

def isAM(self) :
  return self._numSeconds < (12 * 3600)

Elapsed Time

To compute the elapsed time between two times is simply a matter of subtracting the elapsed seconds used to represent each time. %In Python code, These values are obtained using the instance variables from the two objects referenced by self and otherTime:

def elapsedTime(self, otherTime) :
  return abs(self._numSeconds - otherTime.numSeconds)

Consider the following code segment:

  timeA = Time(7, 38, 5)  # 7:38:05 AM
  timeB = Time(14, 0, 0)  # 2:00:00 PM
  diff = timeA.elapsedTime(timeB)
  print("The elapsed number of seconds =", diff)

which creates two Time objects, timeA and timeB (see Figure 1.6.2), and computes the elapsed time between the two. The resulting output

The elapsed number of seconds = 22915

is produced by subtracting 50400 (the number of elapsed seconds representing 2 PM) from 27485 (the number of elapsed seconds representing 7:38:05 AM) and returning the absolute value.

Figure 1.6.2: Two Time objects representing 7:38:05 AM (left) and 2:00:00 PM (right).

Comparable Objects

We can logically compare two Time instances to determine their numerical order. When using the number of seconds that have elapsed since midnight to represent a time, the comparison is as simple as comparing the two integer values and returning the appropriate Boolean value based on the result of that comparison. The comparable operation defined for the ADT is implemented using Python's special logical comparison operators (see the Special Topic "Overloading Operators" below):

def __eq__(self, otherTime) :
  return self._numSeconds == otherTime._numSeconds

def __lt__(self, otherTime) :
  return self._numSeconds < otherTime._numSeconds

def __le__(self, otherTime) :
  return self._numSeconds <= otherTime._numSeconds

By implementing the methods for the logical comparison operators, instances of the class become comparable objects. That is, the objects can be compared against each other to produce a logical ordering.

PROGRAMMING TIP
PROGRAMMING TIP

Python allows classes to define or overload various operators that can be used more naturally in a program without having to call a method by name. The special methods defined by Python are indicated with names that begin and end with two underscores. Do not confuse this notation with that used for protected attributes and methods, which are denoted with names beginning with a single underscore.

The special methods should not be called directly by name. Instead use the corresponding operator or function and let Python call the method for you. See the Special Topic "Protected Attributes and Helper Methods" at the end of the page for more information on defining and using Python's special operator methods.

You will notice that we implemented only three of the logical comparison operators. The reason for this is that starting with Python version 3, Python will automatically swap the operands and call the appropriate reflective method when necessary. For example, if we use the expression timeA > timeB with Time objects in a program,

if timeA > timeB :
  print("timeA occurs after timeB")

Python will automatically swap the operands and call timeB < timeA instead since the lt method is defined but not gt. It will do the same for timeA >= timeB and timeA <= timeB. When testing for equality, Python will automatically invert the result when only one of the equality operators (== or !=) is defined. Thus, we need only define one operator from each of the following pairs to achieve the full range of logical comparisons: < or >, <= or >=, and == or !=.

Special Topic
Overloading Operators

String Representations

The toString operation defined by the ADT is also implemented using a special operator method. This repr special method is supposed to build and return a meaningful string representation of the object's value. The contents of the string depends on the specific ADT. For the Time ADT, it should return a string representing the time in the 24-hour format "hh:mm:ss".

Since the time is stored as the number of seconds that have elapsed since midnight, we must call the appropriate time component method (hours, minutes, and seconds) on the object itself to obtain the three time components. These values can then be used to create and return the string. The implementation of the repr special method is provided below:

def __repr__(self) :
  hrs = self.hours()
  mins = self.minutes()
  secs = self.seconds()
  return "%d:%02d:%02d" % (hrs, mins, secs)

A partial implementation of the Time class is provided below. The implementation of the remaining methods is left as an exercise. As a reminder, the ADT operations specified in the definition using italicized text are implemented using Python's special methods and not using the name given in the ADT definition. Thus, for example, there is no method named toString in the Time class. Instead that operation is implemented using the repr special method. Likewise, there is no comparable method. That ADT operation is implemented by the three special logical operators: eq, lt, and le.

Program Listing
Program: mytime.py
  1. # Implementation of the Time ADT using the number of seconds that have
  2. # elapsed since midnight.
  3.  
  4. class Time :
  5.    # Creates a new Time instance from the given time components.
  6.   def __init__(self, hours, minutes, seconds) :
  7.     self._numSeconds = hours * 3600 + minutes * 60 + seconds
  8.  
  9.    # Returns the hour part of the time (0 - 23)
  10.   def hours(self) :
  11.     return self._numSeconds // 3600
  12.  
  13.    # Returns the minutes part of the time (0 - 59)
  14.   def minutes(self) :
  15.     return (self._numSeconds % 3600) // 60
  16.  
  17.    # Returns the seconds part of the time (0 - 59)
  18.   def seconds(self) :
  19.     return (self._numSeconds % 3600) % 60
  20.  
  21.    # Returns True if this time is ante meridiem (AM) and False otherwise.
  22.   def isAM(self) :
  23.     return self._numSeconds < (12 * 3600)
  24.  
  25.    # Returns the number of seconds between this time and the otherTime.
  26.   def elapsedTime(self, otherTime) :
  27.     return abs(self._numSeconds - otherTime._numSeconds)
  28.  
  29.    # Logically compares this time with the otherTime.
  30.   def __eq__(self, otherTime) :
  31.     return self._numSeconds == otherTime._numSeconds
  32.  
  33.   def __lt__(self, otherTime) :
  34.     return self._numSeconds < otherTime._numSeconds
  35.  
  36.   def __le__(self, otherTime) :
  37.     return self._numSeconds <= otherTime._numSeconds
  38.  
  39.    # Returns a string representation of the time as hh:mm:ss
  40.   def __repr__(self) :
  41.     hrs = self.hours()
  42.     mins = self.minutes()
  43.     secs = self.seconds()
  44.     return "%d:%02d:%02d" % (hrs, mins, secs)
  45.    
  46.    # --- The remaining methods go here ---    
Page last modified on February 09, 2024, at 08:29 AM