p2-birthdays

EECS 183 Project 2: Birthdays

Project Due Wednesday, October 7, 2020, 11:59 pm Eastern

You will write an application to identify the day of the week on which you were born.

Along the way, you will write functions that compute whether a year is a leap year and which day of the week (e.g. Monday, Tuesday) a particular date falls on.

This project is significantly more difficult than Project 1 and you can expect it to take 2 to 3 times longer to complete.

By completing this project, you will learn to:

You will apply the following skills you learned in lecture:

Getting Started

Starter Files

You can download the starter files using this link.

The IDE setup tutorials for Visual Studio and XCode include a video about how to set up a project using the starter files. You can access the tutorals here:

Make sure there are 3 files in your project: birthdays.cpp, test.cpp, and start.cpp.

Submission and Grading

Submit your code to the autograder here. You receive 10 submits each day and your best overall submission counts as your score. You can find where the files you need to submit are on your computer using these steps from Project 1. You will submit two files, which must be called birthdays.cpp and test.cpp.

Here is a grade breakdown:

The deadline is Wednesday, October 7, 2020 at 11:59PM Eastern. If your last submission is on Monday, October 5 by 11:59PM, you will receive a 5% bonus. If your last submission is on Tuesday, October 6 by 11:59PM, you will receive a 2.5% bonus.

You have 3 late days that you can use any time during the semester for projects. There are 3 late days total, not 3 per project. To use a late day, submit to the autograder after the deadline. It will prompt you about using one of your late day tokens. There are more details about late days in the syllabus.

Understanding the Distribution Code

birthdays.cpp: Starter code for the application you will write in this project. Holds the definitions of required functions and the implementations of a couple functions. We have stubbed all required functions for you.

test.cpp: Testing functions for your birthdays.cpp implementation. Holds the definitions of required testing functions. We have stubbed all required functions for you.

start.cpp: This file contains the main() function for your program, which allows you to run either the birthdays application or your test suite. You do not need to modify this file or submit it to the autograder.

Stubbing functions means adding the minimal necessary code to make a function compile. For example, some of the functions in birthdays.cpp have return types of bool. We have added return false in those functions so that they will compile even if you have not implemented all the functions yet. Be sure to remove our stubs when you write your own implementation of the function.

Testing Your Setup

Once you have created a project, you should be able to compile and run the distribution code. We have included a main() function in start.cpp which will allow you to run either your tests or the birthdays application.

EECS 183 Project 2 Menu Options
-------------------------------
1) Execute testing functions in test.cpp
2) Execute birthdays() function in birthdays.cpp
Choice --> 1
Now testing function isGregorianDate()
9/2/2019:  Expected: 1, Actual: 0
1/31/1001: Expected: 0, Actual: 0
...

The first test case currently fails because the isGregorianDate() function is not fully implemented yet.

How to get help

Most students in EECS 183 need help from staff and faculty multiple times each project. We’re here for you! Many more people need help with Project 2 than with Project 1.

If your question is about the specification or about something about the project in general, Piazza is the fastest place to get help. You can also ask about your particular code, but in a private post.

You can get 1-1 help over a video call by signing up for office hours at eecsoh.org. You can find instructions here.

Collaboration Policy

We want students to learn from and with each other, and we encourage you to collaborate. We also want to encourage you to reach out and get help when you need it. You are encouraged to:

To clarify the last item, you are permitted to look at another student’s code to help them understand what is going on with their code. You are not allowed to tell them what to write for their code, and you are not allowed to copy their work to use in your own solution. If you are at all unsure whether your collaboration is allowed, please contact the course staff via the admin form before you do anything. We will help you determine if what you’re thinking of doing is in the spirit of collaboration for EECS 183.

The following are considered Honor Code violations:

The full collaboration policy can be found in the syllabus.

Problem Statement

In this project, you will develop an application to calculate information regarding specific dates in the past and future, which will allow you to find out what day any given birthday was on.

Your program will provide a menu, implemented using a loop, to obtain user input and calculate birthdays.

Here is an example of what the execution of your final application will look like:

