Java:如何获取下一次匹配模式
Java: How to get next time that matches pattern
是否有 easy/direct 方法来使用 DateTimeFormatter 模式来获取下一个与该模式匹配的 LocalDateTime 时间?
我想用它来轻松获取下一次事件应该发生的时间(可能是每天、每周、每月等)。例如,如果某个事件发生在“周一 12:00 AM”,我想在下周一 12:00 AM 获得 LocalDateTime。
/**Get next LocalDateTime that matches this input
*
* @param input a String for time matching the pattern: [dayOfWeek ][dayOfMonth ][month ][year ]<timeOfDay> <AM/PM>
* @return LocalDateTime representing the next time that matches the input*/
public LocalDateTime getNextTime(String input) {
LocalDateTime currentTime = LocalDateTime.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern("[eeee ][d ][MMMM ][u ]h:m a");
TemporalAccessor accessor = format.parse(input);
// TODO somehow get the next time (that's after currentTime) that matches this pattern
// LocalDateTime time = ???
return time;
}
我不能只做 LocalDateTime.from(accessor)
,因为输入中可能没有指定年、月或月中的某天。
为了澄清,这里有一些我想要的例子:
// if current date is Friday, January 1st, 2021 at 12:00 PM
// this should return a LocalDateTime for Monday, January 4th, 2021 12:00 AM
getNextTime("Monday 12:00 AM");
// should return Saturday, January 2nd, 2021 12:00 AM
getNextTime("12:00 AM");
// should return Tuesday, January 5th, 2021 12:00 AM
getNextTime("5 January 12:00 AM");
// should return Friday, January 8th, 2021 12:00 PM (must be AFTER current time)
getNextTime("Friday 12:00 PM");
如果您的输入格式正确并且始终使用英文,您可以在第一个 space 拆分输入并按如下方式使用:
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.Locale;
public class Example {
public static void main(String[] args) {
LocalDateTime desiredDay = getNextDayTime("Friday 12:00 AM");
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd.MM.yyyy hh:mm a");
System.out.println(dtf.format(desiredDay));
}
public static LocalDateTime getNextDayTime(String input){
String[] splited = input.split(" ", 2);
LocalTime localTime = LocalTime.parse(splited[1], DateTimeFormatter.ofPattern("hh:mm a", Locale.US));
LocalDateTime dateTime = LocalDateTime.now().with(localTime);
LocalDateTime desiredDay = dateTime.with(TemporalAdjusters.next(DayOfWeek.valueOf(splited[0].toUpperCase())));
return desiredDay;
}
}
不,没有简单或直接的方法来完成您的要求。它涉及相当多的编码。您基本上有 16 个案例,因为年、月、月中的每一天和一周中的每一天可能存在也可能不存在。而你或多或少将不得不分别处理每个案例。
也未必有下一次了。如果年份是 2019 年,则没有。如果字符串是 Friday 12 January 2021 2:00 AM
,则没有,因为 1 月 12 日是星期二,而不是星期五。
private static DateTimeFormatter format = DateTimeFormatter
.ofPattern("[eeee ][uuuu ][d ][MMMM ][uuuu ]h:m a", Locale.ENGLISH);
// input = [dayOfWeek] [dayOfMonth] [month] [year] <timeOfDay> <AM/PM>
public static LocalDateTime next(String text) {
TemporalAccessor accessor;
try {
accessor = format.parse(text);
} catch (DateTimeParseException dtpe) {
return null;
}
LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault());
LocalTime parsedTime = LocalTime.from(accessor);
LocalDate earliest = now.toLocalDate();
if (parsedTime.isBefore(now.toLocalTime())) {
earliest = earliest.plusDays(1);
}
return resolveYearMonthDomDow(earliest, accessor).atTime(parsedTime);
}
private static LocalDate resolveYearMonthDomDow(LocalDate earliest, TemporalAccessor accessor) {
if (accessor.isSupported(ChronoField.YEAR)) {
Year parsedYear = Year.from(accessor);
if (parsedYear.isBefore(Year.from(earliest))) {
return null;
}
return resolveMonthDomDow(parsedYear, earliest, accessor);
} else {
Year candidateYear = Year.from(earliest);
while (true) {
LocalDate resolved = resolveMonthDomDow(candidateYear, earliest, accessor);
if (resolved != null) {
return resolved;
}
candidateYear = candidateYear.plusYears(1);
}
}
}
private static LocalDate resolveMonthDomDow(Year year, LocalDate earliest, TemporalAccessor accessor) {
if (accessor.isSupported(ChronoField.MONTH_OF_YEAR)) {
YearMonth knownYm = year.atMonth(accessor.get(ChronoField.MONTH_OF_YEAR));
if (knownYm.isBefore(YearMonth.from(earliest))) {
return null;
}
return resolveDomDow(knownYm, earliest, accessor);
} else {
YearMonth candidateYearMonth = YearMonth.from(earliest);
if (candidateYearMonth.getYear() < year.getValue()) {
candidateYearMonth = year.atMonth(Month.JANUARY);
}
while (candidateYearMonth.getYear() == year.getValue()) {
LocalDate resolved = resolveDomDow(candidateYearMonth, earliest, accessor);
if (resolved != null) {
return resolved;
}
candidateYearMonth = candidateYearMonth.plusMonths(1);
}
return null;
}
}
private static LocalDate resolveDomDow(YearMonth ym, LocalDate earliest, TemporalAccessor accessor) {
if (accessor.isSupported(ChronoField.DAY_OF_MONTH)) {
int dayOfMonth = accessor.get(ChronoField.DAY_OF_MONTH);
if (dayOfMonth > ym.lengthOfMonth()) {
return null;
}
LocalDate resolved = ym.atDay(dayOfMonth);
if (resolved.isBefore(earliest)) {
return null;
} else {
return resolveDow(resolved, accessor);
}
} else {
LocalDate candidateDate = earliest;
if (YearMonth.from(earliest).isBefore(ym)) {
candidateDate = ym.atDay(1);
}
while (YearMonth.from(candidateDate).equals(ym)) {
LocalDate resolved = resolveDow(candidateDate, accessor);
if (resolved != null) {
return resolved;
}
candidateDate = candidateDate.plusDays(1);
}
return null;
}
}
private static LocalDate resolveDow(LocalDate date, TemporalAccessor accessor) {
if (accessor.isSupported(ChronoField.DAY_OF_WEEK)) {
if (date.getDayOfWeek().getValue() == accessor.get(ChronoField.DAY_OF_WEEK)) {
return date;
} else {
return null;
}
} else {
return date;
}
}
让我们试试看:
String input = "Monday 12:00 AM";
// get the next time that matches this pattern
LocalDateTime time = next(input);
System.out.println(time);
我刚才运行时的输出(2021年1月11日星期一晚上):
2021-01-18T00:00
所以下周一。看起来不错。
另一个例子,表明闰年得到尊重:
String input = "Wednesday 29 February 12:00 AM";
2040-02-29T00:00
我的代码中很可能存在错误,但基本思路是可行的。
一天中的时间没有问题。挑战在于日期。我正在使用一天中的时间来确定今天的日期是否是最早的候选日期。如果现在时间已经超过字符串中的时间,则最早可能的日期是明天。对于您的示例字符串 Monday 12:00 AM
,实际上总是如此:它总是在午夜 12 点之后。
你在 Monday 25 12:00 AM
中有歧义,因为 25 可能是一年(几千年前)或一个月中的某一天。我坚持使用四位数年份来解决它。因此,如果星期几开头或紧跟其后的数字有四位数,则为年,否则为月中的某一天。我使用的格式化程序看起来很有趣,一年来了两次。我需要这个来强制解析在尝试月份日期之前先尝试年份,否则有时需要四位数字作为月份日期。这反过来意味着格式化程序接受的格式太多了。我想这在实践中不会有问题。
是否有 easy/direct 方法来使用 DateTimeFormatter 模式来获取下一个与该模式匹配的 LocalDateTime 时间?
我想用它来轻松获取下一次事件应该发生的时间(可能是每天、每周、每月等)。例如,如果某个事件发生在“周一 12:00 AM”,我想在下周一 12:00 AM 获得 LocalDateTime。
/**Get next LocalDateTime that matches this input
*
* @param input a String for time matching the pattern: [dayOfWeek ][dayOfMonth ][month ][year ]<timeOfDay> <AM/PM>
* @return LocalDateTime representing the next time that matches the input*/
public LocalDateTime getNextTime(String input) {
LocalDateTime currentTime = LocalDateTime.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern("[eeee ][d ][MMMM ][u ]h:m a");
TemporalAccessor accessor = format.parse(input);
// TODO somehow get the next time (that's after currentTime) that matches this pattern
// LocalDateTime time = ???
return time;
}
我不能只做 LocalDateTime.from(accessor)
,因为输入中可能没有指定年、月或月中的某天。
为了澄清,这里有一些我想要的例子:
// if current date is Friday, January 1st, 2021 at 12:00 PM
// this should return a LocalDateTime for Monday, January 4th, 2021 12:00 AM
getNextTime("Monday 12:00 AM");
// should return Saturday, January 2nd, 2021 12:00 AM
getNextTime("12:00 AM");
// should return Tuesday, January 5th, 2021 12:00 AM
getNextTime("5 January 12:00 AM");
// should return Friday, January 8th, 2021 12:00 PM (must be AFTER current time)
getNextTime("Friday 12:00 PM");
如果您的输入格式正确并且始终使用英文,您可以在第一个 space 拆分输入并按如下方式使用:
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.Locale;
public class Example {
public static void main(String[] args) {
LocalDateTime desiredDay = getNextDayTime("Friday 12:00 AM");
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd.MM.yyyy hh:mm a");
System.out.println(dtf.format(desiredDay));
}
public static LocalDateTime getNextDayTime(String input){
String[] splited = input.split(" ", 2);
LocalTime localTime = LocalTime.parse(splited[1], DateTimeFormatter.ofPattern("hh:mm a", Locale.US));
LocalDateTime dateTime = LocalDateTime.now().with(localTime);
LocalDateTime desiredDay = dateTime.with(TemporalAdjusters.next(DayOfWeek.valueOf(splited[0].toUpperCase())));
return desiredDay;
}
}
不,没有简单或直接的方法来完成您的要求。它涉及相当多的编码。您基本上有 16 个案例,因为年、月、月中的每一天和一周中的每一天可能存在也可能不存在。而你或多或少将不得不分别处理每个案例。
也未必有下一次了。如果年份是 2019 年,则没有。如果字符串是 Friday 12 January 2021 2:00 AM
,则没有,因为 1 月 12 日是星期二,而不是星期五。
private static DateTimeFormatter format = DateTimeFormatter
.ofPattern("[eeee ][uuuu ][d ][MMMM ][uuuu ]h:m a", Locale.ENGLISH);
// input = [dayOfWeek] [dayOfMonth] [month] [year] <timeOfDay> <AM/PM>
public static LocalDateTime next(String text) {
TemporalAccessor accessor;
try {
accessor = format.parse(text);
} catch (DateTimeParseException dtpe) {
return null;
}
LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault());
LocalTime parsedTime = LocalTime.from(accessor);
LocalDate earliest = now.toLocalDate();
if (parsedTime.isBefore(now.toLocalTime())) {
earliest = earliest.plusDays(1);
}
return resolveYearMonthDomDow(earliest, accessor).atTime(parsedTime);
}
private static LocalDate resolveYearMonthDomDow(LocalDate earliest, TemporalAccessor accessor) {
if (accessor.isSupported(ChronoField.YEAR)) {
Year parsedYear = Year.from(accessor);
if (parsedYear.isBefore(Year.from(earliest))) {
return null;
}
return resolveMonthDomDow(parsedYear, earliest, accessor);
} else {
Year candidateYear = Year.from(earliest);
while (true) {
LocalDate resolved = resolveMonthDomDow(candidateYear, earliest, accessor);
if (resolved != null) {
return resolved;
}
candidateYear = candidateYear.plusYears(1);
}
}
}
private static LocalDate resolveMonthDomDow(Year year, LocalDate earliest, TemporalAccessor accessor) {
if (accessor.isSupported(ChronoField.MONTH_OF_YEAR)) {
YearMonth knownYm = year.atMonth(accessor.get(ChronoField.MONTH_OF_YEAR));
if (knownYm.isBefore(YearMonth.from(earliest))) {
return null;
}
return resolveDomDow(knownYm, earliest, accessor);
} else {
YearMonth candidateYearMonth = YearMonth.from(earliest);
if (candidateYearMonth.getYear() < year.getValue()) {
candidateYearMonth = year.atMonth(Month.JANUARY);
}
while (candidateYearMonth.getYear() == year.getValue()) {
LocalDate resolved = resolveDomDow(candidateYearMonth, earliest, accessor);
if (resolved != null) {
return resolved;
}
candidateYearMonth = candidateYearMonth.plusMonths(1);
}
return null;
}
}
private static LocalDate resolveDomDow(YearMonth ym, LocalDate earliest, TemporalAccessor accessor) {
if (accessor.isSupported(ChronoField.DAY_OF_MONTH)) {
int dayOfMonth = accessor.get(ChronoField.DAY_OF_MONTH);
if (dayOfMonth > ym.lengthOfMonth()) {
return null;
}
LocalDate resolved = ym.atDay(dayOfMonth);
if (resolved.isBefore(earliest)) {
return null;
} else {
return resolveDow(resolved, accessor);
}
} else {
LocalDate candidateDate = earliest;
if (YearMonth.from(earliest).isBefore(ym)) {
candidateDate = ym.atDay(1);
}
while (YearMonth.from(candidateDate).equals(ym)) {
LocalDate resolved = resolveDow(candidateDate, accessor);
if (resolved != null) {
return resolved;
}
candidateDate = candidateDate.plusDays(1);
}
return null;
}
}
private static LocalDate resolveDow(LocalDate date, TemporalAccessor accessor) {
if (accessor.isSupported(ChronoField.DAY_OF_WEEK)) {
if (date.getDayOfWeek().getValue() == accessor.get(ChronoField.DAY_OF_WEEK)) {
return date;
} else {
return null;
}
} else {
return date;
}
}
让我们试试看:
String input = "Monday 12:00 AM";
// get the next time that matches this pattern
LocalDateTime time = next(input);
System.out.println(time);
我刚才运行时的输出(2021年1月11日星期一晚上):
2021-01-18T00:00
所以下周一。看起来不错。
另一个例子,表明闰年得到尊重:
String input = "Wednesday 29 February 12:00 AM";
2040-02-29T00:00
我的代码中很可能存在错误,但基本思路是可行的。
一天中的时间没有问题。挑战在于日期。我正在使用一天中的时间来确定今天的日期是否是最早的候选日期。如果现在时间已经超过字符串中的时间,则最早可能的日期是明天。对于您的示例字符串 Monday 12:00 AM
,实际上总是如此:它总是在午夜 12 点之后。
你在 Monday 25 12:00 AM
中有歧义,因为 25 可能是一年(几千年前)或一个月中的某一天。我坚持使用四位数年份来解决它。因此,如果星期几开头或紧跟其后的数字有四位数,则为年,否则为月中的某一天。我使用的格式化程序看起来很有趣,一年来了两次。我需要这个来强制解析在尝试月份日期之前先尝试年份,否则有时需要四位数字作为月份日期。这反过来意味着格式化程序接受的格式太多了。我想这在实践中不会有问题。