使用 java.time/Java-8 根据语言环境从一周的第一天开始打印日历

Printing calendar starting from the first day of the week based on locale using java.time/Java-8

我看到的所有日历视图库和基于日历的日期选择器的示例都使用旧的日历 API。但是,我还没有发现任何使用 Java 8 日期 API 来构建日历视图的方法。这是我正在努力实现并且基本上已经完成的事情,但我 运行 遇到的问题是使用语言环境设置一周的第一天。

我已经设法获取并显示给定月份一周中每一天的所有日期。但是,我遇到的问题是生成的日历不是根据我的语言环境从一周的第一天开始。 Java.Time 使用的 DayOfWeek 枚举从星期一到星期日 (1 - 7) 开始计算天数。但是我希望日历按区域设置显示日期,在我的例子中,星期日到星期六。

根据文档,WeekFields class 提供基于区域设置的星期几的访问权限,具有“正确”的值,但我不确定如何正确使用它。

这是我到目前为止所做的:

private enum class DaysOfWeek {
    SUNDAY,
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY
}

fun TimetableCalendar() {
    val dates = getDaysOfMonth(LocalDate.now().year, LocalDate.now().month)
    
    // WORK AROUND: Hardcoding the first day of the week by manually inserting them into a new map
    val tempDates = hashMapOf<DaysOfWeek, MutableList<LocalDate>>()
    tempDates[DaysOfWeek.SUNDAY] = dates[DayOfWeek.SUNDAY] !!
    tempDates[DaysOfWeek.MONDAY] = dates[DayOfWeek.MONDAY] !!
    tempDates[DaysOfWeek.TUESDAY] = dates[DayOfWeek.TUESDAY] !!
    tempDates[DaysOfWeek.WEDNESDAY] = dates[DayOfWeek.WEDNESDAY] !!
    tempDates[DaysOfWeek.THURSDAY] = dates[DayOfWeek.THURSDAY] !!
    tempDates[DaysOfWeek.FRIDAY] = dates[DayOfWeek.FRIDAY] !!
    tempDates[DaysOfWeek.SATURDAY] = dates[DayOfWeek.SATURDAY] !!

    // Sort the days by ordinal, 0 - 6
    val sortedDates = tempDates.toSortedMap(compareBy {
        d -> d.ordinal
    })

    LazyVerticalGrid(
        cells = GridCells.Fixed(7),
        contentPadding = PaddingValues(16. dp)
    ) {
        // Display short day of week name
        items(sortedDates.keys.toList()) { dayOfWeek ->
                Text(text = dayOfWeek.name.substring(0, 3), textAlign = TextAlign.Center)
        }
        itemsIndexed(sortedDates.values.toList()) { _, date ->
            Column(
                modifier = Modifier.padding(4. dp)
            ) {
                date.forEach { day ->
                        DateView(date = day.dayOfMonth.toString())
                }
            }
        }
    }
}

/**
 * Returns the dates for each day of the week in the given [year] and [month]
 * Including dates of the previous and next month if the [month] does not
 * begin on the first or last day of the week
 */