EECS 183 Project 2 Menu Options
-------------------------------
1) Execute testing functions in test.cpp
2) Execute birthdays() function in birthdays.cpp
Choice --> 2
*******************************
      Birthday Calculator
*******************************


Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 1

Enter your date of birth
format: month / day / year  --> 1 / 25 / 2000

You were born on a: Tuesday

Have a great birthday!!!

Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 3

****************************************************
      Thanks for using the Birthday Calculator
****************************************************

Development Cycle with Functions

In Project 1, you had to divide the program into pieces so you could test each part individually. In Project 2 and later, the program is already divided into functions in the starter code which you can use as the parts to work on. Functions make it much easier to test your code than it was for Project 1. In this section, we will walk through how to write your program one function at a time by writing tests first.

The functions in a program call each other, and it is easiest to start with the functions that do not call any other functions. For example, in this project, print10LeapYears() will call isLeapYear(), so it makes sense to complete isLeapYear() before print10LeapYears(). We will be able to test isLeapYear() before we write the code that actually uses it in our program. The order you write the functions will be different than the order they appear in birthdays.cpp.

We will follow very similar steps to Project 1 to write the isLeapYear() function.

Step 1: Make examples of input and output

There are two places to look for information about each function: first this specification, and second the RME attached to the function declaration. The specification is useful for a high-level overview, and the RME is useful for the very specific details. Here is a link to the section in this specification about isLeapYear(). Here is its RME from birthdays.cpp:

/**
 * Requires: year is a Gregorian year
 * Modifies: nothing
 * Effects: returns 'true' if the year is a leap year
 *          otherwise returns 'false'
 */
bool isLeapYear(int year);

In this case, we are able to find:

In the specificationIn the RME
The definition of a leap yearWhat assumptions you can make about the inputs (that year will always be Gregorian)
Examples of leap years and non-leap yearsWhat the input and output types of the function are

Like we did for Project 1, we’ll a table of example inputs and outputs. The examples from the spec are already filled in for you:

Input (int)Output (bool)
1768true
1800false
2000true
[more examples you think of]
 
 

Can 1740 be a valid input to isLeapYear()? The answer is no, because the REQUIRES clause states that year must be a Gregorian year (see isGregorianDate() for details about Gregorian years). That means that we do not need to check inside isLeapYear() whether year is Gregorian or not – we can make the assumption the programmer will never give an invalid year as input. There is more discussion about these kinds of assumptions you can make in the Invalid vs. Bad Input section later in the specification.

Step 2: Write Test Cases

In your IDE, pull up the test.cpp file. We have provided a test_isLeapYear() function that you can use to write your tests inside. Here’s an example of how to do it. Your goal is not just to call the function, but to use cout statements that will give you proof the function works properly.

void test_isLeapYear()
{
   cout << "Now testing function isLeapYear()" << endl;

   // Remember that true becomes 1 and false becomes 0
   // when you send it to cout
   cout << "1768: Expected 1, Actual: " << isLeapYear(1768) << endl;
   cout << "1800: Expected 0, Actual: " << isLeapYear(1800) << endl;
   // continue writing tests in this style 
}

Don’t forget to add a call to test_isLeapYear() in the startTests() function at the top of tests.cpp!

The important parts of these test cases are that they:

  1. Tell you which input is being tested
  2. Show you which input you expect
  3. Show you the actual output of the function A test case that does not include all of these parts will not help you find problems in your code.

At this point, you should run your tests. It sounds silly, because you know that they will fail. However, this will help you verify that your tests are actually run and that they do not have compiler errors.

Output of test cases with stub

Step 3: Write Code

Now, in birthdays.cpp, implement the isLeapYear() function. You can use your tests to help you discover the pattern for the algorithm. The tests let you check your work as you go.

For example, this buggy solution:

bool isLeapYear(int year) {
   return (year % 4) == 0;
}

gives this output for the test cases:

Output of test cases with buggy algorithm

Because of this, we know that the problem has to do with something that pertains to 1800 but not 1768, which will help us find the bug.

Step 4: Refine your tests

A significant part of your grade for Project 2 comes from your test cases. We will run your tests against buggy code, and your tests will need to be able to find the bugs. As a rule of thumb, about 10 test cases is often the right amount to find all the bugs in many of the Project 2 functions.

