获取两个日期之间的周数。

28
我正在工作一个项目,有两个不同类型的日期。我想计算这两个日期之间的周数。这些日期可以在不同的年份中。有没有好的解决方案?
我尝试使用其他话题中建议的Joda-time实现这个功能。
我不熟悉这个库,但我尝试做这样的事情:
public static int getNumberOfWeeks(Date f, Date l){
    Calendar c1 = Calendar.getInstance();
    Calendar c2 = Calendar.getInstance();
    c1.setTime(f);
    c2.setTime(l);
    DateTime start = new DateTime(c1.YEAR, c1.MONTH, c1.DAY_OF_MONTH, 0, 0, 0, 0);
    DateTime end   = new DateTime(c2.YEAR, c2.MONTH, c2.DAY_OF_MONTH, 0, 0, 0, 0);
    Interval interval = new Interval(start, end);
    Period p = interval.toPeriod();
    return p.getWeeks();
}

但是这完全是错误的...有什么建议吗?


1
FYI:Joda-Time 项目现已进入维护模式。它的创始人 Stephen Colebourne 继续领导 JSR 310,定义了内置于 Java 8+ 的 java.time 类。请参阅 Oracle 的教程 - Basil Bourque
19个回答

44

更新答案以适应Java 8

// TechTrip - ASSUMPTION d1 is earlier than d2
// leave that for exercise
public static long getFullWeeks(Calendar d1, Calendar d2){

    Instant d1i = Instant.ofEpochMilli(d1.getTimeInMillis());
    Instant d2i = Instant.ofEpochMilli(d2.getTimeInMillis());

    LocalDateTime startDate = LocalDateTime.ofInstant(d1i, ZoneId.systemDefault());
    LocalDateTime endDate = LocalDateTime.ofInstant(d2i, ZoneId.systemDefault());

    return ChronoUnit.WEEKS.between(startDate, endDate);
}

请注意,开始日期始终包含在内,而结束日期不包含在内。 - user666
2
如果我输入的是2018年2月28日和2019年3月2日,它会计算为1周,但实际上应该是2周。你能帮我重新计算一下吗? - Irfan Nasim
@IrfanNasim,这正是我正在寻找的。你找到任何解决方案了吗? - SHIVA
LocalDateTime 无法表示一个时刻,一个时间轴上的特定点。因此,在这里使用它是不合适的。请将其替换为 ZonedDateTime 以提供更好的示例。 - Basil Bourque

39

使用joda time非常简单:

DateTime dateTime1 = new DateTime(date1);
DateTime dateTime2 = new DateTime(date2);

int weeks = Weeks.weeksBetween(dateTime1, dateTime2).getWeeks();

5
答案正确,但已经过时。 Joda-Time 的创建者表示我们应该迁移到 java.time 框架。 - Basil Bourque
在这种情况下,如果您依赖于Interval类,那么只有True是可行的选择。在java.time中没有替代方法。 - nansen
ThreeTen-Extra项目中,您将找到一个Interval类,它扩展了java.time。 ThreeTen-Extra还作为可能未来添加到java.time类的试验场所。但是确实Joda-Time和java.time并不完全具备100%的功能平衡。虽然大体上相当,但每个都有一些缺少的功能。 - Basil Bourque
关于Threeten-Extra,似乎几乎处于休眠状态(请参见低活动水平,甚至还没有Jodas PeriodFormatter的替代品)。另一种选择是在我的lib Time4J中找到具有区间支持等功能的工具,它与java.time-package可以互操作,并且也可以用作扩展。 - Meno Hochschild

13

简短明了

ChronoUnit
.WEEKS
.between(
    myJavaUtilDate_Start.toInstant().atZone( ZoneId.of( "Asia/Tokyo" ) ) , 
    myJavaUtilDate_Stop.toInstant().atZone( ZoneId.of( "Asia/Tokyo" ) ) 
)

7

java.time

