如何获得一个月中特定周的第一天和最后一天

How to get first and last day in specific week in month

我需要帮助 return 给定周的第一天(和最后一天),仅传递该月的第几周以及相应的月份和年份。

我已经在寻找使用 LocalDate 或 Calendar return 的方法,但没有找到。

我的语言环境是 PT-BR - 一周的第一天是星期日

提前致谢。

例如:

Year 2021 - Month 7 (July) - Week 1 - First day: 1 - Last Day: 3
Year 2021 - Month 7 (July) - Week 2 - First day: 4 - Last Day: 10
Year 2021 - Month 7 (July) - Week 3 - First day: 11 - Last Day: 17
Year 2021 - Month 7 (July) - Week 4 - First day: 18 - Last Day: 24
Year 2021 - Month 7 (July) - Week 5 - First day: 25 - Last Day: 31

我发现你的周历方法很有趣,想出了一个接收两个参数(月和年)并建立那个月的周历的方法:

private static Map<Integer, Integer[]> getWeekCalendar(int month, int year) {
    Map<Integer, Integer[]> weekCalendar = new HashMap<>();
    LocalDate date = LocalDate.of(year,month,1);
    int week = 1;
    LocalDate firstOfWeek = LocalDate.from(date);
    while (date.getMonth().equals(Month.of(month))) {
        //if it is a saturday, advance to the next week and register current week bounds
        if (date.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {
            weekCalendar.put(week++, new Integer[]{firstOfWeek.getDayOfMonth(), date.getDayOfMonth()});
            firstOfWeek = LocalDate.from(date.plusDays(1L));
        }
        date = date.plusDays(1L);
    }
    //this one is necessary because the month may end on a Saturday
    if (firstOfWeek.getMonth().equals(Month.of(month))) {
        weekCalendar.put(week, new Integer[]{firstOfWeek.getDayOfMonth(), date.minusDays(1L).getDayOfMonth()});
    }
    return weekCalendar;
}

还有一个主要的 class 用于测试目的:

public static void main(String[] args) {
    int wantedMonth = 7;
    int wantedYear = 2021;
    Map<Integer, Integer[]> weekCalendar = getWeekCalendar(wantedMonth, wantedYear);
    weekCalendar.entrySet().stream()
            .map(e -> String.format("Year %d - Month %d (%s) - Week %d - First day: %d - Last day: %d", wantedYear, wantedMonth, Month.of(wantedMonth).name(), e.getKey(), e.getValue()[0], e.getValue()[1]))
            .forEach(System.out::println);
}

输出:

Year 2021 - Month 7 (JULY) - Week 1 - First day: 1 - Last day: 3
Year 2021 - Month 7 (JULY) - Week 2 - First day: 4 - Last day: 10
Year 2021 - Month 7 (JULY) - Week 3 - First day: 11 - Last day: 17
Year 2021 - Month 7 (JULY) - Week 4 - First day: 18 - Last day: 24
Year 2021 - Month 7 (JULY) - Week 5 - First day: 25 - Last day: 31

没有一个函数可以获取一个月中的第几周。

我整理了一个方法来计算一个月中的第几周。这是我的多次测试之一的测试结果。

Week 1 of July 2021 is 1 - 3
Week 2 of July 2021 is 4 - 10
Week 3 of July 2021 is 11 - 17
Week 4 of July 2021 is 18 - 24
Week 5 of July 2021 is 25 - 31
Week 6 of July 2021 is 0 - 0
Week 1 of August 2021 is 1 - 7
Week 2 of August 2021 is 8 - 14

我根据年份和月份创建了一个LocalDate。我做了一些日期运算,得到一个月中的星期日和星期六。

这是代码。我测试了 2021 年 7 月和 8 月。它可能适用于其他月份。

import java.time.LocalDate;

public class DatesFinder {

    public static void main(String[] args) {
        DatesFinder df = new DatesFinder();
        int[] days = df.findDays(2021, 7, 1);
        System.out.println("Week 1 of July 2021 is " + days[0] + " - " + days[1]);
        days = df.findDays(2021, 7, 2);
        System.out.println("Week 2 of July 2021 is " + days[0] + " - " + days[1]);
        days = df.findDays(2021, 7, 3);
        System.out.println("Week 3 of July 2021 is " + days[0] + " - " + days[1]);
        days = df.findDays(2021, 7, 4);
        System.out.println("Week 4 of July 2021 is " + days[0] + " - " + days[1]);
        days = df.findDays(2021, 7, 5);
        System.out.println("Week 5 of July 2021 is " + days[0] + " - " + days[1]);
        days = df.findDays(2021, 7, 6);
        System.out.println("Week 6 of July 2021 is " + days[0] + " - " + days[1]);
        days = df.findDays(2021, 8, 1);
        System.out.println("Week 1 of August 2021 is " + days[0] + " - " + days[1]);
        days = df.findDays(2021, 8, 2);
        System.out.println("Week 2 of August 2021 is " + days[0] + " - " + days[1]);
    }
    
    public int[] findDays(int year, int month, int weekNumber) {
        LocalDate date =  LocalDate.of(year, month, 1);
        int weekday = date.getDayOfWeek().getValue();
        // weekday is 1 - Monday to 7 - Sunday.
        // convert to 0 - Sunday.
        weekday = weekday % 7;
        
        LocalDate sundayDate = date.minusDays(weekday);
        int dayIncrement = (weekNumber - 1) * 7;
        if (dayIncrement > 0) {
            sundayDate = sundayDate.plusDays(dayIncrement);
        }
        
        int lowDay = sundayDate.getDayOfMonth();
        LocalDate saturdayDate = sundayDate.plusDays(6L);
        int highDay = saturdayDate.getDayOfMonth();
//      System.out.println(date + " " + sundayDate + " " + lowDay + " " + highDay);
        
        // Test for beginning of month
        if (sundayDate.getMonth().compareTo(date.getMonth()) < 0) {
            lowDay = 1;
        }
        
        // Test for end of month
        if (saturdayDate.getMonth().compareTo(date.getMonth()) > 0) {
            LocalDate nextMonth = date.plusMonths(1L);
            nextMonth = nextMonth.minusDays(1L);
            highDay = nextMonth.getDayOfMonth();
        }
        
        // Test for outside of month (weekNumber too high)
        if (sundayDate.getMonth().compareTo(date.getMonth()) > 0) {
            lowDay = 0;
            highDay = 0;
        }

        int[] output = new int[2];
        output[0] = lowDay;
        output[1] = highDay;
        
        return output;
    }

}

java.time

java.util 日期时间 API 及其格式 API、SimpleDateFormat 已过时且容易出错。建议完全停止使用它们并切换到 modern Date-Time API*.

解决方案使用 java.time,现代日期时间 API:

  1. 使用 WeekFields.of(locale).getFirstDayOfWeek() 查找一周的第一天。
  2. 使用 TemporalAdjusters.nextOrSame(firstDayOfWeek) 查找一周的最后一天。
  3. 使用 TemporalAdjusters.lastDayOfMonth() 查找该月的最后一天。

演示:

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;
import java.time.format.TextStyle;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.WeekFields;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        // Test
        System.out.println(getWeeks(2021, 7, new Locale("pt", "BR")));

        System.out.println("---------------------------------------------------------");

        System.out.println(getWeeks(2021, 2, new Locale("pt", "BR")));

        System.out.println("---------------------------------------------------------");

        System.out.println(getWeeks(2021, 7, new Locale("en", "GB")));

        System.out.println("---------------------------------------------------------");

        System.out.println(getWeeks(2021, 2, new Locale("en", "GB")));
    }

    static String getWeeks(int year, int month, Locale locale) {
        DayOfWeek firstDayOfWeek = WeekFields.of(locale).getFirstDayOfWeek();
        LocalDate firstDateOfMonth = LocalDate.of(year, month, 1);
        String monthName = Month.of(month).getDisplayName(TextStyle.FULL, Locale.ENGLISH);
        int lastDayOfMonth = firstDateOfMonth.with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();

        int firstDay = 1;
        int lastDayOfFirstWeek = LocalDate.of(year, month, 1).with(TemporalAdjusters.nextOrSame(firstDayOfWeek))
                .getDayOfMonth();
        int lastDay = lastDayOfFirstWeek == 1 ? 7 : lastDayOfFirstWeek - 1;

        int i;
        StringBuilder sb = new StringBuilder();

        for (i = 1; i <= lastDayOfMonth / 7; i++) {
            sb.append(String.format("Year %d - Month %d (%s) - Week %d - First day: %d - Last Day: %d%n", year, month,
                    monthName, i, firstDay, lastDay));
            firstDay = lastDay + 1;
            lastDay += 7;
        }

        if (lastDayOfFirstWeek != 1 && lastDayOfMonth >= 28)
            sb.append(String.format("Year %d - Month %d (%s) - Week %d - First day: %d - Last Day: %d%n", year, month,
                    monthName, i, firstDay, lastDayOfMonth));

        return sb.toString();
    }
}

