Student Created Guide
Section 1: Introduction to the Chada Tech Clock Project
This section introduces the CS-210 Project One, a simulated real-world task for aspiring junior developers. You'll learn about the project's goals, the core requirements of creating a dual 12-hour and 24-hour clock application in C++, and how your work will be evaluated. Understanding these foundational elements is the first step to success.
1.1. Understanding the Core Task: The Chada Tech Clocks
The Chada Tech interview scenario requires the development of a dual clock display system. This system is intended to meet international standard ISO 8601 by providing both 12-hour and 24-hour time formats. Key aspects include simultaneous display, user input handling for time manipulation, and adherence to secure and efficient C++ coding practices. It is important to note that this assignment is a simulation of clock functionality; it does not require display of the local time zone, real-time ticking, or synchronization with system time. The emphasis is on program logic, object-oriented design, and code quality.
1.2. Deconstructing the Project Requirements and Rubric
A thorough understanding of the "Chada Tech Clocks Functional Requirements" document and the "Project One Rubric" is paramount. The functional requirements will detail specific behaviors, such as the format of the clock displays, the menu options, and how the clocks should respond to user input. The rubric outlines the criteria for assessment, including:
- Follows Flowchart Diagram: The applicationβs sequence of functions must align with the provided flowchart logic.
- Code Modularization: Code should be organized into functions, minimizing the amount of code within the
main()
function. (20 points) - Code Execution (Clock Displays, Menu Functionality, Responds to User Input): The program must correctly display both clocks in the specified formats, present a functional menu, and accurately manipulate the clocks based on user selections. (Combined 55 points)
- Industry Standard Best Practices: This includes in-line comments, appropriate naming conventions, and overall code clarity. (15 points, opportunity to exceed)
Adherence to these requirements and rubric criteria will be central to achieving a successful project outcome.
π Connect with Coding United
This project guide is brought to you by a Coding United member! Explore our resources and join our community:
Section 2: Laying the Groundwork: Essential C++ Concepts
Before diving into coding, it's crucial to have a solid grasp of fundamental C++ concepts. This section outlines the building blocks you'll need for the clock project, from basic input/output operations to the principles of object-oriented programming. Each concept plays a vital role in constructing a functional and well-structured application.
Concept | Relevance to Project | Key Aspects to Understand |
---|---|---|
Basic Input/Output | Getting initial time from user, reading menu choices, displaying clocks and menu. | std::cin , std::cout , <iostream> header, stream extraction (>> ) and insertion (<< ) operators. |
Control Flow | Implementing program logic from flowchart: main loop, menu decision making, time update conditions. | if-else , switch , while loops. |
Functions | Modularizing code (e.g., for displaying menu, getting input, updating time, displaying clocks), minimizing main() . |
Declaration (prototype), definition, parameters, return types, function calls. |
Basic Time Concepts | Representing and formatting time components (hours, minutes, seconds, AM/PM). | Integer variables for H, M, S; <ctime> for struct tm and strftime (optional for formatting). |
Object-Oriented Prog. | Designing Clock class(es) to encapsulate time data and operations. |
Classes, objects, member variables, member functions (methods), constructors, encapsulation. |
Headers & Source Files | Organizing class declarations and definitions into separate .h and .cpp files. |
#include , header guards (#ifndef/#define/#endif or #pragma once ). |
2.1. Communicating with the User: Basic Input/Output (<iostream>
)
The <iostream>
header file is fundamental for console-based applications, providing tools for standard input and output operations. The primary objects used will be std::cout
for displaying information (like the clocks and menu) to the screen and std::cin
for reading user input (like the initial time settings and menu choices).
std::cout
uses the insertion operator (<<
) to send data to the output stream. For example:
#include <iostream>
int main() {
std::cout << "Welcome to the Chada Tech Clock Project!" << std::endl;
return 0;
}
std::cin
uses the extraction operator (>>
) to get data from the input stream (usually the keyboard) and store it in variables. For instance, to get a user's menu choice:
#include <iostream>
//...
int main() {
int userChoice;
std::cout << "Enter your choice: ";
std::cin >> userChoice; // Reads an integer from input
std::cout << "You entered: " << userChoice << std::endl;
return 0;
}
It is important to be aware that when std::cin
is used to read into a std::string
, it typically stops reading at the first whitespace character (space, tab, or newline). While this project primarily involves integer input for menu choices and time components, this behavior is a key characteristic of std::cin
. For integer input, std::cin
will attempt to parse a number; if non-numeric characters are entered, the stream can enter an error state, which may require explicit handling for more robust applications (discussed later in input validation).
2.2. Controlling the Flow: if-else
, switch
, and Loops for Program Logic
Control flow statements are essential for dictating the order in which code executes, enabling decision-making and repetition, which are critical for implementing the program's flowchart logic.
These structures allow the program to execute different blocks of code based on whether a condition is true or false. They are vital for logic such as determining AM/PM, handling time rollovers, or validating user input ranges.
if (hour == 0) {
// Handle midnight case for 12-hour clock
} else if (hour == 12) {
// Handle noon case for 12-hour clock
} else {
// Handle other hours
}
A switch
statement provides a clean way to select one of many code blocks to be executed based on the value of an expression, typically an integer or character. This is ideal for handling the user's menu choice. Each case
corresponds to a possible choice, and the break
statement is crucial to prevent "fall-through". A default
case can handle invalid inputs.
// Assuming 'userChoice' is an int
switch (userChoice) {
case 1:
// Call function to add hour
break; // Exits the switch statement
case 2:
// Call function to add minute
break;
// ... other cases ...
default:
std::cout << "Invalid choice. Please try again." << std::endl;
}
The while
loop repeatedly executes a block of statements as long as a given condition remains true. This is perfectly suited for the main program loop, which should continue until the user selects "Exit Program".
bool programIsRunning = true;
while (programIsRunning) {
// Display clocks
// Display menu
// Get user input
// Process input
// If user chooses to exit, set programIsRunning = false;
}
The condition is checked before each iteration. Ensure actions within the loop can eventually make the condition false to avoid infinite loops.
2.3. Organizing Your Code: The Power of Functions
Functions allow breaking down a program into manageable, reusable pieces. Each function should perform a single task, enhancing readability and maintainability. The rubric emphasizes minimizing code in main()
.
- Declaration (Prototype): Informs compiler of function's name, return type, parameters. E.g.,
void displayUserMenu();
- Definition: The actual code block of the function.
- Parameters: Input values for the function.
- Return Type: Value returned by function (
void
if none).
// Prototype
void displayUserMenu();
// Definition
void displayUserMenu() {
std::cout << "******************************\n";
// ... menu items ...
std::cout << "******************************\n";
}
Using functions makes main()
an orchestrator, calling other functions for specific tasks.
2.4. A Quick Look at Time in C++: <ctime>
and struct tm
(and <chrono>
)
C++ offers ways to work with time. <ctime>
(C-style) provides struct tm
to hold time components (seconds, minutes, hours, etc.) and functions like strftime
for formatting.
struct tm {
int tm_sec; // seconds (0-61)
int tm_min; // minutes (0-59)
int tm_hour; // hours (0-23)
//... other members
};
<chrono>
is a more modern, type-safe library. For this project, manually incrementing integers for time components is simpler and aligns with example solutions. <ctime>
might be used optionally for formatting.
Section 3: The Heart of the Matter: Designing Your Clock's Brain
This section dives into the core logic of your clock application. You'll explore how to represent time internally, implement the mechanics for adding seconds, minutes, and hours (including handling rollovers), and master the specific formatting requirements for both 12-hour and 24-hour displays.
3.1. Representing Time: Storing Hours, Minutes, and Seconds
Simple integer variables (int hours;
, int minutes;
, int seconds;
) are suitable for storing time components within your clock class(es).
For the 12-hour clock, an AM/PM indicator is needed. This can be a bool isPM;
or an enum AmPmState { AM, PM };
. The 24-hour clock (0-23 hours) doesn't need this. Using separate classes for 12-hour and 24-hour clocks is a good OOP approach.
3.2. The Tick-Tock Logic: Adding Seconds, Minutes, and Hours
Incrementing time units and handling rollovers is critical.
- Adding a Second (
addSecond()
): Increment seconds. If seconds reach 60, reset to 0 and calladdMinute()
. - Adding a Minute (
addMinute()
): Increment minutes. If minutes reach 60, reset to 0 and calladdHour()
. - Adding an Hour (
addHour()
):- 24-Hour Clock: If hours reach 24, reset to 0.
- 12-Hour Clock: More complex. Incrementing from 11 AM/PM to 12 PM/AM requires toggling AM/PM. Hours typically range 1-12 for display, though internal storage might be 0-11.
Conceptual addSecond
method:
void Clock::addSecond() {
seconds++;
if (seconds >= 60) {
seconds = 0;
addMinute(); // Cascade update
}
}
3.3. Mastering Time Formats
Correct formatting (leading zeros, AM/PM) is key.
Hours: 00-23, Minutes: 00-59, Seconds: 00-59. Leading zeros required (e.g., "09:05:03").
Hours: 01-12, Minutes/Seconds: 00-59. Leading zeros required. AM/PM designation.
Key Points for 12 o'clock:
- Midnight: 12:00:00 AM (00:00:00 in 24-hour)
- Noon: 12:00:00 PM (12:00:00 in 24-hour)
Simple modulo 12 arithmetic is insufficient for 24-to-12 hour conversion due to these cases. Special logic is needed for hours 0 (midnight) and 12 (noon).
If managing time internally in 24-hour format (hour24
from 0-23):
- If
hour24 == 0
(Midnight):hour12 = 12
,meridiem = "AM"
. - If
hour24 >= 1 && hour24 <= 11
(Morning):hour12 = hour24
,meridiem = "AM"
. - If
hour24 == 12
(Noon):hour12 = 12
,meridiem = "PM"
. - If
hour24 >= 13 && hour24 <= 23
(Afternoon/Evening):hour12 = hour24 - 12
,meridiem = "PM"
.
Section 4: Building with Blueprints: Object-Oriented Programming for Clocks
The project requires an Object-Oriented Programming (OOP) approach. This section explains why OOP is beneficial for this project and guides you through designing your Clock
class(es), including how to structure your C++ header (.h
) and source (.cpp
) files effectively.
4.1. Why OOP? Benefits for Your Project
OOP revolves around "objects" that bundle data and methods.
- Encapsulation: Bundles data (time components) and methods (addHour, getFormattedTime) into a class, protecting data and simplifying use.
- Abstraction: Hides complex implementation details (like rollover logic), exposing only essential features.
- Modularity and Reusability: Classes act as blueprints, promoting modular design.
4.2. Designing Your Clock
Class (or classes)
A recommended approach is using an abstract base class (Time
) with derived classes (Clock12
, Clock24
).
Time
(Abstract Base Class)virtual void addOneHour() = 0;
virtual void addOneMinute() = 0;
virtual void addOneSecond() = 0;
virtual std::string getFormattedTime() const = 0;
Clock12
(Derived)h, m, s, meridiem
Overrides:
addOneHour()
, getFormattedTime()
, etc.
Clock24
(Derived)h, m, s
Overrides:
addOneHour()
, getFormattedTime()
, etc.
Member Variables (Data): int
for hours, minutes, seconds. For Clock12
, an enum AmPmState { AM, PM };
is good for clarity.
Member Functions (Methods):
- Constructors: To initialize objects (set initial time).
- Time Manipulation:
addHour()
,addMinute()
,addSecond()
. - Formatting:
getFormattedTime()
returning astd::string
.
4.3. Structuring Your Project Files: .h
(Declarations) and .cpp
(Implementations)
Separate class declarations from definitions.
- Header Files (
.h
or.hpp
): Contain class declarations, function prototypes. Must use include guards (#ifndef/#define/#endif
or#pragma once
) to prevent redefinition errors. - Source Files (
.cpp
): Contain definitions (implementations) of member functions. Include corresponding header file.main()
is typically in its own.cpp
file.
Example Clock.h
(with #pragma once
):
// Clock.h
#pragma once
#include <string>
enum class AmPmState { AM, PM };
class Clock12 {
public:
Clock12(int h, int m, int s, AmPmState ampm);
void addHour();
std::string getFormattedTime() const;
private:
int hour, minute, second;
AmPmState meridiem;
};
// ... Clock24 declaration ...
Example Clock.cpp
:
// Clock.cpp
#include "Clock.h"
#include <iomanip>
#include <sstream>
Clock12::Clock12(int h, int m, int s, AmPmState ampm) : hour(h), minute(m), second(s), meridiem(ampm) {}
void Clock12::addHour() { /* ... 12-hour logic ... */ }
std::string Clock12::getFormattedTime() const {
std::ostringstream oss;
// oss << std::setfill('0') << std::setw(2) << (hour == 0 ? 12 : hour);
return oss.str();
}
// ... other definitions ...
Include guards are essential to prevent "redeclaration" errors when headers are included multiple times.
Section 5: Bringing Your Clocks to Life: Implementation Steps
This section walks you through the practical steps of coding your clock application, translating the design and flowchart into functional C++ code. You'll cover setting the initial time, implementing the main program loop, crafting the user menu, processing input, and displaying the clocks as required.
5.1. Setting Initial Time (User Input)
Prompt user for initial hours, minutes, seconds. Validate input (e.g., hours 0-23, min/sec 0-59). Use these values to initialize clock objects.
int initialHour, initialMinute, initialSecond;
std::cout << "Enter initial hour (0-23): ";
std::cin >> initialHour;
// ... validate and read minutes, seconds ...
// Clock24 clock24(initialHour, initialMinute, initialSecond);
// Clock12 clock12(convertedH12, initialMinute, initialSecond, ampm_val);
5.2. Implementing the Program Flowchart: The Main Loop
Use a while
loop controlled by a boolean flag (e.g., bool keepRunning = true;
).
Inside the loop:
- Display Clocks
- Display Menu
- Get User Input (validated choice)
- Process Choice (
switch
statement):- Add Hour: Call
addHour()
on both clocks. - Add Minute: Call
addMinute()
on both clocks. - Add Second: Call
addSecond()
on both clocks. - Exit: Set
keepRunning = false;
or callexit(0);
. - Default: Handle invalid choice.
- Add Hour: Call
The flowchart can be visualized as follows:
5.3. Crafting the User Menu
Display the menu exactly as specified. A function displayMenu()
is ideal.
void displayMenu() {
std::cout << "******************************\n";
std::cout << "* 1- Add One Hour *\n";
std::cout << "* 2- Add One Minute *\n";
std::cout << "* 3- Add One Second *\n";
std::cout << "* 4- Exit Program *\n";
std::cout << "******************************\n";
std::cout << "Enter your choice: ";
}
Menu Option Displayed | User Input (Integer) | Action Performed |
---|---|---|
* 1- Add One Hour * | 1 | Add one hour to both clocks; refresh display. |
* 2- Add One Minute * | 2 | Add one minute to both clocks; refresh display. |
* 3- Add One Second * | 3 | Add one second to both clocks; refresh display. |
* 4- Exit Program * | 4 | Terminate the program. |
Any other input | N/A | Display error message; re-prompt. |
5.4. Processing User Input: Making Choices Work
Read user's menu choice. Validate input type (is it a number?) and range (1-4).
int choice; std::cin >> choice;
if (std::cin.fail()) { /* ... handle error ... */ }
else if (choice < 1 || choice > 4) { /* ... handle error ... */ }
else { /* Process valid choice */ }
5.5. The Grand Display: Showing Two Clocks Simultaneously
Display clocks side-by-side with asterisk borders.
****************************** ******************************
* 12-Hour Clock * * 24-Hour Clock *
* 03:22:01 PM * * 15:22:01 *
****************************** ******************************
Leading Zeros: Use <iomanip>
: std::setfill('0') << std::setw(2)
.
strftime
from <ctime>
can format time.
//#include
#include
int main() {
time_t t = time(nullptr);
char buffer[100];
strftime(buffer, sizeof(buffer), "Current date and time: %Y-%m-%d %H:%M:%S", localtime(&t));
std::cout << buffer << std::endl;
return 0;
}
Section 6: Polishing Your Code: Professionalism and Best Practices
Meeting functional requirements is just one part of software development...
6.1. Code Modularization: Keeping main()
Lean
main()
should orchestrate. Example functions:
void getInitialTime(int& h, int& m, int& s);
void displayMenu();
int getUserChoice();
void displayClocks(const Clock12& c12, const Clock24& c24);
void processUserChoice(int choice, Clock12& c12, Clock24& c24, bool& running);
6.2. The Art of Comments
Explain the "why." File and function headers are important.
// File Header Example:
// Name: [Your Name]
// Course: CS-210 - Project One
// Date: [Current Date]
// Description: Main application file for the Chada Tech Clocks.
// Function Header Example:
// Processes the user's menu selection and updates clocks.
// choice: The validated integer choice from the user.
// c12: Reference to the 12-hour clock object.
// c24: Reference to the 24-hour clock object.
// running: Reference to the main loop control flag.
void processUserChoice(int choice, Clock12& c12, Clock24& c24, bool& running) { /* ... */ }
6.3. Naming Conventions
Use consistent, meaningful names (PascalCase
, camelCase
, etc.).
- Classes/Structs:
PascalCase
(e.g.,ClockTime
). - Functions/Methods:
camelCase
(e.g.,displayCurrentTime
) orsnake_case
. - Variables:
camelCase
(e.g.,currentUserInput
) orsnake_case
. - Constants:
ALL_CAPS_SNAKE_CASE
(e.g.,SECONDS_IN_MINUTE
).
6.4. "Secure and Efficient C++ Code"
Focus on input validation and straightforward logic.
- Security (Basic): Input Validation (
std::cin.fail()
, etc.). - Efficiency (Basic): Sensible logic, avoid unnecessary computations.
Section 7: Final Checks and Submission
Before submitting your project, it's vital to conduct thorough testing and review your work against the project requirements and rubric. This final polish ensures your application is complete, functional, and meets all expectations for professionalism and code quality.
7.1. Testing Your Application
Test all functional requirements and edge cases (midnight/noon rollovers, invalid inputs).
Initial Time Input: Prompting, acceptance, validation.
Clock Display: Simultaneous, correct 12/24h formats, leading zeros, asterisk borders.
Menu Functionality: Correct display, accepts choices 1-4, handles invalid input.
Time Manipulation (for each menu option - Add Second, Minute, Hour):
Both clocks update.
Correct rollover logic (seconds, minutes, hours, AM/PM). Test edge cases like 11:59:59 AM -> 12:00:00
PM, 23:59:59 -> 00:00:00.
Exit Program: Option 4 terminates cleanly.
7.2. Reviewing Against the Rubric
Self-assess your project against all rubric criteria: Flowchart, Modularization, Execution, Best
Practices.
Follows Flowchart Diagram?
Code Modularization (lean main(), effective functions)?
Code Execution (displays, menu, input response correct)?
Industry Standard Best Practices (comments, naming, readability)?
7.3. Preparing Your Files for Submission
Submit a ZIP file with all .cpp
and .h
files. Ensure file headers are complete.
Use this interactive checklist to ensure you've met all functional requirements. Your progress here is not saved.
Appendix
This appendix provides supplementary information that may be useful for your project, including a quick reference for strftime
format codes and the full C++ example code from the original guidance document.
A.1. Quick Reference: strftime
Format Codes
Specifier | Replaced by | Example |
---|---|---|
%H | Hour (24h) | 14 |
%I | Hour (12h) | 02 |
%M | Minute | 55 |
%S | Second | 02 |
%p | AM/PM | PM |
%r | 12-hour time | 02:55:02 pm |
%T | HH:MM:SS (24h) | 14:55:02 |
%X | Locale's time | 14:55:02 |
The following is the complete C++ code example provided in Appendix A.1 of the original CS-210 Project One Guidance document. Note that this example uses global variables for simplicity and does not fully implement the class-based OOP structure required by the project, but serves as a procedural illustration of the core logic.
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
#include <sstream>
#include <limits>
// Forward declaration for the display function
void displayClocks(int h12, int m12, int s12, bool isPm12, int h24, int m24, int s24);
void displayMenu();
int getValidatedChoice(int min, int max);
// void clearScreen(); // Not fully implemented in example
// Global variables for time
int currentHour12 = 12, currentMinute12 = 0, currentSecond12 = 0;
bool isPM12 = false; // false for AM, true for PM
int currentHour24 = 0, currentMinute24 = 0, currentSecond24 = 0;
// Function to add one second to both clocks
void addSecond() {
// 24-hour clock logic
currentSecond24++;
if (currentSecond24 >= 60) {
currentSecond24 = 0;
currentMinute24++;
if (currentMinute24 >= 60) {
currentMinute24 = 0;
currentHour24++;
if (currentHour24 >= 24) {
currentHour24 = 0;
}
}
}
// 12-hour clock logic
currentSecond12++;
if (currentSecond12 >= 60) {
currentSecond12 = 0;
currentMinute12++;
if (currentMinute12 >= 60) {
currentMinute12 = 0;
currentHour12++;
if (currentHour12 == 12) {
isPM12 = !isPM12;
}
if (currentHour12 > 12) {
currentHour12 = 1;
}
}
}
}
// Function to add one minute to both clocks
void addMinute() {
// 24-hour clock logic
currentMinute24++;
if (currentMinute24 >= 60) {
currentMinute24 = 0;
currentHour24++;
if (currentHour24 >= 24) {
currentHour24 = 0;
}
}
// 12-hour clock logic
currentMinute12++;
if (currentMinute12 >= 60) {
currentMinute12 = 0;
currentHour12++;
if (currentHour12 == 12) {
isPM12 = !isPM12;
}
if (currentHour12 > 12) {
currentHour12 = 1;
}
}
}
// Function to add one hour to both clocks
void addHour() {
// 24-hour clock logic
currentHour24++;
if (currentHour24 >= 24) {
currentHour24 = 0;
}
// 12-hour clock logic
currentHour12++;
if (currentHour12 == 12) {
isPM12 = !isPM12;
}
if (currentHour12 > 12) {
currentHour12 = 1;
}
}
// Helper function to format a number with leading zero if needed
std::string formatTwoDigits(int n) {
std::ostringstream oss;
oss << std::setfill('0') << std::setw(2) << n;
return oss.str();
}
// Function to display both clocks side-by-side
void displayClocks(int h12, int m12, int s12, bool isPm12, int h24, int m24, int s24) {
std::string border = "******************************";
std::string separator = " ";
std::string time12Str = formatTwoDigits(h12) + ":" +
formatTwoDigits(m12) + ":" +
formatTwoDigits(s12) + (isPm12 ? " PM" : " AM");
std::string time24Str = formatTwoDigits(h24) + ":" +
formatTwoDigits(m24) + ":" +
formatTwoDigits(s24);
std::string label12 = "12-Hour Clock";
std::string label24 = "24-Hour Clock";
int p_l12_t = border.length() - 2 - label12.length(); int p_l12_l = p_l12_t / 2; int p_l12_r = p_l12_t - p_l12_l;
int p_l24_t = border.length() - 2 - label24.length(); int p_l24_l = p_l24_t / 2; int p_l24_r = p_l24_t - p_l24_l;
int p_t12_t = border.length() - 2 - time12Str.length(); int p_t12_l = p_t12_t / 2; int p_t12_r = p_t12_t - p_t12_l;
int p_t24_t = border.length() - 2 - time24Str.length(); int p_t24_l = p_t24_t / 2; int p_t24_r = p_t24_t - p_t24_l;
std::cout << border << separator << border << std::endl;
std::cout << "*" << std::string(p_l12_l, ' ') << label12 << std::string(p_l12_r, ' ') << "*"
<< separator
<< "*" << std::string(p_l24_l, ' ') << label24 << std::string(p_l24_r, ' ') << "*" << std::endl;
std::cout << "*" << std::string(p_t12_l, ' ') << time12Str << std::string(p_t12_r, ' ') << "*"
<< separator
<< "*" << std::string(p_t24_l, ' ') << time24Str << std::string(p_t24_r, ' ') << "*" << std::endl;
std::cout << border << separator << border << std::endl;
}
// Function to display the user menu
void displayMenu() {
std::cout << "\n******************************\n";
std::cout << "* 1- Add One Hour *\n";
std::cout << "* 2- Add One Minute *\n";
std::cout << "* 3- Add One Second *\n";
std::cout << "* 4- Exit Program *\n";
std::cout << "******************************\n";
}
int getValidatedChoice(int min, int max) {
int choice;
while (true) {
std::cout << "Enter your choice: ";
std::cin >> choice;
if (std::cin.good() && choice >= min && choice <= max) {
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return choice;
} else {
std::cout << "Invalid input. Please enter a number between " << min << " and " << max << "." << std::endl;
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
}
// int main() { /* ... */ } // Original example main commented out for brevity