7
\$\begingroup\$

I set out trying to use as many methods as possible in order to use them in future projects, but I don't feel like I did a great job in regards to that.

Also i wanted to use as many basic java concepts as possible but I also don't feel i used all that many. any ideas for features i could add that would use more java features and concepts?

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        boolean looper = true;
        while (looper) {
                System.out.println("Hello, this is a calendar. Please type \"Choose month\" to choose a calendar page " +
                    "\n or \"options\" for a list of months, type \"end\" to close program.");
                Scanner menuInput = new Scanner(System.in);
                String menuAnswer = menuInput.nextLine();


if (menuAnswer.equalsIgnoreCase("end")) {
                System.out.println("Shutting down. Thank you for using the calendar");
                looper = false;
            }

else if (menuAnswer.equalsIgnoreCase("Choose month")) {
                System.out.print("Type month: ");
                Scanner monthInput = new Scanner(System.in);
                String pickedMonth = monthInput.nextLine();


    Calendar_methods myCalendar = new Calendar_methods();
    myCalendar.calendarMaker(pickedMonth);
}


else if (menuAnswer.equalsIgnoreCase("options")) {

                Calendar_methods myOptions = new Calendar_methods();
                myOptions.monthList();
                boolean optionsLooper = true;

                while (optionsLooper) {
                    Scanner menuReturn = new Scanner(System.in);
                    String menuReturnAnswer = menuReturn.nextLine();

                    if (menuReturnAnswer.equalsIgnoreCase("no")) {
                        looper = false;
                        optionsLooper = false;
                        System.out.println("Shutting down. Thank you for using the calendar");
                    } else if (menuReturnAnswer.equalsIgnoreCase("yes")) {
                        break;
                    }
                    else{
                    System.out.println("You have entered an invalid option. Please try again.");
                    }
                }
            }


else {
                System.out.println("Invalid option. Please try again.");
            }

        }
    }
}

put my methods into a seperate class, was initally planning more than two methods but not sure how to add anymore of the main code to individual methods.

public class Calendar_methods {


    public void calendarMaker(String month) {
int gridLength = 20;
int gridHeight = 4;
        if (month.equalsIgnoreCase("February (leap year)")) {

        for (int k = 0; k < gridHeight + 1; k++) {
            System.out.print("|");
            for (int i = 1; i < gridLength + 1; i++) {
                System.out.print("-");
if (i%2 == 0 && i != gridLength) System.out.print("|");
            }
            System.out.println("|");

System.out.print(" ");
for (int j = 0; j < (gridLength/2); j++) {
    int weekOfTheMonth = k*10;
    int monthDay = weekOfTheMonth + j;
    if (monthDay < 10) {
        System.out.print("0" + monthDay + " ");
    }
    else if (monthDay > 31) {
        System.out.print("  " + " ");
    }
    else if (monthDay <= 29 && monthDay > 9) {
        System.out.print(monthDay + " ");
    }
}
            System.out.println();
        }


}
else if (month.equalsIgnoreCase("February")){
    for (int k = 0; k < gridHeight + 1; k++) {
    System.out.print("|");
    for (int i = 1; i < gridLength+1; i++) {
        System.out.print("-");
        if (i%2 == 0 && i != gridLength) System.out.print("|");
    }
    System.out.println("|");

    System.out.print(" ");
    for (int j = 0; j < (gridLength/2); j++) {
        int weekOfTheMonth = k*10;
        int monthDay = weekOfTheMonth + j;
        if (monthDay < 10) {
            System.out.print("0" + monthDay + " ");
        }
        else if (monthDay > 31) {
            System.out.print("  " + " ");
        }
        else if (monthDay <= 28 && monthDay > 9) {
            System.out.print(monthDay + " ");
        }
    }
    System.out.println();
}


}
else if (month.equalsIgnoreCase("January") || month.equalsIgnoreCase("march")
         || month.equalsIgnoreCase("may")
         || month.equalsIgnoreCase("july")  || month.equalsIgnoreCase("august")
         || month.equalsIgnoreCase("october") || month.equalsIgnoreCase("december"))
{
    for (int k = 0; k < gridHeight + 1; k++) {
        System.out.print("|");
        for (int i = 1; i < gridLength+1; i++) {
            System.out.print("-");
            if (i%2 == 0 && i != gridLength) System.out.print("|");
        }
        System.out.println("|");

        System.out.print(" ");
        for (int j = 0; j < (gridLength/2); j++) {
            int weekOfTheMonth = k*10;
            int monthDay = weekOfTheMonth + j;
            if (monthDay < 10) {
                System.out.print("0" + monthDay + " ");
            }
            else if (monthDay > 31) {
                System.out.print("  " + " ");
            }
            else if (monthDay <= 31 && monthDay > 9) {
                System.out.print(monthDay + " ");
            }
        }
        System.out.println();
    }


}
else if (month.equalsIgnoreCase("April") || month.equalsIgnoreCase("june")
        || month.equalsIgnoreCase("september") || month.equalsIgnoreCase("november")) {

            for (int k = 0; k < gridHeight + 1; k++) {
                System.out.print("|");
                for (int i = 1; i < gridLength + 1; i++) {
                    System.out.print("-");
                    if (i % 2 == 0 && i != gridLength) System.out.print("|");
                }
                System.out.println("|");

                System.out.print(" ");
                for (int j = 0; j < (gridLength / 2); j++) {
                    int weekOfTheMonth = k * 10;
                    int monthDay = weekOfTheMonth + j;
                    if (monthDay < 10) {
                        System.out.print("0" + monthDay + " ");
                    } else if (monthDay > 30) {
                        System.out.print("  " + " ");
                    } else if (monthDay <= 30 && monthDay > 9) {
                        System.out.print(monthDay + " ");
                    }
                }
                System.out.println();
            }
        }

else {
    System.out.println("Invalid month");
        }
}