java.time框架内置于Java 8及更高版本中。这些新类替代了早期版本Java中捆绑的旧日期时间类。

java.time类也替代了非常成功的Joda-Time框架。 java.timeJoda-Time都由Stephen Colbourne领导。

Table of date-time types in Java, both modern and legacy

Instant替代java.util.Date

现代类Instant替换了旧的类java.util.Date。两者都表示UTC上的一个时刻,即时间线上的特定点。它们内部都使用自1970年1月1日UTC的第一个时刻的纪元引用以来的计数。旧类使用毫秒计数,而Instant使用更细粒度的纳秒计数。

要进行转换,请调用添加到旧类的新方法。

Instant start = myJavaUtilDateStart.toInstant() ;
Instant stop = myJavaUtilDateStop.toInstant() ;

让我们通过一些示例值来具体说明。

Instant start = OffsetDateTime.of( 2020 , 1 , 23 , 15 , 30 , 0 , 0 , ZoneOffset.UTC ).toInstant();
Instant stop = OffsetDateTime.of( 2020 , 1 , 23 , 15 , 30 , 0 , 0 , ZoneOffset.UTC ).plusWeeks(7 ).toInstant();

时刻与日期

我们的两个Instant对象都表示一个时刻。目标是计算周数。周意味着天数,而天数意味着日历上的特定日期。

因此我们有一些不匹配的地方。对于任何给定的时刻,由于时区的原因,日期会在全球各地发生变化。在法国巴黎午夜过后几分钟,就是新的日期。同时,在蒙特利尔魁北克,由于落后了几个小时,同样的时刻仍然是“昨天”,即日历上的前一天。因此,我们无法直接从一对时刻中计算出周数。

您必须首先确定希望通过哪个时区来看待这些时刻的日历。

Continent/Region 的格式指定一个正确的时区名称,例如 America/MontrealAfrica/CasablancaPacific/Auckland。绝不要使用2-4个字母的缩写,如ESTIST,因为它们不是真正的时区,没有标准化,甚至不是唯一的!

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

ZonedDateTime

将这个 ZoneId 应用到我们的 Instant 对象中,以调整到一个特定的时区,产生一对 ZonedDateTime 对象。

ZonedDateTime startZdt = start.atZone( z ) ;
ZonedDateTime stopZdt = stop.atZone( z ) ;

ChronoUnit.WEEKS

现在我们可以使用ChronoUnit枚举来计算经过的周数。 long weeks = ChronoUnit.WEEKS.between(startZdt, stopZdt); 将结果输出到控制台。
System.out.println( "start.toString() = " + start );
System.out.println( "stop.toString() = " + stop );
System.out.println( "startZdt.toString() = " + startZdt );
System.out.println( "stopZdt.toString() = " + stopZdt );
System.out.println( "weeksCount: " + weeksCount );

请查看 在IdeOne.com上运行代码

start.toString() = 2020-01-23T15:30:00Z

stop.toString() = 2020-03-12T15:30:00Z

startZdt.toString() = 2020-01-23T10:30-05:00[America/Montreal]

stopZdt.toString() = 2020-03-12T11:30-04:00[America/Montreal]

weeksCount: 7

ThreeTen-Extra

ThreeTen-Extra项目为内置于Java 8及更高版本的java.time框架添加了功能。

Weeks

该项目包括一个Weeks类,用于表示若干周。它不仅可以进行计算,还可以作为类型安全的对象在您的代码中使用。这种使用方式还有助于使您的代码自我记录。
你可以使用Weeks.between方法提供一对时间点进行实例化。这些时间点可以是任何实现java.time.temporal.Temporal的东西,包括Instant, LocalDate, OffsetDateTime, ZonedDateTime, Year, YearMonth等等。

您的 java.util.Date 对象可以轻松转换为 Instant 对象,在 UTC 时间线上具有以纳秒为分辨率的时刻。查看旧日期时间类中添加的新方法。要从 Date 转换为 Instant,请调用 java.util.Date::toInstant

