获取指定时间段过后的下一个有效日期,并相应地调整月份中的第几天
Get the next valid date after a specified period has passed and adjust the day of month accordingly
我有一个像这样的枚举
enum Period{DAY, WEEK, MONTH, YEAR}
我需要的是一个函数,它在设置月份日期时将给定期间的指定次数添加到 today
,以便它等于开始日期(如果结果有效)。
或者这样更容易理解:
假设您在每个月的 31 号(如适用)领取薪水。函数 return 是您将收到下一份薪水的下一个有效日期(从今天开始)。如果你在指定的时间间隔内每天、每周、每月、每年获取它,该功能可以区分在哪里。
它还处理无效日期
让我们看一个例子:
public static Date getNextDate(Date startDate, Period period, int times){
/*
Examples:
getNextDate(31.08.2020, MONTH, 1) -> 30.09.2020
getNextDate(31.08.2020, MONTH, 2) -> 31.10.2020
getNextDate(30.05.2020, MONTH, 2) -> 30.09.2020
getNextDate(30.06.2020, MONTH, 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, YEAR, 1) -> 28.02.2021 <- Edge case, as 2020 is a gap year
getNextDate(29.02.2020, YEAR, 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, DAY, 3) -> 05.09.2020
getNextDate(29.02.2020, WEEK, 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, MONTH, 1) -> 03.09.2020 (No change here, the date matches today)
*/
}
我实际上更愿意使用现代的 LocalDate API(只是输入目前是旧日期对象,但稍后会更改)
我希望我没有忘记任何边缘情况。
更新我所做的
//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;
}
我的getNextDate()
方法有效。 getNextDateAfterToday()
也有效,但对边缘情况没有 return 有效日期。示例 31.06.2020, MONTH,1
将在每个月的 30 日立即执行,并且即使该月有 31 天也不会跳回。对于 2020 年 9 月 30 日,它是正确的。但是对于 2020 年 10 月 31 日,它不会
您可以使用 class 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();
}
周期是在日历 class 中定义的一个整数,您可以这样调用您的函数:
System.out.println(getNextDate(new Date(), Calendar.MONTH, 1));
System.out.println(getNextDate(new Date(), Calendar.MONTH, 3));
System.out.println(getNextDate(new Date(), Calendar.YEAR, 1));
如果你真的需要使用你的enum
,你可以做到!
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));
我终于想出了一个办法(尽管对于我真正想要实现的目标来说,这看起来很复杂)。我把我的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,但将来会这样做。
这适用于我上面发布的所有测试用例。虽然我希望我没有错过重要的
… (although it seems way, way, way to complicated for what i really
wanted to achieve) …
你自己的方案还不错。我只是不能让挑战休息,所以我开始了。我相信它会更简单一些。
我要去 all-in java.time,现代 Java 日期和时间 API。我还跳过了您的 Period
枚举,因为预定义的 ChronoUnit
枚举可以达到目的。只是它还包括小时、分钟等在这里没有意义的单位,所以我们需要拒绝这些。
Date
class 设计不佳且已过时。能避免就避免(如果不能避免,最后给你解决方法)。
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, TemporalUnit period, int times) {
LocalDate nextDate = getNextDate(startDate, period, times);
System.out.format("getNextDate(%s, %s, %d) -> %s%n", startDate, period, times, nextDate);
}
现在让我们看看:
demo(LocalDate.of(2020, Month.AUGUST, 31), ChronoUnit.MONTHS, 1);
demo(LocalDate.of(2020, Month.AUGUST, 31), ChronoUnit.MONTHS, 2);
demo(LocalDate.of(2020, Month.MAY, 30), ChronoUnit.MONTHS, 2);
demo(LocalDate.of(2020, Month.JUNE, 30), ChronoUnit.MONTHS, 2);
System.out.println();
demo(LocalDate.of(2020, Month.FEBRUARY, 28), ChronoUnit.YEARS, 1);
demo(LocalDate.of(2020, Month.FEBRUARY, 29), ChronoUnit.YEARS, 1);
demo(LocalDate.of(2020, Month.FEBRUARY, 29), ChronoUnit.YEARS, 4);
System.out.println();
demo(LocalDate.of(2020, Month.FEBRUARY, 29), ChronoUnit.DAYS, 1);
demo(LocalDate.of(2020, Month.FEBRUARY, 29), ChronoUnit.DAYS, 3);
demo(LocalDate.of(2020, Month.FEBRUARY, 29), ChronoUnit.WEEKS, 2);
System.out.println();
demo(LocalDate.of(2020, Month.SEPTEMBER, 4), ChronoUnit.MONTHS, 1);
刚才运行时,输出为:
getNextDate(2020-08-31, Months, 1) -> 2020-09-30
getNextDate(2020-08-31, Months, 2) -> 2020-10-31
getNextDate(2020-05-30, Months, 2) -> 2020-09-30
getNextDate(2020-06-30, Months, 2) -> 2020-10-30
getNextDate(2020-02-28, Years, 1) -> 2021-02-28
getNextDate(2020-02-29, Years, 1) -> 2021-02-28
getNextDate(2020-02-29, Years, 4) -> 2024-02-29
getNextDate(2020-02-29, Days, 1) -> 2020-09-04
getNextDate(2020-02-29, Days, 3) -> 2020-09-05
getNextDate(2020-02-29, Weeks, 2) -> 2020-09-12
getNextDate(2020-09-04, Months, 1) -> 2020-09-04
我应该说它同意你在问题中的例子。
如果您无法避免拥有一个 old-fashioned Date
对象和一个您自己的 Period
枚举 and/or 的实例,那么您不可避免地需要一个 old-fashioned 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);
}
我有一个像这样的枚举
enum Period{DAY, WEEK, MONTH, YEAR}
我需要的是一个函数,它在设置月份日期时将给定期间的指定次数添加到 today
,以便它等于开始日期(如果结果有效)。
或者这样更容易理解: 假设您在每个月的 31 号(如适用)领取薪水。函数 return 是您将收到下一份薪水的下一个有效日期(从今天开始)。如果你在指定的时间间隔内每天、每周、每月、每年获取它,该功能可以区分在哪里。 它还处理无效日期
让我们看一个例子:
public static Date getNextDate(Date startDate, Period period, int times){
/*
Examples:
getNextDate(31.08.2020, MONTH, 1) -> 30.09.2020
getNextDate(31.08.2020, MONTH, 2) -> 31.10.2020
getNextDate(30.05.2020, MONTH, 2) -> 30.09.2020
getNextDate(30.06.2020, MONTH, 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, YEAR, 1) -> 28.02.2021 <- Edge case, as 2020 is a gap year
getNextDate(29.02.2020, YEAR, 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, DAY, 3) -> 05.09.2020
getNextDate(29.02.2020, WEEK, 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, MONTH, 1) -> 03.09.2020 (No change here, the date matches today)
*/
}
我实际上更愿意使用现代的 LocalDate API(只是输入目前是旧日期对象,但稍后会更改)
我希望我没有忘记任何边缘情况。
更新我所做的
//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;
}
我的getNextDate()
方法有效。 getNextDateAfterToday()
也有效,但对边缘情况没有 return 有效日期。示例 31.06.2020, MONTH,1
将在每个月的 30 日立即执行,并且即使该月有 31 天也不会跳回。对于 2020 年 9 月 30 日,它是正确的。但是对于 2020 年 10 月 31 日,它不会
您可以使用 class 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();
}
周期是在日历 class 中定义的一个整数,您可以这样调用您的函数:
System.out.println(getNextDate(new Date(), Calendar.MONTH, 1));
System.out.println(getNextDate(new Date(), Calendar.MONTH, 3));
System.out.println(getNextDate(new Date(), Calendar.YEAR, 1));
如果你真的需要使用你的enum
,你可以做到!
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));
我终于想出了一个办法(尽管对于我真正想要实现的目标来说,这看起来很复杂)。我把我的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,但将来会这样做。 这适用于我上面发布的所有测试用例。虽然我希望我没有错过重要的
… (although it seems way, way, way to complicated for what i really wanted to achieve) …
你自己的方案还不错。我只是不能让挑战休息,所以我开始了。我相信它会更简单一些。
我要去 all-in java.time,现代 Java 日期和时间 API。我还跳过了您的 Period
枚举,因为预定义的 ChronoUnit
枚举可以达到目的。只是它还包括小时、分钟等在这里没有意义的单位,所以我们需要拒绝这些。
Date
class 设计不佳且已过时。能避免就避免(如果不能避免,最后给你解决方法)。
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, TemporalUnit period, int times) {
LocalDate nextDate = getNextDate(startDate, period, times);
System.out.format("getNextDate(%s, %s, %d) -> %s%n", startDate, period, times, nextDate);
}
现在让我们看看:
demo(LocalDate.of(2020, Month.AUGUST, 31), ChronoUnit.MONTHS, 1);
demo(LocalDate.of(2020, Month.AUGUST, 31), ChronoUnit.MONTHS, 2);
demo(LocalDate.of(2020, Month.MAY, 30), ChronoUnit.MONTHS, 2);
demo(LocalDate.of(2020, Month.JUNE, 30), ChronoUnit.MONTHS, 2);
System.out.println();
demo(LocalDate.of(2020, Month.FEBRUARY, 28), ChronoUnit.YEARS, 1);
demo(LocalDate.of(2020, Month.FEBRUARY, 29), ChronoUnit.YEARS, 1);
demo(LocalDate.of(2020, Month.FEBRUARY, 29), ChronoUnit.YEARS, 4);
System.out.println();
demo(LocalDate.of(2020, Month.FEBRUARY, 29), ChronoUnit.DAYS, 1);
demo(LocalDate.of(2020, Month.FEBRUARY, 29), ChronoUnit.DAYS, 3);
demo(LocalDate.of(2020, Month.FEBRUARY, 29), ChronoUnit.WEEKS, 2);
System.out.println();
demo(LocalDate.of(2020, Month.SEPTEMBER, 4), ChronoUnit.MONTHS, 1);
刚才运行时,输出为:
getNextDate(2020-08-31, Months, 1) -> 2020-09-30 getNextDate(2020-08-31, Months, 2) -> 2020-10-31 getNextDate(2020-05-30, Months, 2) -> 2020-09-30 getNextDate(2020-06-30, Months, 2) -> 2020-10-30 getNextDate(2020-02-28, Years, 1) -> 2021-02-28 getNextDate(2020-02-29, Years, 1) -> 2021-02-28 getNextDate(2020-02-29, Years, 4) -> 2024-02-29 getNextDate(2020-02-29, Days, 1) -> 2020-09-04 getNextDate(2020-02-29, Days, 3) -> 2020-09-05 getNextDate(2020-02-29, Weeks, 2) -> 2020-09-12 getNextDate(2020-09-04, Months, 1) -> 2020-09-04
我应该说它同意你在问题中的例子。
如果您无法避免拥有一个 old-fashioned Date
对象和一个您自己的 Period
枚举 and/or 的实例,那么您不可避免地需要一个 old-fashioned 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);
}