    public void monthList(){

        System.out.println("January");
        System.out.println("February");
        System.out.println("February (leap year)");
        System.out.println("March");
        System.out.println("April");
        System.out.println("May");
        System.out.println("June");
        System.out.println("July");
        System.out.println("August");
        System.out.println("September");
        System.out.println("October");
        System.out.println("November");
        System.out.println("December");
        System.out.println();
        System.out.println("Return to menu?");
    }
}
\$\endgroup\$
3
  • \$\begingroup\$ derp, fixed. is it recommended to manually type large sections like that? or is there a way to avoid repeating those chunks in general? \$\endgroup\$ Commented Jun 18 at 10:22
  • \$\begingroup\$ DRY was my intent starting out but since the complicated stuff is already in a method i wasn’t sure how to further condense it. \$\endgroup\$ Commented Jun 18 at 10:29
  • 2
    \$\begingroup\$ You should use an autoformatter (not on this code, since answers have already been posted, but in the future). \$\endgroup\$
    – ggorlen
    Commented Jun 19 at 0:54

3 Answers 3

5
\$\begingroup\$
boolean looper = true;

That variable could have a better name. looper is something that does something, loop or running are states.


Scanner menuInput = new Scanner(System.in);

You're constantly creating new Scanner instances, while it is not a problem in itself here, it's something that should be avoided.