fun getDaysOfMonth(year: Int, month: Month): HashMap<DayOfWeek, MutableList<LocalDate>> {
    val weekFields = WeekFields.of(Locale.getDefault())

    val daysOfWeek = mutableSetOf<DayOfWeek>()
    daysOfWeek.add(DayOfWeek.SUNDAY)
    daysOfWeek.add(DayOfWeek.MONDAY)
    daysOfWeek.add(DayOfWeek.TUESDAY)
    daysOfWeek.add(DayOfWeek.WEDNESDAY)
    daysOfWeek.add(DayOfWeek.THURSDAY)
    daysOfWeek.add(DayOfWeek.FRIDAY)
    daysOfWeek.add(DayOfWeek.SATURDAY)

    val ym = YearMonth.of(year, month)
    val firstOfMonth = ym.atDay(1) // first day of the month
    val lastDayOfMonth = ym.atDay(ym.lengthOfMonth())

    val dayDates = hashMapOf<DayOfWeek, MutableList<LocalDate>>()

    // Get all the dates for each day of the week
    daysOfWeek.forEach { day ->
            val dates = mutableListOf<LocalDate>()
        var ld = firstOfMonth.with(TemporalAdjusters.dayOfWeekInMonth(1, day))
        do {
            dates.add(ld)
            ld = ld.plusWeeks(1)
        } while (YearMonth.from(ld).equals(ym))
        dayDates[day] = dates
    }

    // If current month does not start on a Sunday, get the last few days of the previous month
    if (firstOfMonth.dayOfWeek != weekFields.firstDayOfWeek) {
        val previousMonth = YearMonth.of(year, month.minus(1))
        var lastDateOfPrevMonth = LocalDate.of(year, previousMonth.month, previousMonth.atEndOfMonth().dayOfMonth)

        do {
            dayDates[lastDateOfPrevMonth.dayOfWeek]?.add(0, lastDateOfPrevMonth)
            lastDateOfPrevMonth = lastDateOfPrevMonth.minusDays(1)
        } while (lastDateOfPrevMonth.dayOfWeek != DayOfWeek.SATURDAY)
    }

    // If current month does not end on a saturday, get the first few days of the next month
    if (lastDayOfMonth.dayOfWeek != weekFields.firstDayOfWeek.minus(1)) {
        val nextMonth = YearMonth.of(year, month.plus(1))
        var firstDateOfNextMonth = LocalDate.of(year, nextMonth.month, 1)

        do {
            dayDates[firstDateOfNextMonth.dayOfWeek]?.add(firstDateOfNextMonth)
            firstDateOfNextMonth = firstDateOfNextMonth.plusDays(1)
        } while (firstDateOfNextMonth.dayOfWeek != DayOfWeek.SUNDAY)
    }

    return dayDates
}

以上代码按预期工作并显示一个日历,一周从星期日开始。像我所做的那样,使用从星期日开始的新枚举手动将星期几插入新的哈希图中是否有意义?还是让日期 api 处理它(如果可能)以及如何处理更有意义?

或者我只是采取了错误的方法,日历 API 会是更好的选择吗?

我觉得值得一试。尝试在 API.

中结合使用 LocalTime 和 LocalDate
var dt = LocalDateTime.of(2021, 1, 1, 12, 45);// => 2021-01-01T12:45
It is possible to easily decompose into components:

var dt = LocalDateTime.of(2021, 1, 1, 12, 45);
// => 2021-01-01T12:45

var date = dt.toLocalDate();
// => 2021-01-01

var time = dt.toLocalTime();
// => 12:45

如果程序检测到错误,那么为了修复它,时间 API 将责任分成几个 类。

  • ZoneOffset — 从 UTC/GMT 的时间偏移,从 +14:00 到 -12:00。

  • ZoneRules — 更改单个时区偏移量的规则(例如,夏令时、历史更改)。 ZoneId 为时区标识,例如Europe/Berlin。 有两种不同类型的时区可用。

  • ZonedDateTime-绑定到特定的 ZoneId。 OffsetDateTime / OffsetTime — date/time 具有偏移量,但不绑定到特定时区。

无需您定义private enum class DaysOfWeek {…}。参见 DayOfWeek

参见

DayOfWeek firstDayOfWeek = WeekFields.of( Locale.CANADA_FRENCH ).getFirstDayOfWeek();

使用YearMonth来表示年和月。

ZoneId z = ZoneId.of( "America/Montreal" ) ;
YearMonth yearMonth = YearMonth.now( z ) ;

获取该月的第一天。

LocalDate firstOfMonth = yearMonth.atDay( 1 ) ;

如果需要,及时倒退,找到一周的第一天。使用 TemporalAdjuster 实现。

TemporalAdjuster ta = TemporalAdjusters.previousOrSame( firstDayOfWeek ) ;
LocalDate startOfCalendar = firstOfMonth.with( ta );

建立我们的日历。使用可导航地图将每周编号收集到 LocalDate 个对象的列表中。

