LocalDate 解析抛出异常‽

LocalDate parse throws exceptions‽

我正在进行以下编程练习:Unlucky Days。语句为:

Friday 13th or Black Friday is considered as unlucky day. Calculate how many unlucky days are in the given year.

Find the number of Friday 13th in the given year.

Input: Year as an integer.

Output: Number of Black Fridays in the year as an integer.

Examples:

unluckyDays(2015) == 3 unluckyDays(1986) == 1

首先我尝试了以下代码:

import java.time.*;
import java.time.format.*;
import java.util.Locale;

public class Kata {
  public static int unluckyDays/**/(int year) {
    System.out.println("\nyear: "+year);
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/M/dd");
    for(int i = 1; i <= 12; i++){
      LocalDate date = LocalDate.parse(String.format("%d/%d/13",year,i));
      DayOfWeek dow = date.getDayOfWeek();
      String output = dow.getDisplayName(TextStyle.FULL, Locale.US);
      System.out.println("\noutput: "+output);
    }
    return 0;
  }
}

我们得到以下异常:

java.time.format.DateTimeParseException: Text '2015/1/13' could not be parsed at index 4
    at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2046)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1948)
    at java.base/java.time.LocalDate.parse(LocalDate.java:428)
    at java.base/java.time.LocalDate.parse(LocalDate.java:413)
    at Kata.unluckyDays(Kata.java:10)

因此,正如我们所见,用于 LocalDate.parse(String.format("%d/%d/13",year,i)); 的字符串中索引 4 处的“/”是错误的。

然后我决定将格式更改为使用“-”而不是“/”

import java.time.*;
import java.time.format.*;
import java.util.Locale;

public class Kata {
  public static int unluckyDays/**/(int year) {
    System.out.println("\nyear: "+year);
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-M-dd");
    for(int i = 1; i <= 12; i++){
      LocalDate date = LocalDate.parse(String.format("%d-%d-13",year,i));
      DayOfWeek dow = date.getDayOfWeek();
      String output = dow.getDisplayName(TextStyle.FULL, Locale.US);
      System.out.println("\noutput: "+output);
    }
    return 0;
  }
}

对于前面的代码,抛出的异常是:

java.time.format.DateTimeParseException: Text '2015-1-13' could not be parsed at index 5
    at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2046)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1948)
    at java.base/java.time.LocalDate.parse(LocalDate.java:428)
    at java.base/java.time.LocalDate.parse(LocalDate.java:413)
    at Kata.unluckyDays(Kata.java:10)

正如我们所见,一位数字月份“1”存在问题。

为了继续,我将条件用 0 填充那些只有一个数字的月份:

import java.time.*;
import java.time.format.*;
import java.util.Locale;

public class Kata {
  public static int unluckyDays/**/(int year) {
    System.out.println("\nyear: "+year);
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-M-dd");
    int count = 0;
    for(int i = 1; i <= 12; i++){
      LocalDate date = LocalDate.parse(String.format("%d-%s-13",year,String.valueOf(i).length() < 2 ? "0"+i : i));
      DayOfWeek dow = date.getDayOfWeek();
      String output = dow.getDisplayName(TextStyle.FULL, Locale.US);
      System.out.println("\noutput: "+output);
      if(output.equals("Friday")){
        count++;  
      }
    }
    return count;
  }
}

在这种情况下它有效。但是,我们如何在 DateTimeFormatter.ofPattern("yyyy-M-dd"); 中表示月份只能有一个数字。

此外,我们应该如何使用不同的日期格式,例如:DateTimeFormatter.ofPattern("dd-M-yyyy");

因为我尝试过:

import java.time.*;
import java.time.format.*;
import java.util.Locale;

public class Kata {
  public static int unluckyDays/**/(int year) {
    System.out.println("\nyear: "+year);
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-M-yyyy");
    int count = 0;
    for(int i = 1; i <= 12; i++){
      LocalDate date = LocalDate.parse(String.format("13-%s-%d",String.valueOf(i).length() < 2 ? "0"+i : i,year));
      DayOfWeek dow = date.getDayOfWeek();
      String output = dow.getDisplayName(TextStyle.FULL, Locale.US);
      System.out.println("\noutput: "+output);
      if(output.equals("Friday")){
        count++;  
      }
    }
    return count;
  }
}

输出很有趣:

java.time.format.DateTimeParseException: Text '13-01-2015' could not be parsed at index 0
    at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2046)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1948)
    at java.base/java.time.LocalDate.parse(LocalDate.java:428)
    at java.base/java.time.LocalDate.parse(LocalDate.java:413)
    at Kata.unluckyDays(Kata.java:11)

据我们观察,几天来“13”在索引 0 处引起了异常,我不明白这一点。

此外我还阅读了:

我们怎么知道,为什么LocalDate解析会抛出异常‽

你必须传递 formatter 作为第二个参数

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-M-yyyy");
LocalDate date = LocalDate.parse("01-1-2019", formatter);

