Search code examples
javacalendarcoding-stylejava-calendar

Print 12 Month Year and the next 2 Years using Java


Already had an initial program for this one, I made the code as much shorter and precise as possible. But upon checking it seems that the next 2 years still prints 2018 and I am expecting 2019 and 2020. How can I possible dynamically prints the year when it shows 2018, 2019, and 2020. might be I am missing some iterations in my code.

Also feel free to criticize my code, you can also suggest more shorter codes by using the Calendar API or Java 8 utilities as much as you can.

See code below:

package calendarjava;

import java.util.Calendar;
import java.util.Scanner;
import java.util.GregorianCalendar;
import java.util.Locale;


public class CalendarJava {

   public static void main(String[] args) {

      Scanner sc = new Scanner(System.in);
      System.out.print("Enter a year: ");
      int year = sc.nextInt();

      Calendar cal = new GregorianCalendar();

      int startDay;
      int numberOfDays;
      for (int i=0; i<36; i++){
        cal.set(year, i, 1);
        startDay = cal.get(Calendar.DAY_OF_WEEK);
        numberOfDays = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
        System.out.print(cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.US));
        System.out.println( " " + year);
        printMonth(numberOfDays,startDay);
        System.out.println();
      }
   }

   private static void printMonth(int numberOfDays, int startDay) {

      int weekdayIndex = 0;
      System.out.println("Su  Mo  Tu  We  Th  Fr  Sa");

      for (int day = 1; day < startDay; day++) {
         System.out.print("    ");
         weekdayIndex++;
      }

      for (int day = 1; day <= numberOfDays; day++) {
         System.out.printf("%1$2d", day);
         weekdayIndex++;
         if (weekdayIndex == 7) {
            weekdayIndex = 0;
            System.out.println();
         } else { 
            System.out.print("  ");
         }
      }
      System.out.println();
   }
}
Enter a year: 2018
January 2018
Su  Mo  Tu  We  Th  Fr  Sa
     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  

February 2018
Su  Mo  Tu  We  Th  Fr  Sa
                 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  

March 2018
Su  Mo  Tu  We  Th  Fr  Sa
                 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


April 2018
Su  Mo  Tu  We  Th  Fr  Sa
 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  

May 2018
Su  Mo  Tu  We  Th  Fr  Sa
         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  

June 2018
Su  Mo  Tu  We  Th  Fr  Sa
                     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


July 2018
Su  Mo  Tu  We  Th  Fr  Sa
 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  

August 2018
Su  Mo  Tu  We  Th  Fr  Sa
             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  

September 2018
Su  Mo  Tu  We  Th  Fr  Sa
                         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  

October 2018
Su  Mo  Tu  We  Th  Fr  Sa
     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  

November 2018
Su  Mo  Tu  We  Th  Fr  Sa
                 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  

December 2018
Su  Mo  Tu  We  Th  Fr  Sa
                         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  

January 2018
Su  Mo  Tu  We  Th  Fr  Sa
         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  

February 2018
Su  Mo  Tu  We  Th  Fr  Sa
                     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  

March 2018
Su  Mo  Tu  We  Th  Fr  Sa
                     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  

April 2018
Su  Mo  Tu  We  Th  Fr  Sa
     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  

May 2018
Su  Mo  Tu  We  Th  Fr  Sa
             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  

June 2018
Su  Mo  Tu  We  Th  Fr  Sa
                         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  

July 2018
Su  Mo  Tu  We  Th  Fr  Sa
     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  

August 2018
Su  Mo  Tu  We  Th  Fr  Sa
                 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


September 2018
Su  Mo  Tu  We  Th  Fr  Sa
 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  

October 2018
Su  Mo  Tu  We  Th  Fr  Sa
         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  

November 2018
Su  Mo  Tu  We  Th  Fr  Sa
                     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


December 2018
Su  Mo  Tu  We  Th  Fr  Sa
 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  

January 2018
Su  Mo  Tu  We  Th  Fr  Sa
             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  

February 2018
Su  Mo  Tu  We  Th  Fr  Sa
                         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


March 2018
Su  Mo  Tu  We  Th  Fr  Sa
 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  

April 2018
Su  Mo  Tu  We  Th  Fr  Sa
             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  

May 2018
Su  Mo  Tu  We  Th  Fr  Sa
                     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  

June 2018
Su  Mo  Tu  We  Th  Fr  Sa
     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  

July 2018
Su  Mo  Tu  We  Th  Fr  Sa
             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  

August 2018
Su  Mo  Tu  We  Th  Fr  Sa
                         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  

September 2018
Su  Mo  Tu  We  Th  Fr  Sa
         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  

October 2018
Su  Mo  Tu  We  Th  Fr  Sa
                 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


November 2018
Su  Mo  Tu  We  Th  Fr  Sa
 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  

December 2018
Su  Mo  Tu  We  Th  Fr  Sa
         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

Solution

  • Modern Java

    Here is an entirely different take, for your comparison. This code uses the features of modern Java including the java.time classes defined in JSR 310, streams, convenient List factory method, and enums.

    All of this code in contained in a single .java file, to define our CalendarMaker class. See the main method as a demo for how to make use of this class.

    The class has two member variables you inject via the constructor: the end-of-line (newline) character(s) to use in our resulting text, and the Locale by which we (a) determine the order of the days-of-week, and (b) localize the name of the month and the name of the day-of-week.

    We use StringBuilder class to build up our text by calling its append method.

    Use specific types whenever possible, to make your code more self-documenting, ensure valid values, and provide type-safety. So we start with a list of Year objects, for the current year along with previous and following years.

    For each year, we loop the months. Each month is represented as a YearMonth object. We localize the name of the month by calling Month.getDisplayName. Then we localize the day-of-week column headers by first localizing the name of the day-of-week, and then truncating to take only the first two letters.

    The DayOfWeek enum provides ready-made objects to represent each day-of-week.

    Note that we also localize the order of the days in the week. In the US, Sunday starts the week on most calendars. But in Europe and elsewhere you will often see Monday first. Any day-of-week is tolerated by our code to start the week, in case there are other choices by some cultural norms.

    A TemporalAdjuster found in the TemporalAdjusters class determines a date for the starting date in our monthly grid. Then we increment day by day, in weekly chunks. Optionally, we suppress the display of dates that lay outside our target month.

    To generate the text for each day number, use a DateTimeFormatter. Use a formatting pattern of dd to pad single-digit numbers with a zero. To pad with a SPACE, use ppd.

    Update: I replaced the for loop in this block of code with a stream from LocalDate.datesUntil. Inside we use a ternary operator to suppress the dates outside our target month. I am not saying this rewrite is necessarily better; I just want to show off the spiffy syntax with stream & lambda as an example of modern Java programming.

        // Rows (each week)
        LocalDate localDate = yearMonth.atDay( 1 ).with( TemporalAdjusters.previousOrSame( firstDayOfWeek ) );
        while ( ! localDate.isAfter( yearMonth.atEndOfMonth() ) )  // "Not after" is a shorter way of saying "is equal to or sooner than".
        {
            for ( int i = 0 ; i < 7 ; i++ )
            {
                // If we want to suppress the out-of-month dates that may exist in first and last rows.
                if ( ! YearMonth.from( localDate ).equals( yearMonth ) )
                {
                    sb.append( "  " );  // Use 2 spaces rather than 2 digits of day-of-month number.
                } else  // Else the date is inside our target year-month.
                {
                    sb.append( localDate.format( CalendarMaker.DAY_FORMATTER ) );
                }
                if ( i < 6 )
                {
                    sb.append( " " ); // Pad with a SPACE between columns.
                }
                localDate = localDate.plusDays( 1 );  // Increment one day at a time.
            }
            sb.append( this.eol );
        }
    

    …became:

        // Rows (each week)
        LocalDate localDate = yearMonth.atDay( 1 ).with( TemporalAdjusters.previousOrSame( firstDayOfWeek ) ); // Get the first date of the month, then move backwards in time to determine the first date that fits our calendar grid. May be the same as the first, or may be earlier date from the previous month.
        while ( ! localDate.isAfter( yearMonth.atEndOfMonth() ) )  // "Not after" is a shorter way of saying "is equal to or sooner than".
        {
            String week =
                    localDate
                            .datesUntil( localDate.plusWeeks( 1 ) )  // Get a stream of dates via `LocalDate::datesUntil`. The ending date is exclusive (half-open).
                            .map( ld -> ( YearMonth.from( ld ).equals( yearMonth ) ? ld.format( CalendarMaker.DAY_FORMATTER ) : "  " ) ) // Display the day-of-month number if within the target month, otherwise display a pair of SPACE characters.
                            .collect( Collectors.joining( " " ) );  // Separate columns with a SPACE in our calendar grid.
            sb.append( week ).append( this.eol ); // Add this row of text for the week, and wrap to next line for next loop.
            localDate = localDate.plusWeeks( 1 );  // Increment one week at a time to set up our next loop.
        }
    

    CalendarMaker.java

    package work.basil.example;
    
    import java.time.*;
    import java.time.format.DateTimeFormatter;
    import java.time.format.TextStyle;
    import java.time.temporal.TemporalAdjusters;
    import java.time.temporal.WeekFields;
    import java.util.EnumSet;
    import java.util.List;
    import java.util.Locale;
    import java.util.stream.Collectors;
    import java.util.stream.IntStream;
    
    public class CalendarMaker
    {
        // Member variables.
        private String eol;
        private Locale locale;
        static private DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern( "ppd" ); // Use `dd` to pad single-digits values with a leading zero. Use `ppd` to pad with a SPACE.
    
        // Constructor
    
        public CalendarMaker ( String eol , Locale locale )
        {
            this.eol = eol;
            this.locale = locale;
        }
    
        private CharSequence generateYear ( final Year year )
        {
            // Year header.
            StringBuilder sb = new StringBuilder();
            sb.append( "|------  " + year + "  ------|" ).append( this.eol ).append( this.eol );
    
            // Each month.
            for ( Month month : EnumSet.allOf( Month.class ) )
            {
                YearMonth ym = YearMonth.of( year.getValue() , month );
                CharSequence monthCalendar = this.generateMonth( ym );
                sb.append( monthCalendar );
            }
            return sb;
        }
    
        private CharSequence generateMonth ( final YearMonth yearMonth )
        {
            // Title
            StringBuilder sb = new StringBuilder();
            String monthName = yearMonth.getMonth().getDisplayName( TextStyle.FULL , this.locale );
            sb.append( yearMonth.getYear() ).append( " " ).append( monthName ).append( this.eol );
    
            // Column headers.
            DayOfWeek firstDayOfWeek = WeekFields.of( this.locale ).getFirstDayOfWeek();
            List < DayOfWeek > dows =
                    IntStream
                            .range( 0 , 7 )
                            .mapToObj( firstDayOfWeek :: plus )
                            .collect( Collectors.toList() );
            String columnHeaders =
                    dows
                            .stream()
                            .map( dayOfWeek -> dayOfWeek.getDisplayName( TextStyle.SHORT_STANDALONE , this.locale ).substring( 0 , 2 ) )
                            .collect( Collectors.joining( " " ) );
            sb.append( columnHeaders ).append( this.eol );
    
            // Rows (each week)
            LocalDate localDate = yearMonth.atDay( 1 ).with( TemporalAdjusters.previousOrSame( firstDayOfWeek ) ); // Get the first date of the month, then move backwards in time to determine the first date that fits our calendar grid. May be the same as the first, or may be earlier date from the previous month.
            while ( ! localDate.isAfter( yearMonth.atEndOfMonth() ) )  // "Not after" is a shorter way of saying "is equal to or sooner than".
            {
                String week =
                        localDate
                                .datesUntil( localDate.plusWeeks( 1 ) )  // Get a stream of dates via `LocalDate::datesUntil`. The ending date is exclusive (half-open).
                                .map( ld -> ( YearMonth.from( ld ).equals( yearMonth ) ? ld.format( CalendarMaker.DAY_FORMATTER ) : "  " ) ) // Display the day-of-month number if within the target month, otherwise display a pair of SPACE characters.
                                .collect( Collectors.joining( " " ) );  // Separate columns with a SPACE in our calendar grid.
                sb.append( week ).append( this.eol ); // Add this row of text for the week, and wrap to next line for next loop.
                localDate = localDate.plusWeeks( 1 );  // Increment one week at a time to set up our next loop.
            }
    
            // Footer (for the month)
            sb.append( this.eol );  // Put a blank line after every month.
            return sb;
        }
    
        // Demonstrate this class with a psvm method.
        public static void main ( String[] args )
        {
    
            CalendarMaker calendarMaker = new CalendarMaker( "\n" , Locale.CANADA_FRENCH );
    
            // Demonstrate 3 years: previous year, current, and next year.
            Year currentYear = Year.now( ZoneId.of( "America/Boise" ) );
            List < Year > years = List.of( currentYear.minusYears( 1 ) , currentYear , currentYear.plusYears( 1 ) );
            for ( Year year : years )
            {
                CharSequence calendar = calendarMaker.generateYear( year );
                System.out.println( "" );
                System.out.println( calendar );
            }
        }
    
    }
    

    When run.

    |------  2018  ------|
    
    2018 janvier
    di lu ma me je ve sa
        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         
    
    2018 février
    di lu ma me je ve sa
                 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      
    …
    

    Switch the locale from Locale.CANADA_FRENCH to Locale.FRANCE to see how we keep the French language but switch cultural norms from North American to European to start the week with Monday (lundi) rather than Sunday (dimanche).