Weeks weeks = Weeks.between( startZdt , stopZdt );

您可以询问周数。
int weeksNumber = weeks.getAmount(); // The number of weeks in this Weeks object.

你可以做更多的事情。
生成一个符合ISO 8601标准格式的字符串。 P表示开始,W表示一定数量的周数。

PW7


关于java.time

java.time框架内置于Java 8及以后版本中。这些类代替了老旧的遗留日期时间类,例如java.util.DateCalendarSimpleDateFormat

要了解更多,请参阅Oracle教程。并在Stack Overflow上搜索许多示例和解释。规范是JSR 310

Joda-Time项目现在处于维护模式,建议迁移到java.time类。

您可以直接使用java.time对象与数据库交换。使用符合JDBC 4.2或更高版本的JDBC驱动程序。无需字符串,无需java.sql.*类。

从哪里获取java.time类?

Table of which java.time library to use with which version of Java or Android

这个 ThreeTen-Extra 项目扩展了 java.time 的附加类。该项目是 java.time 可能未来添加的一个试验场。您可能会在这里找到一些有用的类,例如 Interval, YearWeek, YearQuarter更多。请注意,保留html标记。

通常我不喜欢点踩,但是这里有以下原因:a) 你的代码会抛出 UnsupportedTemporalTypeException 异常。b) 引入 Threeten-Extra 作为解决方案是愚蠢的,因为 Java-8 已经支持计算经过的周数,可以看到 @TechTrip 的正确答案,那么只为此目的添加额外的依赖有什么意义呢?c) Threeten-Extra 没有 "官方" 状态。未来的增强功能将直接进入 OpenJDK 中的 java.time(请参见 Java-9 中 java.time-component 的所有预定功能)。 - Meno Hochschild
@MenoHochschild 感谢您的批评。在此基础上,我对我的回答进行了改进。 - Basil Bourque
好的,我在你的更正之后取消了踩票。 - Meno Hochschild

8
如果您的要求是开始日期为2020年4月3日,结束日期为2020年4月7日。两个日期之间的差异为4天。现在,对于这两个日期之间的周数为1,您可以使用以下代码片段。
ChronoUnit.WEEKS.between(LocalDate startDate, LocalDate endDate);

但是,如果您的要求是像2020年4月3日在一周内,而2020年4月7日在另一周,因此您想要两个日期之间的周数为2,则可以使用以下代码片段。
LocalDate actualStartDate=...
LocalDate actualEndDate=...

LocalDate startDate = actualStartDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.SUNDAY)) 

LocalDate endDate = actualEndDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.SATURDAY)) 

long daysBetweenTwoDates = ChronoUnit.DAYS.between(startDate, endDate);
int numberOfWeeks =  (int)Math.ceil(daysBetweenTwoDates/7.0);

测试在Java 1.8中


在使用JDK11 Base(正确版本)的项目中进行了测试。 - ieselisra

8

使用 java.util.Calendar 中的日期算术:

public static int getWeeksBetween (Date a, Date b) {

    if (b.before(a)) {
        return -getWeeksBetween(b, a);
    }
    a = resetTime(a);
    b = resetTime(b);

    Calendar cal = new GregorianCalendar();
    cal.setTime(a);
    int weeks = 0;
    while (cal.getTime().before(b)) {
        // add another week
        cal.add(Calendar.WEEK_OF_YEAR, 1);
        weeks++;
    }
    return weeks;
}

public static Date resetTime (Date d) {
    Calendar cal = new GregorianCalendar();
    cal.setTime(d);
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);
    return cal.getTime();
}

1
如果日期在不同的年份,这个函数能正常工作吗? - stackoverflowuser2010
1
你需要将日期对齐到一周的第一天,否则在同一周的不同日期中它会返回1。 - foal
类似这样的代码:cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek()); - foal

2
以下是我编写的两种方法,不依赖于外部库。
第一种方法适用于周一为一周的第一天的情况。
第二种方法适用于周日为一周的第一天的情况。
请查看代码中的注释,其中有一种选项可以返回两个日期之间完整周数的数量,以及在两个日期之前和之后剩余天数的小数部分。
public static int getNumberOfFullWeeks(LocalDate startDate,LocalDate endDate)
{
    int dayBeforeStartOfWeek = 0;
    int daysAfterLastFullWeek = 0;

    if(startDate.getDayOfWeek() != DayOfWeek.MONDAY)
    {
        // get the partial value before loop starting
        dayBeforeStartOfWeek = 7-startDate.getDayOfWeek().getValue() + 1;
    }

    if(endDate.getDayOfWeek() != DayOfWeek.SUNDAY)
    {
        // get the partial value after loop ending
        daysAfterLastFullWeek = endDate.getDayOfWeek().getValue();
    }

    LocalDate d1 = startDate.plusDays(dayBeforeStartOfWeek); // now it is the first day of week;
    LocalDate d2 = endDate.minusDays(daysAfterLastFullWeek); // now it end in the last full week

    // Count how many days there are of full weeks that start on Mon and end in Sun
    // if the startDate and endDate are less than a full week the while loop
    // will not iterate at all because d1 and d2 will be the same date
    LocalDate looper = d1;
    int counter = 1;
    while (looper.isBefore(d2))
    {
        counter++;
        looper = looper.plusDays(1);
    }

    // Counter / 7 will always be an integer that will represents full week
    // because we started to count at Mon and stop counting in Sun
    int fullWeeks = counter / 7;

    System.out.println("Full weeks between dates: "
            + fullWeeks + " Days before the first monday: "
            + dayBeforeStartOfWeek + " "
            + " Days after the last sunday: " + daysAfterLastFullWeek);
    System.out.println(startDate.toString() + " - " + endDate.toString());

    // You can also get a decimal value of the full weeks plus the fraction if the days before
    // and after the full weeks
    float full_weeks_decimal = (float)fullWeeks;
    float fraction = ((float)dayBeforeStartOfWeek + (float)daysAfterLastFullWeek) / 7.0F;
    System.out.println("Full weeks with fraction: " + String.valueOf(fraction + full_weeks_decimal));

    return fullWeeks;
}

public static int getNumberOfFullWeeks_WeekStartAtSunday(LocalDate startDate,LocalDate endDate)
{
    int dayBeforeStartOfWeek = 0;
    int daysAfterLastFullWeek = 0;

    if(startDate.getDayOfWeek() != DayOfWeek.SUNDAY)
    {
        // get the partial value before loop starting
        dayBeforeStartOfWeek = 7-getDayOfWeekBySundayIs0(startDate.getDayOfWeek()) + 1;
    }

    if(endDate.getDayOfWeek() != DayOfWeek.SATURDAY)
    {
        // get the partial value after loop ending
        daysAfterLastFullWeek = 1+getDayOfWeekBySundayIs0(endDate.getDayOfWeek());
    }

    LocalDate d1 = startDate.plusDays(dayBeforeStartOfWeek); // now it is the first day of week;
    LocalDate d2 = endDate.minusDays(daysAfterLastFullWeek); // now it end in the last full week

    // Count how many days there are of full weeks that start on Sun and end in Sat
    // if the startDate and endDate are less than a full week the while loop
    // will not iterate at all because d1 and d2 will be the same date
    LocalDate looper = d1;
    int counter = 1;
    while (looper.isBefore(d2))
    {
        counter++;
        looper = looper.plusDays(1);
    }

    // Counter / 7 will always be an integer that will represents full week
    // because we started to count at Sun and stop counting in Sat
    int fullWeeks = counter / 7;

    System.out.println("Full weeks between dates: "
            + fullWeeks + " Days before the first sunday: "
            + dayBeforeStartOfWeek + " "
            + " Days after the last saturday: " + daysAfterLastFullWeek);
    System.out.println(startDate.toString() + " - " + endDate.toString());

    // You can also get a decimal value of the full weeks plus the fraction if the days before
    // and after the full weeks
    float full_weeks_decimal = (float)fullWeeks;
    float fraction = ((float)dayBeforeStartOfWeek + (float)daysAfterLastFullWeek) / 7.0F;
    System.out.println("Full weeks with fraction: " + String.valueOf(fraction + full_weeks_decimal));

    return fullWeeks;
}

   public static int getDayOfWeekBySundayIs0(DayOfWeek day)
    {
        if(day == DayOfWeek.SUNDAY)
        {
            return 0;
        }
        else
        {
            // NOTE: getValue() is starting to count from 1 and not from 0
            return  day.getValue();
        }
    }