输出:

Year 2021 - Month 7 (July) - Week 1 - First day: 1 - Last Day: 3
Year 2021 - Month 7 (July) - Week 2 - First day: 4 - Last Day: 10
Year 2021 - Month 7 (July) - Week 3 - First day: 11 - Last Day: 17
Year 2021 - Month 7 (July) - Week 4 - First day: 18 - Last Day: 24
Year 2021 - Month 7 (July) - Week 5 - First day: 25 - Last Day: 31

---------------------------------------------------------
Year 2021 - Month 2 (February) - Week 1 - First day: 1 - Last Day: 6
Year 2021 - Month 2 (February) - Week 2 - First day: 7 - Last Day: 13
Year 2021 - Month 2 (February) - Week 3 - First day: 14 - Last Day: 20
Year 2021 - Month 2 (February) - Week 4 - First day: 21 - Last Day: 27
Year 2021 - Month 2 (February) - Week 5 - First day: 28 - Last Day: 28

---------------------------------------------------------
Year 2021 - Month 7 (July) - Week 1 - First day: 1 - Last Day: 4
Year 2021 - Month 7 (July) - Week 2 - First day: 5 - Last Day: 11
Year 2021 - Month 7 (July) - Week 3 - First day: 12 - Last Day: 18
Year 2021 - Month 7 (July) - Week 4 - First day: 19 - Last Day: 25
Year 2021 - Month 7 (July) - Week 5 - First day: 26 - Last Day: 31