这里是计算黑色星期五的方法:

private static void unluckyDays(int year) {
    int blackFridays = 0;
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/M/dd");
    for (int i = 1; i <= 12; i++) {
        LocalDate date = LocalDate.parse(String.format("%d/%d/13", year, i), formatter);
        DayOfWeek dow = date.getDayOfWeek();
        if (dow.getValue() == 5) {
            blackFridays++;
        }
    }
    System.out.println(String.format("Year %s has %s black fridays", year, blackFridays));
}

你离成功不远了。您在这里有两个选择:

  • 要么使用 DateTimeFormatter 并将其传递给您的 LocalDate.parseLocalDate date = LocalDate.parse("13/01/2019", new DateTimeFormatter("dd/MM/yyyy");
    查看此网站以获取更多信息: https://www.baeldung.com/java-string-to-date
  • 或者,您可以使用方法 LocalDate.of(year, month, day);:在您的情况下,这将是 LocalDate date = LocalDate.of(year, i, 13);。如果您查看 javadoc,您会看到对于 month 参数,1 是一月,12 是十二月。基本上,您可以接受它。而且您不需要 DateTimeFormatterString.format...

我试过你的代码,两种方法都有效。

编辑:
我想回答你最后一个问题。每次调用 LocalDate.parse("anyStringRepresentingADate") 时,幕后发生的事情是 LocalDate 使用默认值 DateTimeFormatter:

public static LocalDate parse(CharSequence text) {
    return parse(text, DateTimeFormatter.ISO_LOCAL_DATE);
}

DateTimeFormatter.ISO_LOCAL_DATE'yyyy-MM-dd'
当您执行 LocalDate.parse("13/1/2020") 时,您的字符串与默认格式不匹配,因此出现异常。

流和 lambda

其他答案正确。为了好玩,这里有一些使用 lambda 语法、流和谓词的方法。

月流

来自 , this solution uses a stream of Month objects taken from that enum class via the Arrays 实用程序 class,1 月至 12 月。对于每个月,我们可以询问特定年份的月 13 日是否是星期五。

我们正在即时实例化一个 LocalDate,一个没有时间和时区的纯日期值。

int year = 2020 ;
long count = 
        Arrays                            // Utility class, `java.util.Arrays`.
        .stream(                          // Generate a stream from an array.
            Month.values()                // Generate an array from all the objects defined on this enum `Month`: An object for January, and February, and so on.
        )                                 // Returns a `Stream` object.
        .filter(                          // Applies a `Predicate` to test each object produced by the stream. Those that pass the test are fed into a new second stream.
            ( Month month ) ->            // For each `Month` enum object.
            LocalDate                     // Represent a date-only value, a year-month-day.
            .of( year , month , 13 )      // Instantiate a `LocalDate` from inputs for year number, `Month` enum object, and day number.
            .getDayOfWeek()               // Interrogate for the `DayOfWeek` enum object that represents the day-of-week for that particular date.
            .equals( DayOfWeek.FRIDAY )   // Ask if the day-of-week for this date is a Friday.
        )                                 // Returns a new 2nd `Stream` object.
        .count()                          // Returns the number of Friday-the-13th dates are being produced by this second stream. The year 2020 has two such dates.
;

看到这个code run live at IdeOne.com

日期流

在另一个解决方案中,对于开始和停止之间的每个日期,我们得到 StreamLocalDate 对象。请注意,日期范围是半开,其中开始为 inclusive,结束为 exclusive。因此,一年从当年的 1 日开始,一直到但不包括 下一个 年的 1 日。

对于流中的每个日期,我们使用 Predicate checking if (a) the day-of-month is thirteen, and (b) the DayOfWeek is Friday. All the LocalDate objects passing the test are put into a newly generated List.

进行测试
int input = 2020;
Year year = Year.of( input );
LocalDate start = year.atDay( 1 );
LocalDate stop = year.plusYears( 1 ).atDay( 1 );

List < LocalDate > fridayThe13ths =
        start
                .datesUntil( stop )
                .filter(
                        ( LocalDate localDate ) ->
                                ( localDate.getDayOfMonth() == 13 )
                                        &&
                                        localDate.getDayOfWeek().equals( DayOfWeek.FRIDAY )
                )
                .collect( Collectors.toList() )
;

转储到控制台。以标准 ISO 8601 格式生成文本。

System.out.println( "fridayThe13ths = " + fridayThe13ths );

fridayThe13ths = [2020-03-13, 2020-11-13]

为了验证这些结果确实是 Friday-the-13th,让我们生成表示它们值的字符串。我们为包括星期几的自动本地化输出定义了一个格式化程序。

DateTimeFormatter f = DateTimeFormatter.ofLocalizedDate( FormatStyle.FULL ).withLocale( Locale.US );
fridayThe13ths.forEach( ( LocalDate localDate ) -> System.out.println( localDate.format( f ) ) );

Friday, March 13, 2020

Friday, November 13, 2020