Do not write tests that break the REQUIRES clause of the function you are testing. This can cause you to lose points for your tests. As examples, you should not use 1740 as a test input for isLeapYear(), and you should not use May 132, 1700 as an input for determineDay().

Implementation

Overview

Although you will begin work on your project by implementing individual functions, here is how you can expect to string together those functions into your overall program at the end:

There are many functions that assist in making all of this happen, which you can find in birthdays.cpp. You will need to implement the following functions.

All of these functions have RMEs in birthdays.cpp with additional details.

getMenuChoice()

This function will print a menu and return the user’s selection. You cannot depend upon users to input a number within range, so if the user enters a menu option other than 1, 2, or 3 you need to:

  1. Print Invalid menu choice
  2. Re-print the menu, and
  3. Get another menu choice

Repeat this until a valid menu choice is entered.

isGregorianDate()

isGregorianDate() tests whether a date falls within the “Gregorian calendar”. The modern rules for dates and leap years started when the “Gregorian calendar” was adopted. Although the Gregorian calendar came into effect in the 1500s, later adjustments to the calendar mean that in this project, any date that falls on or after September 14, 1752 is a Gregorian date, and any date on September 13, 1752 or earlier is not.

For example: 9 / 14 / 1752 and 1 / 10 / 1978 are valid Gregorian dates, while 9 / 10 / 1752, 9 / 13 / 1752 and 1 / 10 / 1751 are not.

You do not need to check in this function whether the month and day represent a valid day of the year.

isLeapYear()

isLeapYear() computes whether or not a particular year is a leap year. In the Gregorian calendar, every year evenly divisible by 4 is a leap year, with the exception of the following conditions:

For example: 1768 is a leap year. 1800 is not a leap year. 2000 is a leap year.

isValidDate()

isValidDate() verifies that a date is valid, according to the following definition:

  1. A valid date is a Gregorian date, according to isGregorianDate().
  2. day is a valid day in the given month. For example, if month is 4 (April), day must be between 1 and 30 inclusive. Note that the number of days in February depends on whether the given year is a leap year.
MonthDaysMonthDays
January31July31
February29 if leap year, 28 otherwiseAugust31
March31September30
April30October31
May31November30
June30December31

Here are some examples of invalid user input to get you started.

In all of these cases, isValidDate() should return false.

All dates are entered MONTH / DAY / year and not DAY / MONTH / year. 9 / 12 / 2000 is the 12th of September and not the 9th of December.

determineDay()

determineDay() will compute the day of the week on which a date occurs using Zeller’s Rule. Using the month, day and year of a date, Zeller’s Rule computes the day of the week on which that date occurred/will occur.

For example:

Note that none of the constants in Zeller’s Formula count as “magic numbers” for the purposes of style grading so long there is a comment explaining that the section of code implements Zeller’s Formula.

Calendar Adjustments

Zeller’s Rule uses a calendar year beginning in March. To account for this, we count March as month 3, and January and February as months 13 and 14 of the previous year. The table below marks conversions.

Our calendar Zeller’s calendar
1/1/2015 13/1/2014
2/1/2015 14/1/2014
3/1/2015 3/1/2015
4/1/2015 4/1/2015
5/1/2015 5/1/2015
6/1/2015 6/1/2015
7/1/2015 7/1/2015
8/1/2015 8/1/2015
9/1/2015 9/1/2015
10/1/2015 10/1/2015
11/1/2015 11/1/2015
12/1/2015 12/1/2015

Converting to day of week

Zeller’s rule will return a number f between 0 and 6. This zero-indexed number will correlate to a day of the week in the following manner:

f Day of the week
0 Saturday
1 Sunday
2 Monday
3 Tuesday
4 Wednesday
5 Thursday
6 Friday

Example Calculations

A detailed example of Zeller’s Formula is included below.

Date: January 29, 2064 (i.e., 1 / 29 / 2064)






3 corresponds to a Tuesday, so January 29, 2064 will be a Tuesday.

printDayOfBirth()

printDayOfBirth() prints to cout the day of the week corresponding to an f computed using Zeller’s Formula.

determineDayOfBirth()

