【问题标题】:Parsing date string (MM-dd) to java date in default year将日期字符串(MM-dd)解析为默认年份的 java 日期
【发布时间】:2012-07-14 16:02:22
【问题描述】:

我想将 MM-dd 格式的字符串解析为 java 日期。由于未指定年份,因此解析日期应为当年。应该只解析有效的日期字符串,所以我应该在SimpleDateFormat 中使用setLenient(false)

public static Date parseDate(String ds) throws ParseException {
    SimpleDateFormat df = new SimpleDateFormat("MM-dd");
    df.setLenient(false);
    Date d = df.parse(ds);
    Calendar cal = Calendar.getInstance();
    int year = cal.get(Calendar.YEAR);
    cal.setTime(d);
    cal.set(Calendar.YEAR, year);
    return cal.getTime();
}

在我通过参数“02-29”之前,这似乎运作良好。今年(2012)是闰年,2012-02-29是有效日期,“02-29”应该已经解析成功了。

我发现当我在SimpleDateFormat中没有指定年份部分时,它会解析到1970年。而1970年不是闰年,“02-29”无法解析。因此,解析到 1970 年的日期并在解析后设置当前年份的策略并不完美。

在 Java 中解析 MM-dd 格式字符串到日期(日期应设置为当前年份)的最佳方法是什么?

PS1:我搜索了这个话题,在本站找到了很多问题和答案,但都没有找到满意的答案。 PS2:df.setLenient(false); 很重要,因为只有有效的日期字符串才能成功解析。不应解析“01-32”、“02-30”等无效日期字符串。

提前致谢。

【问题讨论】:

  • 您可以使用“yyyy-MM-dd”并在解析之前在字符串中添加实际年份。
  • 您能解释一下为什么您不接受我的回答吗?
  • 我建议你不要使用SimpleDateFormatDateCalendar。这些类设计不佳且过时已久,尤其是第一个类是出了名的麻烦。而是使用LocalDateDateTimeFormatterBuilderDateTimeFormatter,均来自java.time, the modern Java date and time API

标签: java date calendar date-format


【解决方案1】:

这可能被认为有点 hacky,但您总是可以在解析之前将年份添加到日期字符串的末尾,如下所示:

ds += "-" + Calendar.getInstance().get(Calendar.YEAR);
SimpleDateFormat df = new SimpleDateFormat("MM-dd-yyyy");
// Parse date as usual

【讨论】:

  • 这并不能完全回答这个问题,原因很重要:如果您不知道输入字符串是否有年份。在您建议您可以先检查之前:在更具体的情况下,您甚至可能不知道输入日期的格式。假设您有 N 个格式化程序尝试解析各种已知日期格式的输入字符串,如果你可以在解析器上设置一个默认日期来处理那些没有提供年份的日期,那就太好了。
【解决方案2】:

像在代码中那样从日历中获取年份,将解析格式字符串设置为MM-dd-yyyy,然后这样做

 Date d = df.parse(ds + "-" + year);