if (menuAnswer.equalsIgnoreCase("end")) {

Your indentation/formatting seems to be off here.


else if (menuAnswer.equalsIgnoreCase("Choose month")) {

While "long options" are nice, typing all that is hard. You could either provider shortcuts, or make a number-based menu to allow fast navigation and usage.


public class Calendar_methods {

The Java Naming Conventions state that classes should be UpperCamelCase. While it is just a guideline, it is a good idea to follow it.

That said, the name of the class could be better.


for (int k = 0; k < gridHeight + 1; k++) {

I know I'm standing (nearly) alone on this hill, but I have the rule "You are only allowed to use single-letter variable names for dimensions (x, y, z, w). for loops are no exception.". Using x, y or column, columnIndex and so forth here would improve readability.


if (month.equalsIgnoreCase("February (leap year)")) {

Again, that's quite long to type.


I did not check your logic, but it seems overly complicated for what you're trying to do. "Complicated" as in "could be achieved simpler" and not in "does a lot". Have a look at the java.time API and its classes and I'm sure you will find easier ways to get all the information you need with less edge-cases in your code.

Additionally, your user interaction is confusing. Why are the months "options"? Why do I get asked whether I'd want to return when having entered them? Why do have to first type that I want to choose a month instead of just typing the month?

Another point is the structure of your program. Ideally you'd separate logic from user interface as much as possible. The first step would be encapsulate the user interaction in methods, something like this (pseudo-code):

private static final void showMessage(String message) {
    System.out.println(message);
}

private static final String askQuestion(String question) {
    System.out.print(question);
    System.out.print(" ");
    
    return scanner.nextLine();
}

With that you can make your main logic much easier to read:

showMessage("Welcome to the Calendar application.");

String month = askQuestion("Please type the month you want to print:");

And so forth.

Coming back to your Calendar_methods class, it's not a good class at all. Classes should be used to group things, states and actions that belong together, in some way. For exmaple an UserInput oder UserInteraction class would be good. Here it would be something like CalendarMaker or CalendarFormatter or MonthFormatter.

public class CalendarFormatter {
    public static final String format(String month) {
        return theFormattedMonthAsString;
    }
}

Here I've choosen a static approach because the class itself must not hold any state (and it is very unlikely that it must be extended). Ideally such a class would format the month and return it as a String to your main logic. So theoretically you could build your main logic like this:

showMessage("Welcome to the Calendar application.");

String month = askQuestion("Please type the month you want to print:");

showMessage(CalendarFormatter.format(month));

The mian logic should comprise what is happening, not how it us happening.

Another good change would be to accept numbers for the months, that would allow the user to easily use your program. Maube greeting them with available input options would also be a nice touch, so that your interaction would look something like this:

Welcome to the calendar formatting application.

Please choose the month to format: 2

|--|--|--|--|--|--|--|--|--|--|
 00 01 02 03 04 05 06 07 08 09 
|--|--|--|--|--|--|--|--|--|--|
 10 11 12 13 14 15 16 17 18 19 
|--|--|--|--|--|--|--|--|--|--|
 20 21 22 23 24 25 26 27 28 
|--|--|--|--|--|--|--|--|--|--|
                         
|--|--|--|--|--|--|--|--|--|--|

Please choose the month to format: q

Exiting.

All that said, you're printing "00" in your calendar, too. And I'm confused by your formatting choice, most of the time you'd see something like this:

     June 2024      
Mo Tu We Th Fr Sa Su
                1  2
 3  4  5  6  7  8  9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

With one week per row and the days of the week as columns.


Another possible way to separate logic from user interaction might be to have the formatter class return an array of Strings of formatted values at the correct locations inside the array. These would then be assembled into the final display form.

\$\endgroup\$
5
\$\begingroup\$

DRY

The presented code has 4 big blocks of code (if/else if/...) whose only difference is the value of a limit; a limit that is duplicated. (Duplication is an open invitation to coders making an incomplete adaptation. That is called "a bug.") (Another difference, as presented here for review, is a problem with indentation that confounds seeing the repetition of code. This may be a "site" problem of the posting of the OP's original coding layout.)

The value of that limit is one of 28, 29, 30 or 31.

Suggest the OP use an appropriate Java construct to assign a variables value once, then use one instance of code to process based on that value. (I do not write Java, so present this as I might write it in C.)

if (
    strcasecmp( month, "april" ) == 0
||  strcasecmp( month, "june" ) == 0
||  strcasecmp( month, "september" ) == 0
||  strcasecmp( month, "november" ) == 0 ) lastday = 30;
else if (strcasecmp( month, "february (ly)" ) == 0 ) lastday = 29;
else if (strcasecmp( month, "february" ) == 0 ) lastday = 28;
/* omitting 31 day month name validation/assignments for brevity */
else { /* error: unrecognised month name entered */ }

Then one block of processing using two altered conditionals:
if (monthDay > 31) becomes if (monthDay > lastday)
and
if (monthDay <= 30 && monthDay > 9) becomes if (monthDay <= lastday && monthDay > 9)

The objective is to write concise code, not to write (copy/paste) lots of lines of code. Don't force your readers to go sifting through verbiage trying to detect insignificant significant differences.


UX

Consider asking/accepting only three letter abbreviations for month names. Your users will thank you for the ease, and the reduction in rejected typos from entering "Febuary" instead of "February"...

\$\endgroup\$
3
\$\begingroup\$

Don't \n in the middle of your prints - use the OS-independent %n newline format in a printf, or just use separate println.

Don't keep recreating Scanners - make only one.

Delete your looper variable and break out of the loop when you're ready; make the loop a forever-loop.

Calendar_methods shouldn't be named such, and shouldn't just be a collection of methods - it should be an object with (immutable) state.

I don't entirely understand why there needs to be a "return-to-menu" feature - this seems to me like it should just be a one-level menu with no sub-menus.

It seems less likely that the user knows the leap status of the year in question, and more likely that the user knows the year in question. Why not just ask for the year and calculate accordingly?

You should not have a fixed grid width of 20. Instead, calculate this based on the number of days per week and the boundary widths.

It's fairly straightforward to support locales, so you should. Locales will tell you the first day of the week and the names of months.

Suggested

Main.java

import java.time.format.DateTimeParseException;
import java.util.Scanner;

public class Main {
    private final Scanner input = new Scanner(System.in);
    private final YearCalendar yearCal = new YearCalendar();

    public static void main(String[] args) {
        new Main().run();
    }

    public void run() {
        System.out.println("Hello, this is a calendar.");
        while (true) {
            System.out.println("Please type \"Choose month\" to choose a calendar page");
            System.out.println(" or \"options\" for a list of months. Type \"end\" to close program.");

            String menuAnswer = input.nextLine().toLowerCase();
            switch (menuAnswer) {
            case "end":
                System.out.println("Shutting down. Thank you for using the calendar");
                return;
            case "choose month":
                chooseMonth();
                break;
            case "options":
                yearCal.print(System.out);
                break;
            default:
                System.out.println("You have entered an invalid option. Please try again.");
            }
        }
    }

    public void chooseMonth() {
        System.out.print("Year: ");
        int year = input.nextInt();
        input.nextLine();

        System.out.print("Month: ");
        String month = input.nextLine();

        try {
            MonthCalendar calendar = new MonthCalendar(year, month);
            calendar.print(System.out);
        } catch (DateTimeParseException e) {
            System.out.println("Invalid date.");
        }
    }
}

MonthCalendar.java

import java.io.PrintStream;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.DateTimeException;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.WeekFields;
import java.util.Locale;


public class MonthCalendar {
    private static final String row = "-".repeat(7*3 + 1);
    private static final String gap = "|  ";

    public final DayOfWeek firstDay;
    public final int year;
    public final String month;
    public final LocalDate baseDate;

    public MonthCalendar(int year, String month) throws DateTimeException {
        this(year, month, Locale.getDefault());
    }

    public MonthCalendar(int year, String month, Locale locale) throws DateTimeException {
        this.year = year;
        this.month = month;

        DateTimeFormatter format = new DateTimeFormatterBuilder()
                .parseCaseInsensitive()
                .appendPattern("yyyy-MMMM-d")
                .toFormatter(locale);
        firstDay = WeekFields.of(locale).getFirstDayOfWeek();

        String baseString = "%d-%s-1".formatted(year, month);
        baseDate = LocalDate.parse(baseString, format);
    }

    public void print(PrintStream out) {
        int weekdayOffset = baseDate.getDayOfWeek().minus(
            firstDay.getValue()
        ).getValue() % 7;

        // Start of week, potentially in the previous month, with weekday based on locale
        LocalDate weekStart = baseDate.minusDays(weekdayOffset);
        LocalDate nextWeek = weekStart.plusDays(7);
        LocalDate nextMonth = baseDate.plusMonths(1).withDayOfMonth(1);

        // First row header, and gaps for days in previous month as needed
        out.println(row);
        weekStart.datesUntil(baseDate).forEach(oldDay ->
            // print previous-month days in a different style, or just spaces like this
            out.print(gap)
        );

        for (
            weekStart = baseDate;
            weekStart.isBefore(nextMonth);
            nextWeek = nextWeek.plusDays(7)
        ) {
            LocalDate weekEnd = nextMonth.isBefore(nextWeek)? nextMonth: nextWeek;

            weekStart.datesUntil(weekEnd).forEach(day ->
                out.printf("|%2d", day.getDayOfMonth())
            );
            weekEnd.datesUntil(nextWeek).forEach(day ->
                out.print(gap)
            );
            out.println('|');
            out.println(row);

            weekStart = nextWeek;
        }
    }
}

YearCalendar.java

import java.io.PrintStream;
import java.text.DateFormatSymbols;
import java.util.Locale;

public class YearCalendar {
    private final DateFormatSymbols symbols;

    public YearCalendar() {
        this(Locale.getDefault());
    }

    public YearCalendar(Locale locale) {
        symbols = new DateFormatSymbols(locale);
    }

    public void print(PrintStream out) {
        String[] names = symbols.getMonths();
        for (String name: names)
            out.println(name);
    }
}

Output

Hello, this is a calendar.
Please type "Choose month" to choose a calendar page
 or "options" for a list of months. Type "end" to close program.
options
January
February
March
April
May
June
July
August
September
October
November
December

Please type "Choose month" to choose a calendar page
 or "options" for a list of months. Type "end" to close program.
choose month
Year: 2023
Month: March
----------------------
|  |  |  | 1| 2| 3| 4|
----------------------
| 5| 6| 7| 8| 9|10|11|
----------------------
|12|13|14|15|16|17|18|
----------------------
|19|20|21|22|23|24|25|
----------------------
|26|27|28|29|30|31|  |
----------------------
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.