determineDayOfBirth() reads a date from the user and prints the day of the week corresponding to that date. This function will:

  1. Prompt for a date
  2. Check whether that date is valid
    1. If the date isn’t valid, it will print the error message Invalid date.
    2. If it is, it will
      1. print the day of the week you were born on.
      2. tell you to Have a great birthday!!!

This function will contain the following prompts, which print depending on program’s execution:

Enter your date of birth
format: month / day / year  -->
Invalid date
You were born on a:
Have a great birthday!!!

Make sure you use these exact prompts, letter-for-letter.

This function will call the following functions:

You can assume dates will always be entered with spaces around the /. For example, we may test the input 12 / 20 / 1980 on the autograder, but we will never test 12/20/1980.

Even if the user enters a birthday that has not yet taken place, your program must still use the past tense You were born on a: .

print10LeapYears()

print10LeapYears() prompts the user for a Gregorian year and prints the first 10 leap years occurring after (not including) the input year. If the year is invalid, it prints nothing. The first Gregorian year was 1752.

This function will contain the following prompts, which print depending on program’s execution:

Enter year -->
Leap year is

Putting it Together

You will need to reference both this specification and the RMEs when implementing these functions.

Once you have written and tested each of the above functions, it is time to combine everything in birthdays() and do further testing with your new debugging skills. Be sure that your program behaves as illustrated in the Sample Output.

birthdays() will call the following function(s):

Testing

As part of this project, you will also submit code in test.cpp that tests the functions you implement in birthdays.cpp. The autograder will run your tests against buggy programs in order to see if your tests can expose the bugs. Because you will not know the exact nature of the bugs on the autograder, you will need to build comprehensive tests.

As a rule of thumb, every function needs at least 5-10 test cases to find a reasonable set of bugs.

Invalid vs. Bad Input

You will testing your program and functions for invalid input rather than bad input. Invalid input has the correct form but invalid values. Bad input has an incorrect form. For example, for the determineDayOfBirth() function, here are examples of invalid input and bad input:

Invalid (test these) Bad (don’t test)
12 / 40 / 2000 1/1/2019
40 / 10 / 2104 January / 10 / 2104
0 / 1 / 1910 1 1 2019
1 / 1 / 1200 asdoiw8032hfg80r

The specification and RMEs for a function will define invalid input for a specific function. Something that breaks the REQUIRES clause is always bad input, and should not be tested.

getMenuChoice()

getMenuChoice() is a good function to test early, since you do not need any other functions implemented for it to work. To test getMenuChoice(), you need to check it against good input and invalid input.

We have provided two initial test cases for getMenuChoice(). Because it reads from cin, you need to type input as part of the test.

isValidDate()

Because isValidDate() does not read from cin, it can be tested automatically, without any user input. To test isValidDate(), write your own tests inside the function called test_isValidDate(). Here are some ideas for test cases:

Be sure to call your test_isValidDate() function within runTests().

isLeapYear()

Testing isLeapYear() will be similar to isValidDate(). Note, however, that isLeapYear() incudes “year must be a Gregorian year” in its REQUIRES clause. All your tests must respect this REQUIRES clause. This means that even though it is legal C++ to write the following test, it should not be used as a test case:

// invalid test case because 1750 is not a Gregorian year
cout << "1750: Expected: 1, actual: " << isLeapYear(1750) << endl;

Other Functions

Test functions for the other functions in birthday.cpp have been stubbed for you in test.cpp. These functions will be tested in similar ways to getMenuChoice(), isValidDate(), and isLeapYear().

Bugs To Expose

After you submit your test suite to the autograder, you might see output that looks like this:

That means that your test suite exposed 5 out of 8 bugs in the staff’s “buggy” implementations of birthdays.cpp and your score for the test suite is 7.5 out of 10 points.

There are a total of 8 unique bugs to find in our implementations. Your tests do not need to expose all of the bugs to receive full points for the project. The autograder will tell you the names of the bugs that you have exposed, from the following set:

Sample Output

Here are a few examples of the way your program output should look, where red underlined text represents some user’s input. Make sure to use a diffchecker when comparing your program’s output to these runs.

Sample Run 1

