在指定的期限过去之后获取下一个有效日期,并相应地调整一个月中的某天

问题描述

我有一个像这样的枚举

enum Period{DAY,WEEK,MONTH,YEAR}

我需要的功能是在给today添加指定时间段的同时给public static Date getNextDate(Date startDate,Period period,int times){ /* Examples: getNextDate(31.08.2020,1) -> 30.09.2020 getNextDate(31.08.2020,2) -> 31.10.2020 getNextDate(30.05.2020,2) -> 30.09.2020 getNextDate(30.06.2020,2) -> 30.10.2020 (This is the next valid date after today) Years are pretty simple i guess (Okay,there is at least one edge case): getNextDate(28.02.2020,YEAR,1) -> 28.02.2021 getNextDate(29.02.2020,1) -> 28.02.2021 <- Edge case,as 2020 is a gap year getNextDate(29.02.2020,4) -> 29.02.2024 <- gap year to gap year For weeks and days there are no edge cases,are there? getNextDate(29.02.2020,DAY,1) -> 03.09.2020 getNextDate(29.02.2020,3) -> 05.09.2020 getNextDate(29.02.2020,2) -> 12.09.2020 (Same as DAY,14) Important: If today is already a payment day,this already is the solution getNextDate(03.09.2020,1) -> 03.09.2020 (No change here,the date matches today) */ } 设置月份中的某天,使其等于开始日期(如果结果有效)。

或者也许这样更容易理解: 想象一下,您每月31号得到薪水(如适用)。该函数将返回您下一个薪水的下一个有效日期(从今天开始)。函数在何处可以区分您是每天,每周,每月,每年还是在指定间隔内获取频率。 它还可以处理无效日期

让我们看一个例子:

//This is a method of the enum mentioned
public Date getNextDate(Date baseDate,int specific) {
        Date result = null;
        switch (this) {
            case DAY:
                result = DateTimeUtils.addDays(baseDate,specific);
                break;
         
            case WEEK:
                result = DateTimeUtils.addWeeks(baseDate,specific);
                break;
          
            case MONTH:
                result = DateTimeUtils.addMonths(baseDate,specific);
                break;
            case YEAR:
                result = DateTimeUtils.addYears(baseDate,specific);
                break;
        }
        return result;
    }

public Date getNextDateAfterToday(Date baseDate) {
        today = new Date();
        while(!baseDate.equals(today ) && !baseDate.after(today)){
            baseDate= getNextDate(baseDate,1);
        }
        return startHere;
    }

我实际上更喜欢使用现代的LocalDate API(只是输入目前是一个旧的日期对象,但稍后会更改)

我希望我不会忘记任何边缘情况。

更新我所做的事情

getNextDate()

我的getNextDateAfterToday()方法有效。 31.06.2020,1也可以使用,但不会返回边缘情况的有效日期。示例{{1}}将在每个月的30日立即显示为stuc,即使该月有31天,也绝不会跳回。对于30.09.2020,这是正确的。但是对于2020年10月31日则不会

解决方法

我终于找到了一种方法(尽管对于我真正想要实现的目标来说,它似乎很复杂)。我将getNextDateAfterToday更改为此

public Date getNextValidFutureDate(Date entryDate,Date startDate,int specific) {
        Date result = new Date(startDate.getTime());
        while (!result.equals(entryDate) && !result.after(entryDate)) {
            result = getNextDate(result,true,specific);
        }
        LocalDate ldStart = startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        LocalDate ldResult = result.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

        if (ldResult.getDayOfMonth() < ldStart.getDayOfMonth() && this != DAY && this != WEEK && this != YEAR) {
            if (ldResult.lengthOfMonth() >= ldStart.getDayOfMonth()) {
                ldResult = ldResult.with(ChronoField.DAY_OF_MONTH,ldStart.getDayOfMonth());
            } else {
                ldResult = ldResult.with(ChronoField.DAY_OF_MONTH,ldResult.lengthOfMonth());
            }
        }
        return Date.from(ldResult.atStartOfDay(ZoneId.systemDefault()).toInstant());
    }

我没有更改其他方法来使用LocalDate,但是将来会这样做。 这适用于我上面发布的所有测试用例。虽然我希望我不要错过必不可少的东西

,

Not using the decade old date api which is badly written and generally unsafe and painful to use might be the best idea.在这里可能更适合使用java.time。您只需要做的就是将方法签名更改为此:

import java.time.LocalDate;
import java.time.Period;

...

public static LocalDate getNextDate(LocalDate startDate,Period period) {
    return startDate.plus(period);
}

然后可以这样称呼:

LocalDate startDate = LocalDate.of(3,9,2020);
LocalDate nextDate = getNextDate(startDate,Period.ofDays(20)); // 2020-09-23