NavigableMap< Integer , List< LocalDate > > calendar = new TreeMap<>() ;

或者只使用列表列表。

List< List < LocalDate > > calendar = new ArrayList<>() ;

赚一周。

LocalDate ld = startOfCalendar ; 
List< LocalDate > week = ld.datesUntil( ld.plusWeeks(1) ).toList() ;

如果使用旧版本的 Java,请将 .datesUntil 行替换为您自己的循环。

int daysInWeek = 7;
List < LocalDate > week = new ArrayList <>( daysInWeek );
for ( int indexIntoWeek = 0 ; indexIntoWeek < daysInWeek ; indexIntoWeek++ )
{
    week.add( ld.plusDays( indexIntoWeek ) );
}

将那一周添加到您的地图,并继续直到进入新的 YearMonth

将所有代码放在一起。

// Populate data.
DayOfWeek firstDayOfWeek = WeekFields.of( Locale.CANADA_FRENCH ).getFirstDayOfWeek();

ZoneId z = ZoneId.of( "America/Montreal" );
YearMonth yearMonth = YearMonth.now( z );
LocalDate firstOfMonth = yearMonth.atDay( 1 );

TemporalAdjuster ta = TemporalAdjusters.previousOrSame( firstDayOfWeek );
LocalDate startOfCalendar = firstOfMonth.with( ta );

NavigableMap < Integer, List < LocalDate > > calendar = new TreeMap <>();
int nthWeek = 1;
LocalDate ld = startOfCalendar;
while ( ! YearMonth.from( ld ).isAfter( yearMonth ) )
{
    List < LocalDate > week = ld.datesUntil( ld.plusWeeks( 1 ) ).toList();
    calendar.put( nthWeek++ , week );
    ld = ld.plusWeeks( 1 );  // Set up the next loop. Increment `ld` to the next week.
}

// Report results.
for ( Integer weekNumber : calendar.keySet() )
{
    DateTimeFormatter f = DateTimeFormatter.ofLocalizedDate( FormatStyle.SHORT ).withLocale( Locale.US );
    System.out.println( "weekNumber = " + weekNumber );
    for ( LocalDate localDate : calendar.get( weekNumber ) )
    {
        String output = localDate.getDayOfWeek().getDisplayName( TextStyle.FULL , Locale.US ) + " = " + localDate.format( f );
        System.out.println( output );
    }
}

当运行.

weekNumber = 1
Sunday = 8/29/21
Monday = 8/30/21
Tuesday = 8/31/21
Wednesday = 9/1/21
Thursday = 9/2/21
Friday = 9/3/21
Saturday = 9/4/21
weekNumber = 2
Sunday = 9/5/21
Monday = 9/6/21
Tuesday = 9/7/21
Wednesday = 9/8/21
Thursday = 9/9/21
Friday = 9/10/21
Saturday = 9/11/21
weekNumber = 3
Sunday = 9/12/21
Monday = 9/13/21
Tuesday = 9/14/21
Wednesday = 9/15/21
Thursday = 9/16/21
Friday = 9/17/21
Saturday = 9/18/21
weekNumber = 4
Sunday = 9/19/21
Monday = 9/20/21
Tuesday = 9/21/21
Wednesday = 9/22/21
Thursday = 9/23/21
Friday = 9/24/21
Saturday = 9/25/21
weekNumber = 5
Sunday = 9/26/21
Monday = 9/27/21
Tuesday = 9/28/21
Wednesday = 9/29/21
Thursday = 9/30/21
Friday = 10/1/21
Saturday = 10/2/21

使用现代日期时间 API java.time 非常容易。第一步是根据您的 Locale 找到一周的第一天,您可以按以下步骤操作:

DayOfWeek firstDayOfWeek = WeekFields.of(locale).getFirstDayOfWeek();