1
我认为使用内置的java.time(LocalDateDayOfWeek)是很好的选择。在我看来,你的解决方案似乎过于复杂了。感谢分享你的代码。 - Ole V.V.

2
Calendar a = new GregorianCalendar(2002,1,22);
    Calendar b = new GregorianCalendar(2002,1,28);
    System.out.println(a.get(Calendar.WEEK_OF_YEAR));
    System.out.println(b.get(Calendar.WEEK_OF_YEAR)); 
   int weeks = b.get(Calendar.WEEK_OF_YEAR)-a.get(Calendar.WEEK_OF_YEAR);
    System.out.println(weeks);

尝试这个,一定可以运行

    Calendar calendar1 = Calendar.getInstance();
Calendar calendar2 = Calendar.getInstance();
calendar1.set(2007, 01, 10);
calendar2.set(2007, 07, 01);
long milliseconds1 = calendar1.getTimeInMillis();
long milliseconds2 = calendar2.getTimeInMillis();
long diff = milliseconds2 - milliseconds1;
int diffWeeks = (int)diff / (7*24 * 60 * 60 * 1000);

5
这种方法不能正确处理不同年份的日期。 - Joost
问题已经是关于如何计算不同年份的周数了。解决方案没有意义! - gokhansari

1
如果您想要精确的完整周数,请使用以下方法,其中结束日期不包括在内:
public static long weeksBetween(Date date1, Date date2) {
    return WEEKS.between(date1.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(),
        date2.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
  }

如果您想要这个的向上取整版本,请使用以下代码:

public static long weeksBetween(Date date1, Date date2) {
    long daysBetween = DAYS.between(date1.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(),
        date2.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()) + 1;
    return daysBetween / 7 + (daysBetween % 7 == 0 ? 0 : 1);
  }

1
亲爱的点踩者,请礼貌地添加一条评论,说明您点踩的原因。 - Sahil Chhabra

0
    int startWeek = c1.get(Calendar.WEEK_OF_YEAR);
    int endWeek = c2.get(Calendar.WEEK_OF_YEAR);    

    int diff = c2.get(Calendar.YEAR) - c1.get(Calendar.YEAR);

    int deltaYears = 0;
    for(int i = 0;i < diff;i++){
        deltaYears += c1.getWeeksInWeekYear();
        c1.add(Calendar.YEAR, 1);        
    }
    diff = (endWeek + deltaYears) - startWeek;

包括年份差异。 这对我有用 :)


0

在参考了许多解决方案后,这个对我有用。 {前提是我不想使用外部库}

public static int getNumberOfWeeks(Date date1, Date date2) {
if (date1.after(date2)) {
        return getNumberOfWeeks(date2, date1);
    }

    Date date = date1;
    int days = 0;
    while (date.before(date2)) {
        days++;
        date = addDays(date, 1);
    }
    return days/7;
}

在日期上添加天数:

Date addDays(Date date, int days) {
    if (days == 0) {
        return date;
    } else {
        Date shiftedDate = new Date(date.getTime() + (long)days * 86400000L);
        return shiftedDate;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接