或者只是将您的辅助函数放在首位并直接使用它:

LocalDate nextDate = startDate.plus(Period.ofDays(20));
,

…(尽管对于我真正的东西来说似乎很复杂) 想要实现)...

您自己的解决方案还不错。我只是无法解决挑战,所以这是我的努力。我相信这会更简单。

我全力以赴java.time,这是现代的Java日期和时间API。由于预定义的Period枚举已达到目的,因此我也跳过了您的ChronoUnit枚举。只有它还包括小时,分钟和其他在这里没有意义的单位,因此我们需要拒绝这些。

Date类的设计很差,而且已经过时了。如果可以的话,请避免使用它(如果您无法避免的话,我最终会为您提供解决方案)。

public static LocalDate getNextDate(LocalDate startDate,TemporalUnit period,int times) {
    if (! period.isDateBased()) {
        throw new IllegalArgumentException("Cannot add " + period + " to a date");
    }
    
    LocalDate today = LocalDate.now(ZoneId.of("America/Eirunepe"));
    
    if (startDate.isBefore(today)) {
        // Calculate how many times we need to add times units to get a future date (or today).
        // We need to round up; the trick for doing so is count until yesterday and add 1.
        LocalDate yesterday = today.minusDays(1);
        long timesToAdd = period.between(startDate,yesterday) / times + 1;
        return startDate.plus(timesToAdd * times,period);
    } else {
        return startDate;
    }
}

为了演示该方法,我使用了这个小实用程序方法:

public static void demo(LocalDate startDate,int times) {
    LocalDate nextDate = getNextDate(startDate,period,times);
    System.out.format("getNextDate(%s,%s,%d) -> %s%n",startDate,times,nextDate);
}

现在让我们看看:

    demo(LocalDate.of(2020,Month.AUGUST,31),ChronoUnit.MONTHS,1);
    demo(LocalDate.of(2020,2);
    demo(LocalDate.of(2020,Month.MAY,30),Month.JUNE,2);
    
    System.out.println();
    
    demo(LocalDate.of(2020,Month.FEBRUARY,28),ChronoUnit.YEARS,29),4);
    
    System.out.println();
    
    demo(LocalDate.of(2020,ChronoUnit.DAYS,3);
    demo(LocalDate.of(2020,ChronoUnit.WEEKS,Month.SEPTEMBER,4),1);

目前运行时,输出为:

getNextDate(2020-08-31,Months,1) -> 2020-09-30
getNextDate(2020-08-31,2) -> 2020-10-31
getNextDate(2020-05-30,2) -> 2020-09-30
getNextDate(2020-06-30,2) -> 2020-10-30

getNextDate(2020-02-28,Years,1) -> 2021-02-28
getNextDate(2020-02-29,4) -> 2024-02-29

getNextDate(2020-02-29,Days,1) -> 2020-09-04
getNextDate(2020-02-29,3) -> 2020-09-05
getNextDate(2020-02-29,Weeks,2) -> 2020-09-12

getNextDate(2020-09-04,1) -> 2020-09-04

我应该说这与您在问题中所举的例子相符。

如果您无法避免使用老式的Date对象和您自己的Period枚举的实例,并且/或者您必不可少地需要老式的Date,则可以将其包装起来将我的方法转换为执行必要的转换的方法。首先,我将扩展您的枚举以了解其对应的ChronoUnit常量:

enum Period {
    DAY(ChronoUnit.DAYS),WEEK(ChronoUnit.WEEKS),MONTH(ChronoUnit.MONTHS),YEAR(ChronoUnit.YEARS);

    private final ChronoUnit unit;
    
    private Period(ChronoUnit unit) {
        this.unit = unit;
    }

    public ChronoUnit getUnit() {
        return unit;
    }
}

现在,包装方法可能看起来像这样;

public static Date getNextDate(Date startDate,Period period,int times) {
    ZoneId zone = ZoneId.of("America/Eirunepe");
    LocalDate startLocalDate = startDate.toInstant().atZone(zone).toLocalDate();
    LocalDate nextDate = getNextDate(startLocalDate,period.getUnit(),times);
    Instant startOfDay = nextDate.atStartOfDay(zone).toInstant();
    return Date.from(startOfDay);
}
,

您可以使用类Calendar来解决您的问题,

public static Date getNextDate(Date startDate,int period,int times) {

    Calendar calendar = Calendar.getInstance();
    calendar.setTime(startDate);
    calendar.add(period,times);
    return calendar.getTime();
}

句点是Calendar类中定义的一个int,您可以这样调用函数:

System.out.println(getNextDate(new Date(),Calendar.MONTH,1));
System.out.println(getNextDate(new Date(),3));
System.out.println(getNextDate(new Date(),Calendar.YEAR,1));

如果您确实需要使用enum,可以做到!