-------------------------------
EECS 183 Project 2 Menu Options
-------------------------------
1) Execute testing functions in test.cpp
2) Execute birthdays() function in birthdays.cpp
Choice --> 2
*******************************
      Birthday Calculator      
*******************************


Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 1

Enter your date of birth
format: month / day / year  --> 9 / 31 / 1980

Invalid date

Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 1

Enter your date of birth
format: month / day / year  --> 1 / 25 / 1956

You were born on a: Wednesday

Have a great birthday!!!

Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 3

****************************************************
      Thanks for using the Birthday Calculator      
****************************************************

Sample Run 2

-------------------------------
EECS 183 Project 2 Menu Options
-------------------------------
1) Execute testing functions in test.cpp
2) Execute birthdays() function in birthdays.cpp
Choice --> 2
*******************************
      Birthday Calculator      
*******************************


Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 5

Invalid menu choice

Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 3

****************************************************
      Thanks for using the Birthday Calculator      
****************************************************

Sample Run 3

-------------------------------
EECS 183 Project 2 Menu Options
-------------------------------
1) Execute testing functions in test.cpp
2) Execute birthdays() function in birthdays.cpp
Choice --> 2
*******************************
      Birthday Calculator      
*******************************


Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 1

Enter your date of birth
format: month / day / year  --> 9 / 13 / 1752

Invalid date

Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 1

Enter your date of birth
format: month / day / year  --> 19 / 13 / 1982

Invalid date

Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 1

Enter your date of birth
format: month / day / year  --> 9 / 13 / 1982

You were born on a: Monday

Have a great birthday!!!

Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 3

****************************************************
      Thanks for using the Birthday Calculator      
****************************************************

Sample Run 4

-------------------------------
EECS 183 Project 2 Menu Options
-------------------------------
1) Execute testing functions in test.cpp
2) Execute birthdays() function in birthdays.cpp
Choice --> 2
*******************************
      Birthday Calculator      
*******************************


Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 1

Enter your date of birth
format: month / day / year  --> 9 / 24 / 1980

You were born on a: Wednesday

Have a great birthday!!!

Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 5

Invalid menu choice

Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> -2

Invalid menu choice

Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 2

Enter year --> 1972

Leap year is 1976
Leap year is 1980
Leap year is 1984
Leap year is 1988
Leap year is 1992
Leap year is 1996
Leap year is 2000
Leap year is 2004
Leap year is 2008
Leap year is 2012

Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 3

****************************************************
      Thanks for using the Birthday Calculator      
****************************************************

Sample Run 5

-------------------------------
EECS 183 Project 2 Menu Options
-------------------------------
1) Execute testing functions in test.cpp
2) Execute birthdays() function in birthdays.cpp
Choice --> 2
*******************************
      Birthday Calculator      
*******************************


Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 2

Enter year --> 1842

Leap year is 1844
Leap year is 1848
Leap year is 1852
Leap year is 1856
Leap year is 1860
Leap year is 1864
Leap year is 1868
Leap year is 1872
Leap year is 1876
Leap year is 1880

Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 2

Enter year --> 1600


Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 2

Enter year --> 2013

Leap year is 2016
Leap year is 2020
Leap year is 2024
Leap year is 2028
Leap year is 2032
Leap year is 2036
Leap year is 2040
Leap year is 2044
Leap year is 2048
Leap year is 2052

Menu Options
------------
1) Determine day of birth
2) Print the next 10 leap years
3) Finished

Choice --> 3

****************************************************
      Thanks for using the Birthday Calculator      
****************************************************

Style

Your code must follow the EECS 183 style guide. Keep in mind that only birthdays.cpp is graded for style. Your test.cpp will not be style graded in any way.

Style Rubric

Top Comment

Must have name, uniqname, program name, and project description at the top of the file.

If all or part of the top comment is missing, take 1 point off.

Readability

-1 point for each of the following categories:

Indentations

Spacing

Bracing

Variables

Line Limit and Statements

Comments

RMEs

bools

Coding Quality

-2 for each of the following categories:

Global Variables

Magic Numbers

Egregious code

Function Misuse

Checklist

To maximize your style points, be sure to follow this non-exhaustive checklist:

Optional Warm-Up Questions