---------------------------------------------------------
Year 2021 - Month 2 (February) - Week 1 - First day: 1 - Last Day: 7
Year 2021 - Month 2 (February) - Week 2 - First day: 8 - Last Day: 14
Year 2021 - Month 2 (February) - Week 3 - First day: 15 - Last Day: 21
Year 2021 - Month 2 (February) - Week 4 - First day: 22 - Last Day: 28

ONLINE DEMO

Trail: Date Time 中了解有关 modern Date-Time API* 的更多信息.


* 无论出于何种原因,如果您必须坚持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

年历周数

为了扩展 ,让我们获取一年的周历。对于我们这一周,让我们使用第三方 class 来表示几天的时间跨度。我们收集月地图作为键,周列表作为值。从该地图我们可以检索任何特定的一周。

ThreeTen-Extra 图书馆,LocalDateRange class

ThreeTen-Extra 库添加到您的项目中。这给了我们 LocalDateRange class 将时间跨度表示为一对 LocalDate 对象。这个class提供了abutscontains等很多方便的方法,在你处理周的时候可能会有帮助。

指定 YearLocale。我们根据区域设置确定一周中的第一天,如 .

所示

每月循环,然后每周循环

我们使用 Month objects returned by the Month.values method on this enum. We combine each month with the year to get a YearMonth 对象数组循环一年中的每个月。

对于每个 YearMonth,我们得到该月的第一天。然后我们及时回溯得到一周的第一天 (DayOfWeek), or use the same date if it is indeed our desired day-of-week. A TemporalAdjuster moves us along the timeline. You can write your own TemporalAdjuster implementation, but we can find one already written for our needs here: TemporalAdjusters.previousOrSame( DayOfWeek ).

然后我们循环计算每月的每一周,从一对 LocalDate 对象中创建一个 LocalDateRange 作为每周的开始和结束。我们将那些 LocalDateRange 个对象收集到 NavigableSet, a TreeSet.

我们通过将每个 YearMonth 作为键添加到 NavigableMap (TreeMap) 来报告我们的结果,其值为 LocalDateRangeNavigableSet

示例代码

package work.basil.datetime;

import org.threeten.extra.LocalDateRange;

import java.time.*;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.WeekFields;
import java.util.*;

