Современная Java
Вот для сравнения совершенно другой подход.Этот код использует функции современной Java, включая java.time классы, определенные в JSR 310 , потоки , удобно List
фабричный метод и перечисления .
Весь этот код содержится в одном файле .java
, чтобы определить наш класс CalendarMaker
.Посмотрите метод main
как демонстрацию того, как использовать этот класс.
В классе есть две переменные-члены, которые вы вводите с помощью конструктора: end-of-line (newline) символ (ы) для использования в нашем итоговом тексте и Locale
, с помощью которого мы (а) определяем порядок дней недели и (б) локализуем название месяца и название дня-of-week.
Мы используем класс StringBuilder
для создания нашего текста, вызывая его метод append
.
Используйте по возможности определенные типы, чтобыВаш код больше Самодокументируемый , укажите допустимые значения и обеспечьте type-safety .Итак, мы начнем со списка Year
объектов для текущего года вместе с предыдущими и последующими годами.
Для каждого года мы чередуем месяцы.Каждый месяц представлен как YearMonth
объект.Мы локализуем название месяца по телефону Month.getDisplayName
.Затем мы локализуем заголовки столбцов дня недели, сначала локализуя название дня недели, а затем обрезаем, чтобы получить только первые две буквы.
Перечисление DayOfWeek
предоставляет готовые объекты для представления каждого дня недели.
Обратите внимание, что мы также локализуем порядок дней в неделе,В США воскресенье начинается с недели в большинстве календарей.Но в Европе и в других местах вы часто увидите понедельник первым.Наш код допускает любой день недели для начала недели, если есть какие-либо другие варианты выбора по некоторым культурным нормам.
A TemporalAdjuster
, найденные в TemporalAdjusters
class определяет дату начала в нашей месячной сетке.Затем мы увеличиваем день ото дня, еженедельно.При желании мы отключаем отображение дат, выходящих за пределы целевого месяца.
Чтобы сгенерировать текст для каждого номера дня, используйте DateTimeFormatter
.Используйте шаблон форматирования dd
, чтобы дополнить однозначные числа нулем.Чтобы дополнить пробелом, используйте ppd
.
Обновление: я заменил цикл for
в этом блоке кода потоком из LocalDate.datesUntil
.Внутри мы используем троичный оператор для подавления дат вне нашего целевого месяца.Я не говорю, что это переписать обязательно лучше;Я просто хочу показать элегантный синтаксис с помощью stream & lambda в качестве примера современного программирования на Java.
// 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 );
}
… стало:
// 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 );
}
}
}
При запуске.
|------ 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
…
Переключите язык с Locale.CANADA_FRENCH
на Locale.FRANCE
, чтобы увидеть, как мы сохраняем французский язык, но переключаем культурныйНормы от североамериканских до европейских начинаются с понедельника (лунди), а не с воскресенья (диманче).