【讨论】:

    【解决方案3】:

    如果您知道您的字符串格式正确,那么关于附加当前年份的其他答案就足够了。

    如果您需要处理未知格式的输入字符串(您不确定年份是否在字符串上),您可以首先尝试使用完整格式解析日期,然后使用较短的格式年份的覆盖。

    public static Date parseDate(String ds) throws ParseException {
        SimpleDateFormat fullFormat = new SimpleDateFormat("MM-dd-yyyy");
        fullFormat.setLenient(false);
        try {
          return fullFormat.parse(ds);
        } catch (ParseException e) {}
    
        // Full format unsuccessful. Attempt short format.
        SimpleDateFormat shortFormat = new SimpleDateFormat("MM-dd");
        shortFormat.setLenient(false);
        Date d = shortFormat.parse(ds);
        Calendar cal = Calendar.getInstance();
        int year = cal.get(Calendar.YEAR);
        cal.setTime(d);
        cal.set(Calendar.YEAR, year);
        return cal.getTime();
    }
    

    奖励:如果您出于某种原因想要一个“包罗万象”的解析器,请定义一堆非宽松的日期格式并逐个检查它们。请注意,顺序很重要;第一个匹配的将返回。如果要设置默认年份,则必须更进一步,检查输入中是否以某种方式引用了默认的 1970 年:

    public static Date parseDate(String ds) throws ParseException {
      Calendar cal = Calendar.getInstance();
      int currentYear = cal.get(Calendar.YEAR);
    
      for (DateFormat knownFormat : knownFormats) {
        try {
          Date d = knownFormat.parse(ds);
          cal.setTime(d);
    
          if (cal.get(Calendar.YEAR) == 1970 && !ds.contains("70")) {
            cal.set(Calendar.YEAR, currentYear);
          }
    
          return cal.getTime();
        } catch (ParseException e) {}
      }
    
      throw new ParseException("Unknown date format for String: " + ds);
    }
    

    【讨论】:

    • 感谢您的贡献。这个问题是在 2012 年提出的,并且比使用 DateSimpleDateFormatCalendar 是合理的。在 2019 年使用这些课程发布答案是误入歧途。我们在java.time, the modern Java date and time API 中做得更好。
    • 这几乎没有误导性,因为 JDK7 上仍有大量代码库。提供现代替代方案很好,但历史性答案不会导致 folx 误入歧途。
    • 感谢您的回复。意见不一。如果您在回答中声明它适用于 Java 4(对于讨厌各种外部依赖项的人最多适用于 7 个),我将能够理解您的观点。我的观点和我之前写的一样。
    【解决方案4】:

    tl;博士

    以 MM-dd 格式解析字符串……在当年

    MonthDay               // Represent a month-day as such, in a class designed for that purpose.
    .parse (               // By default parses strings in standard ISO 8601 format.
        "--" + "02-29"     // Prepending a double-hyphen to make this input comply with ISO 8601.
    )                      // Returns a `MonthDay` object.
    .atYear(               // Get the date of this month-day in a specified year.
        Year.now( ZoneId.of( "Asia/Tokyo" ) ).getValue()  // Getting current year requires a time zone.
    )                      // Returns a `LocalDate` object, a year-month-day without time zone and without time-of-day. 
    

    看到这个code run live at IdeOne.com

    2019-02-28

    java.time

    现代解决方案使用 Java 8 及更高版本中内置的行业领先的 java.time 类,并具有可用于 Java 6 和 7 以及早期 Android 的后向端口。

    MonthDay

    以适当命名的MonthDay 类表示月份。

    ISO 8601 中定义的月日标准格式是--MM-DD,其中第一个破折号是年份的占位符。 java.time 类中默认使用 ISO 8601 格式来解析/生成字符串。

    您的输入几乎符合要求。您可以使用 DateTimeFormatter 对象定义格式化模式。但我只会在输入前加上--

    String input = "02-29" ;
    String inputModified = "--" + input ;
    

    然后默认解析。

    MonthDay md = MonthDay.parse( inputModified ) ;
    

    看到这个code run live at IdeOne.com

    md.toString(): --02-29

    闰年

    请注意,您的leap year 问题消失了。通过使用真正代表月份和日期而不是时刻的适当类型,我们不必担心闰年。

    要获取本月的日期,只需调用MonthDay::atYear 以获取LocalDate 对象。通过年号。

    LocalDate leapYear2012 = md.atYear( 2012 ) ;
    

    leapYear2012.toString(): 2012-02-29

    当年

    在当年获得一个日期可能会让您感到惊讶。请注意,获取当前年份需要获取当前日期。获取当前日期需要时区

    时区对于确定日期至关重要。对于任何给定的时刻,日期在全球范围内因区域而异。例如,Paris France 中午夜过后几分钟是新的一天,而 Montréal Québec 中仍然是“昨天”。

    如果没有指定时区,JVM 会隐式应用其当前的默认时区。该默认值可能在运行时(!)期间change at any moment,因此您的结果可能会有所不同。最好将您想要/预期的时区明确指定为参数。如果您想使用 JVM 当前的默认时区,请通过调用 ZoneId.systemDefault() 明确您的意图。如果关键,请与您的用户确认该区域。

    Continent/Region 的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用 2-4 个字母的缩写,例如 ESTIST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。

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

    在我们的例子中,我们只关心年份。所以我们可以使用Year 类而不是LocalDate。但与时区相同的想法。如果当前时刻恰好是在新年前夜/新年前夜切换,那么全球各地的年份会因时区而异。

    ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
    Year y = Year.now( z ) ;
    LocalDate currentYear = md.atYear( y.getValue() ) ;
    

    currentYear.toString(): 2019-02-28

    请注意,上述结果中的闰年是自动处理的。 2019年没有2月29日,所以java.time调整为28日。

    解析为LocalDate

    或者,您可以直接解析为LocalDate。您需要使用 DateTimeFormatterBuilder 类来构建默认为特定年份的 DateTimeFormatter

    类似这样的:

    ZoneId zKolkata = ZoneId.of( "Asia/Kolkata" ) ;
    long yearNumber = Year.now( zKolkata ).getValue() ;
    DateTimeFormatter formatter = new DateTimeFormatterBuilder().parseDefaulting( ChronoField.YEAR , yearNumber ).appendPattern( "MM-dd").toFormatter() ;
    LocalDate ld = LocalDate.parse( "02-28" , formatter ) ;
    System.out.println( "ld.toString(): " + ld ) ;
    

    但我不建议这样做。 MonthDay 对象的方法对于您的问题、解决方案和意图更加清晰。另一个好处:如果你得到这样的输入,我怀疑你可能需要像这样处理月日,并且使用 MonthDay 类你手头有一个对象来完成这项工作。


    关于java.time

    java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

    要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

    Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

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

    从哪里获得 java.time 类?

    【讨论】:

    • 感谢您回答这个旧的(过时的)问题。当时 Java8 不可用,但可以使用 JodaTime。还有MonthDay...我不知道这门课。再次感谢。
    • @ntalbs 请记住,这些答案是给后代的,是给后来阅读此页面的三千人的,而不是给你个人的。 Stack Overflow 更像是维基百科,而不是聊天室。至于MonthDay,是的,java.time 有许多非常方便的类——在由同一个人 Stephen Colebourne 领导的 ThreeTen-Extra 项目中(他还领导Joda-Time)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-10
    • 1970-01-01
    • 1970-01-01
    • 2016-01-30
    • 1970-01-01
    相关资源
    最近更新 更多