public final class WeeklyCalendar {
    public static final NavigableMap < YearMonth, List < LocalDateRange > > calculate ( final Year year , final Locale locale ) {
        Objects.requireNonNull( year );
        DayOfWeek startOfWeek = WeekFields.of( Objects.requireNonNull( locale ) ).getFirstDayOfWeek();

        NavigableMap < YearMonth, List < LocalDateRange > > navMap = new TreeMap <>();

        TemporalAdjuster adjuster = TemporalAdjusters.previousOrSame( startOfWeek );
        for ( Month month : Month.values() ) {
            List < LocalDateRange > weeks = new ArrayList <>();
            YearMonth yearMonth = year.atMonth( month );
            LocalDate start = yearMonth.atDay( 1 ).with( adjuster );
            LocalDate end = start.plusWeeks( 1 );
            do {
                LocalDateRange week = LocalDateRange.of( start , end );
                weeks.add( week );
                // Set up next loop.
                start = start.plusWeeks( 1 );
                end = start.plusWeeks( 1 );
            } while ( ! YearMonth.from( end ).isAfter( yearMonth ) );
            navMap.put( yearMonth , weeks );
        }

        return Collections.unmodifiableNavigableMap( navMap );  // Generally best to return immutable values.
    }
}

用法:

Year year = Year.of( 2021 );
Locale locale = new Locale( "pt" , "BR" );
NavigableMap < YearMonth, List < LocalDateRange > > map = WeeklyCalendar.calculate( year , locale );

LocalDateRange weekOneOfMarch2021 = map.get( YearMonth.of( 2021 , Month.MARCH ) ).get( 0 );  // Lists use annoying zero-based counting, index number. 

当运行.

map = {2021-01=[2020-12-27/2021-01-03, 2021-01-03/2021-01-10, 2021-01-10/2021-01-17, 2021-01-17/2021-01-24, 2021-01-24/2021-01-31], 2021-02=[2021-01-31/2021-02-07, 2021-02-07/2021-02-14, 2021-02-14/2021-02-21, 2021-02-21/2021-02-28], 2021-03=[2021-02-28/2021-03-07, 2021-03-07/2021-03-14, 2021-03-14/2021-03-21, 2021-03-21/2021-03-28], 2021-04=[2021-03-28/2021-04-04, 2021-04-04/2021-04-11, 2021-04-11/2021-04-18, 2021-04-18/2021-04-25], 2021-05=[2021-04-25/2021-05-02, 2021-05-02/2021-05-09, 2021-05-09/2021-05-16, 2021-05-16/2021-05-23, 2021-05-23/2021-05-30], 2021-06=[2021-05-30/2021-06-06, 2021-06-06/2021-06-13, 2021-06-13/2021-06-20, 2021-06-20/2021-06-27], 2021-07=[2021-06-27/2021-07-04, 2021-07-04/2021-07-11, 2021-07-11/2021-07-18, 2021-07-18/2021-07-25], 2021-08=[2021-08-01/2021-08-08, 2021-08-08/2021-08-15, 2021-08-15/2021-08-22, 2021-08-22/2021-08-29], 2021-09=[2021-08-29/2021-09-05, 2021-09-05/2021-09-12, 2021-09-12/2021-09-19, 2021-09-19/2021-09-26], 2021-10=[2021-09-26/2021-10-03, 2021-10-03/2021-10-10, 2021-10-10/2021-10-17, 2021-10-17/2021-10-24, 2021-10-24/2021-10-31], 2021-11=[2021-10-31/2021-11-07, 2021-11-07/2021-11-14, 2021-11-14/2021-11-21, 2021-11-21/2021-11-28], 2021-12=[2021-11-28/2021-12-05, 2021-12-05/2021-12-12, 2021-12-12/2021-12-19, 2021-12-19/2021-12-26]}

weekOneOfMarch2021 = 2021-02-28/2021-03-07

注意 LocalDateRange#toString 如何使用开始日期的标准格式,然后是 SOLIDUS /,然后是结束日期。

年度重叠

请注意,我们从前一年的天数开始。我们不包括日历年的最后几天。

半开

请注意,这里的周是使用半开方法定义的,其中开始是 包含 ,而结尾是 不包含 。这通常是定义时间跨度的最佳方法。我建议在整个代码库中始终如一地使用这种方法,以保持逻辑一致,从而避免错误和混淆。

全封闭

不过,如果你坚持全封闭的方式,LocalDateRangeclass可以支持。请参阅 LocalDateRange.ofClosed 方法。

使用风险自负

我刚才快速编写了这段代码。所以没有保证。验证逻辑,并执行您自己的测试。

ISO 8601 周

请注意,定义一周的方法有多种。一些行业通常使用特定的定义。

有一个越来越受欢迎的国际标准:ISO 8601. That standard defines weeks因为星期一是一周的第一天,第 1 周包含日历年的第一个星期四。因此,一年由 52 或 53 个完整的星期组成,每个星期 7 天。

ThreeTen-Extra 库提供了一个 class 来表示 ISO 8601 周:YearWeek.