如果您只需要工作日列表:

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.WeekFields;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        // Let's test it
        System.out.println(getWeekDays(Locale.UK));
        System.out.println(getWeekDays(Locale.US));
    }

    static List<DayOfWeek> getWeekDays(Locale locale) {
        LocalDate localDate = LocalDate.now();

        // First day of week
        DayOfWeek firstDayOfWeek = WeekFields.of(locale).getFirstDayOfWeek();

        LocalDate date = localDate.with(TemporalAdjusters.dayOfWeekInMonth(0, firstDayOfWeek));
        return IntStream.rangeClosed(0, 6)
                    .mapToObj(i -> date.plusDays(i).getDayOfWeek())
                    .collect(Collectors.toList());
    }
}

输出:

[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
[SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY]

ONLINE DEMO

如果您需要扩展它以打印当月的完整日历:

其余逻辑围绕创建具有适当间距的标签展开,任何人只要具备循环的基本知识就应该能够做到。

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.WeekFields;
import java.util.Locale;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        // Let's test it
        System.out.println(getMonthCalendar(Locale.UK));
        System.out.println("-+-+-+-+-+-+-+-+-+-+-+-+-+-");
        System.out.println(getMonthCalendar(Locale.US));
    }

    static String getMonthCalendar(Locale locale) {
        LocalDate localDate = LocalDate.now();
        YearMonth ym = YearMonth.of(localDate.getYear(), localDate.getMonthValue());
        StringBuilder sb = new StringBuilder();

        // First day of week
        DayOfWeek firstDayOfWeek = WeekFields.of(locale).getFirstDayOfWeek();

        LocalDate date = localDate.with(TemporalAdjusters.dayOfWeekInMonth(0, firstDayOfWeek));
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("EEE", locale);
        sb.append(
                    IntStream.rangeClosed(0, 6)
                    .mapToObj(i -> dtf.format(date.plusDays(i)))
                    .collect(Collectors.joining(" "))
        )
        .append(System.lineSeparator());

        int counter = 1;

        // Print as many space as the difference between the day of week of 1st date of
        // the month and the first day of the week in that Locale
        int dayValue = localDate.withDayOfMonth(1).getDayOfWeek().getValue() - firstDayOfWeek.getValue();
        dayValue = dayValue < 0 ? 7 + dayValue : dayValue;
        for (int i = 0; i < dayValue; i++, counter++) {
            sb.append(String.format("%-4s", ""));
        }

        for (int i = 1; i <= ym.getMonth().length(ym.isLeapYear()); i++, counter++) {
            sb.append(String.format("%-4d", i));

            // Break the line if the value of the counter is multiple of 7
            if (counter % 7 == 0) {
                sb.append(System.lineSeparator());
            }
        }

        return sb.toString();
    }
}

输出:

Mon Tue Wed Thu Fri Sat Sun
        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  
-+-+-+-+-+-+-+-+-+-+-+-+-+-
Sun Mon Tue Wed Thu Fri Sat
            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  

ONLINE DEMO

检查 UK Calendar and the US Calendar 进行比较。

详细了解 modern Date-Time API* from Trail: Date Time


* 无论出于何种原因,如果您必须坚持Java 6 或Java 7,您可以使用ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and

WeekFields.dayOfWeek()

你是完全正确的:要为一个语言环境正确排序星期几,也就是说,根据该语言环境是一周的第一天,星期几排在第一位,你需要使用适当的WeekFields 对象。下面的 Java 方法只是一个简短的演示:

public static void sortAndPrintDaysOfWeek(Locale loc) {
    List<DayOfWeek> daysOfTheWeek = Arrays.asList(DayOfWeek.values());
    WeekFields wf = WeekFields.of(loc);

    daysOfTheWeek.sort(Comparator.comparingInt(dow -> dow.get(wf.dayOfWeek())));

    System.out.println(daysOfTheWeek);
}

我相信您会将类似的比较器应用于您的 Kotlin 排序映射。试试上面的方法:

    sortAndPrintDaysOfWeek(Locale.FRANCE);

输出:

[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]

    sortAndPrintDaysOfWeek(Locale.US);

[SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY]

    sortAndPrintDaysOfWeek(Locale.forLanguageTag("ar-EG"));

[SATURDAY, SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY]