#include #include // One of the biggest issues when learning to program is // recognizing when it is useful to write a function. // When solving a larger problem, you may encounter a // smaller problem that must be solved as part of the // larger problem but is otherwise entirely self-contained. // // This exercise was inspired by Yi Fan Liu. // // Author a program that asks the user how many seconds // have passed since midnight (12:00:00 AM) and prints // the corresponding time in a more human-readable format: // // hh:mm:ss AM // // or // // hh:mm:ss PM. // // If the integer is negative, quit. // // If the value is between 0 and 24*60*60 - 1, print the // time followed by "today". // // If the minutes or seconds are less than 10, they must // have a leading zero. For example, // // 32949 // // must be printed as // // 9:09:09 AM today. // // Note also that // // 488 // // must be printed as // // 12:08:08 AM today, // // not // // 0:08:08 AM today. // // If the value represents a time after today, print the // time followed by the number of days from now. For // example, one million seconds should be printed as // // 1:46:40 PM 11 days from now. // // If the time is tomorrow, simply print "tomorrow". // // Breaking the problem down: // // There is a larger problem involving the flow of // execution: // // - Query the user for a value. // - Determine whether to continue. // - Convert a valid value into days, hours, minutes, // and seconds. // - Print the time. // - Print the relative day. // // The bigger problem: // // To begin, do not write everything. Just write the loop // that queries the user for an integer. For now, print // // 12:00:00 AM today // // for every non-negative value. // Function declarations int main(); // Function definitions int main() { return 0; } // You probably came up with something like this: /* // Function definitions int main() { int time{}; while ( true ) { std::cout << "Enter a time in seconds (negative to quit): "; std::cin >> time; if ( time < 0 ) { break; } std::cout << "12:00:00 AM today" << std::endl; } return 0; } */ // If you run this, the program queries the user for a // time and then either ends the loop or prints the // default value. The important point is that the program // compiles and the overall flow of execution is correct. // // The next subproblem: // // Add code that breaks the time into // // days, hours, minutes, and seconds. // // For now, do not try to do any special formatting. // Instead, just print // // days hours minutes seconds // You probably came up with something like this: /* // Function definitions int main() { int time{}; while ( true ) { std::cout << "Enter a time in seconds (negative to quit): "; std::cin >> time; if ( time < 0 ) { break; } int days{ time/(24*60*60) }; time %= 24*60*60; int hours{ time/(60*60) }; time %= 60*60; int minutes{ time/60 }; int seconds{ time%60 }; std::cout << days << " " << hours << " " << minutes << " " << seconds << std::endl; } return 0; } */ // Now test whether your code works. // // Come up with some simple tests. // Here are some examples: // // 12 should print "0 0 0 12" // 1234 should print "0 0 20 34" // 12345 should print "0 3 25 45" // 123456 should print "1 10 17 36" // 1234567 should print "14 6 56 7" // // If your code does not produce these results, go back // and fix it. // // Continuing on: // // When we want to print, for example, // // 3:45:12 AM tomorrow, // // there are two independent subproblems: // // 1. Print the time: // // 3:45:12 AM // // 2. Print the relative day: // // tomorrow // // This suggests writing two functions: one to print the // time and another to print the relative day. // // What should each function do, specifically? // What information must be passed to each function? // // As the second function is easier, implement it as well. // We will decide that each function prints only its // portion of the text, with no leading spaces and no // end-of-line characters. This allows main() to control // how the pieces are combined. // // The program should eventually have the following // structure: /* // Function definitions int main() { int time{}; while ( true ) { std::cout << "Enter a time in seconds (negative to quit): "; std::cin >> time; if ( time < 0 ) { break; } int days{ time/(24*60*60) }; time %= 24*60*60; int hours{ time/(60*60) }; time %= 60*60; int minutes{ time/60 }; int seconds{ time%60 }; print_time( hours, minutes, seconds ); std::cout << " "; print_relative_day( days ); std::cout << std::endl; } return 0; } */ // Before we continue, notice that we repeatedly use // // 24*60*60 // 60*60 // 60 // // These values are constants, but someone reading the // code may not immediately understand what they mean. // In this case, their meanings may seem obvious, but // that will not always be true. // // Give these constants descriptive names and use those // names from this point onward. // The program should now have the following structure: /* // Function definitions int main() { constexpr int seconds_per_day{ 24*60*60 }; constexpr int seconds_per_hour{ 60*60 }; constexpr int seconds_per_minute{ 60 }; int time{}; while ( true ) { std::cout << "Enter a time in seconds (negative to quit): "; std::cin >> time; if ( time < 0 ) { break; } int days{ time/seconds_per_day }; time %= seconds_per_day; int hours{ time/seconds_per_hour }; time %= seconds_per_hour; int minutes{ time/seconds_per_minute }; int seconds{ time%seconds_per_minute }; print_time( hours, minutes, seconds ); std::cout << " "; print_relative_day( days ); std::cout << std::endl; } return 0; } */ // For me, this requires two functions: // // void print_time( // int hours, // int minutes, // int seconds // ); // // void print_relative_day( int days ); // // Write these functions. For now, do not worry about // converting 0 hours to 12 or adding leading zeros to // minutes and seconds. We can deal with those problems // later. // Here is my implementation: /* // Function declarations int main(); void print_time( int hours, int minutes, int seconds ); void print_relative_day( int days ); // Function definitions int main() { constexpr int seconds_per_day{ 24*60*60 }; constexpr int seconds_per_hour{ 60*60 }; constexpr int seconds_per_minute{ 60 }; int time{}; while ( true ) { std::cout << "Enter a time in seconds (negative to quit): "; std::cin >> time; if ( time < 0 ) { break; } int days{ time/seconds_per_day }; time %= seconds_per_day; int hours{ time/seconds_per_hour }; time %= seconds_per_hour; int minutes{ time/seconds_per_minute }; int seconds{ time%seconds_per_minute }; print_time( hours, minutes, seconds ); std::cout << " "; print_relative_day( days ); std::cout << std::endl; } return 0; } // void print_time( ... ) // // Given three integers representing hours, minutes, and // seconds, print the time in the format // // HH:MM:SS AM // // or // // HH:MM:SS PM. // // For now, HH is between 0 and 11 and MM and SS are // printed without leading zeros. void print_time( int hours, int minutes, int seconds ) { std::cout << hours%12 << ":" << minutes << ":" << seconds << " "; if ( hours < 12 ) { std::cout << "AM"; } else { std::cout << "PM"; } } // void print_relative_day( ... ) // // Given a non-negative integer 'days', print the day at // which a time occurs relative to today: // // days == 0: print "today" // days == 1: print "tomorrow" // otherwise: print "n days from now" // // where 'n' is the value of 'days'. void print_relative_day( int days ) { if ( days == 0 ) { std::cout << "today"; } else if ( days == 1 ) { std::cout << "tomorrow"; } else { std::cout << days << " days from now"; } } */ // Now try our tests: // // 12 should print "0:0:12 AM" and "today" // 1234 should print "0:20:34 AM" and "today" // 12345 should print "3:25:45 AM" and "today" // 123456 should print "10:17:36 AM" and "tomorrow" // 1234567 should print "6:56:7 AM" and "14 days from now" // // If you look at these tests, none tests a time in the // afternoon. The original values were chosen somewhat // arbitrarily, and by chance none ended up in the // afternoon. // // Add some carefully chosen boundary tests: // // 0 should print "0:0:0 AM" and "today" // 43199 should print "11:59:59 AM" and "today" // 43200 should print "0:0:0 PM" and "today" // 86399 should print "11:59:59 PM" and "today" // 86400 should print "0:0:0 AM" and "tomorrow" // 129599 should print "11:59:59 AM" and "tomorrow" // 129600 should print "0:0:0 PM" and "tomorrow" // 172799 should print "11:59:59 PM" and "tomorrow" // 172800 should print "0:0:0 AM" and "2 days from now" // // If your code does not produce these results, try to // determine why and fix it. // // Next problem: // // We must print the hours as 1 through 12 rather than // 0 through 11, and we must print minutes and seconds // with a leading zero if they are less than 10. // // These sound like two more self-contained subproblems. // Come up with two functions that solve these problems // and implement them. // Here is my solution: /* // Function declarations int main(); void print_time( int hours, int minutes, int seconds ); void print_relative_day( int days ); void print_twelve_hour_time( int hours ); void print_minutes_seconds( int value ); // Function definitions int main() { constexpr int seconds_per_day{ 24*60*60 }; constexpr int seconds_per_hour{ 60*60 }; constexpr int seconds_per_minute{ 60 }; int time{}; while ( true ) { std::cout << "Enter a time in seconds (negative to quit): "; std::cin >> time; if ( time < 0 ) { break; } int days{ time/seconds_per_day }; time %= seconds_per_day; int hours{ time/seconds_per_hour }; time %= seconds_per_hour; int minutes{ time/seconds_per_minute }; int seconds{ time%seconds_per_minute }; print_time( hours, minutes, seconds ); std::cout << " "; print_relative_day( days ); std::cout << std::endl; } return 0; } // void print_time( ... ) // // Given three integers representing hours, minutes, and // seconds, print the time in the format // // HH:MM:SS AM // // or // // HH:MM:SS PM. // // HH is 1 through 12 and MM and SS are 00 through 59. void print_time( int hours, int minutes, int seconds ) { print_twelve_hour_time( hours ); std::cout << ":"; print_minutes_seconds( minutes ); std::cout << ":"; print_minutes_seconds( seconds ); std::cout << " "; if ( hours < 12 ) { std::cout << "AM"; } else { std::cout << "PM"; } } // void print_relative_day( ... ) // // Given a non-negative integer 'days', print the day at // which a time occurs relative to today: // // days == 0: print "today" // days == 1: print "tomorrow" // otherwise: print "n days from now" // // where 'n' is the value of 'days'. void print_relative_day( int days ) { if ( days == 0 ) { std::cout << "today"; } else if ( days == 1 ) { std::cout << "tomorrow"; } else { std::cout << days << " days from now"; } } // void print_twelve_hour_time( ... ) // // Given an integer 'hours' between 0 and 11, print the // corresponding hour between 1 and 12: // // 0 is printed as 12, // 1 through 11 are printed unchanged. void print_twelve_hour_time( int hours ) { if ( hours == 0 ) { std::cout << 12; } else { std::cout << hours; } } // void print_minutes_seconds( ... ) // // Given an integer 'value' between 0 and 59, print the // value using exactly two digits. // // Because printing an integer already prints at least one // digit, we need only print an additional '0' before // values between 0 and 9. void print_minutes_seconds( int value ) { if ( value <= 9 ) { std::cout << 0 << value; } else { std::cout << value; } } */ /////////////////////////////////// // HOW NOT TO SOLVE THIS PROBLEM // /////////////////////////////////// // Do not solve this problem by putting everything in // main(): /* // Function declarations int main(); // Function definitions int main() { int time{}; while ( true ) { std::cout << "Enter a time in seconds (negative to quit): "; std::cin >> time; if ( time < 0 ) { break; } int days{ time/(24*60*60) }; time %= 24*60*60; int hours{ time/(60*60) }; time %= 60*60; int minutes{ time/60 }; int seconds{ time%60 }; if ( hours == 0 ) { std::cout << "12:"; } else { std::cout << hours << ":"; } if ( minutes <= 9 ) { std::cout << 0 << minutes << ":"; } else { std::cout << minutes << ":"; } if ( seconds <= 9 ) { std::cout << 0 << seconds; } else { std::cout << seconds; } if ( hours < 12 ) { std::cout << " AM "; } else { std::cout << " PM "; } if ( days == 0 ) { std::cout << "today" << std::endl; } else if ( days == 1 ) { std::cout << "tomorrow" << std::endl; } else { std::cout << days << " days from now" << std::endl; } } return 0; } */ ////////////////////// // SUBSEQUENT STEPS // ////////////////////// // We have now written four functions that help solve a // reasonably straightforward problem. You may ask why // this is preferable to putting everything in main(). // // Most importantly, each function can now be tested // independently. Once a function has been thoroughly // tested and found to work correctly for all values in // its specified domain, we generally do not need to // investigate its implementation when searching for a // bug elsewhere in the program. // // Functions can also be reused. For example, we might // later want to write a date and time in different // formats, including an ISO 8601 representation such as // // 1970-01-01T00:00:00 // // which represents midnight at the beginning of // January 1, 1970. There is no reason to rewrite code // that already performs part of the required task. // // Another important question concerns invalid arguments. // For example, print_minutes_seconds() expects a value // between 0 and 59. What should happen if it is passed // some other value? // // One approach is to state that values outside this // range violate the requirements of the function and // use an assertion to detect such a programming error: /* void print_minutes_seconds( int value ) { assert( (value >= 0) && (value <= 59) ); if ( value <= 9 ) { std::cout << 0 << value; } else { std::cout << value; } } */ // The assertion documents and checks a precondition of // the function: // // 0 <= value <= 59. // // If the calling code violates this precondition while // assertions are enabled, the program stops at the // assertion. This makes the programming error easier to // find rather than silently changing the invalid value.