无法使用JodaTime解析日期

时间:2017-09-14 13:00:20

标签: date jodatime datetime-format date-parsing android-jodatime

我一直试图用昨天的JodaTime来解析一个简单的约会,到目前为止我一直在失败......

这是我正在尝试解析的(随机)日期: 2017年9月14日(即使S大写不改变任何东西......)

这是我的代码

 DateTimeFormatter dateTimeFormat = DateTimeFormat.forPattern("yyyy-MM-dd");
 // The variable 'parsed' is a dynamic string. For now I set it to 2017-sept-14 
 DateTime dateTime = dateTimeFormat.parseDateTime(parsed);
 Log.d(TAG, "Parsed date = "+ dateTime.toString());

这是我的例外:

java.lang.IllegalArgumentException: Invalid format: "2017-sept-14" is malformed at "sept-14" at org.joda.time.format.DateTimeFormatter.parseDateTime(DateTimeFormatter.java:945)

我在这里缺少什么?

更新 实际上我从我的文本字段得到的是上面的表格,即日期 - 月 - 日(月份是3或4个字符长,具体取决于月份....) 因此,当我有2017年9月14日时,我希望得到的输出只是2017-09-14

4 个答案:

答案 0 :(得分:1)

Joda-Time 并不提供简单的方法。唯一(复杂)的方法是定义自己的DateTimeParser实现。您可能会从我那里找到另一个old SO-post中的实现方法,然后执行以下操作:

DateTimeParser monthParser = ...; // left as exercise to you

DateTimeFormatter joda = 
  new DateTimeFormatterBuilder()
  .appendPattern("yyyy-")
  .append(monthParser)
  .append("-dd")
  .toFormatter();
LocalDate ld = joda.parseLocalDate("2017-sept-14");

我不确定为什么Hugo的回答建议基于地图的自定义名称查找不适合您。也许月份名称是可变的而不是固定的(同月)。也许您只想检查月份名称是否以相同的前缀开头。如果是这样,那么我的lib Time4J可能会帮助你,因为它管理更多格式化/解析属性来控制解析,请参阅下面的简短示例(如果存在,它还管理其他尾随点):

String s = "2017-sept.-14";
ChronoFormatter<PlainDate> f =
  ChronoFormatter
    .setUp(PlainDate.class, new java.util.Locale("fr", "FR"))
    .addPattern("uuuu-MMM", PatternType.CLDR)
    .skipUnknown(c -> c == '.', 1)
    .addPattern("-dd", PatternType.CLDR)
    .build()
    .with(net.time4j.format.Attributes.PARSE_CASE_INSENSITIVE, true)
    .with(net.time4j.format.Attributes.PARSE_PARTIAL_COMPARE, true);
System.out.println(f.parse(s)); // 2017-09-14

对于Android,您可以用Time4A替换Time4J,并用实现ChronoCondition - 接口的匿名类替换给定的lambda表达式。

顺便说一句,每个库都可以使用固定月份名称的查找表。对于Joda,请参阅我上面提到的旧帖子作为链接。对于Java-8或三重后端,请参阅Hugo的答案。对于SimpleDateFormat,请参阅如何设置DateFormatSymbols的合适实例。对于Time4J,请参阅builder-API(类似于Java-8)。

关于SimpleDateFormat的最后一句话。即使它现在似乎对你有用(只是因为宽松的解析适用于旧的Java和Time4J,但有趣的是不适用于java.time - API),我仍然不会相信它在任何情况下,这个旧的解析器类也不是线程安全的。

答案 1 :(得分:0)

如果您想保留模式yyyy-MM-dd,则需要一个两位数的月份(09)。否则,请将您的模式更改为yyyy-MMMM-dd

答案 2 :(得分:0)

所以我不知道这是否正确,但这对我有用(我最终使用了SimpleDateFormat ......):

SimpleDateFormat sdfSource = new SimpleDateFormat("yyyy-MMM-dd");
SimpleDateFormat sdfDestination = new SimpleDateFormat("yyyy-MM-dd");
try {
      Date date = sdfSource.parse(parsed);
      String strOutput = sdfDestination.format(date);
      Log.d(TAG, "Parsed date = "+ strOutput);
    } catch (ParseException e) {
        e.printStackTrace();
    }

答案 3 :(得分:0)

Joda-Time以短格式接受月份名称(在大多数语言中,通常使用3个字母)或长格式(使用全名)。您的输入似乎是英文和4个字母,但不支持。

如果可以操作输入,您可以删除多余的字符,并确保月份名称只包含3个字母。

我还使用java.util.Locale指定月份名称是英文。如果您没有指定区域设置,它将使用系统默认值,并且不保证始终为英语,因此最好指定一个。

我还将其解析为LocalDate,因为它的toString()方法已经产生了你想要的输出:

String input = "2017-Sept-14";
input = input.replace("Sept", "Sep");
DateTimeFormatter dateTimeFormat = DateTimeFormat.forPattern("yyyy-MMM-dd").withLocale(Locale.ENGLISH);
LocalDate dateTime = dateTimeFormat.parseLocalDate(input);
System.out.println(dateTime);

输出结果为:

  

2017年9月14日

我假设语言环境是英语,但在爱沙尼亚语中,9月的短月名称是“9月”,所以你也可以这样做:

String input = "2017-Sept-14";
input = input.toLowerCase(); // et_EE locale accepts only "sept"
DateTimeFormatter dateTimeFormat = DateTimeFormat.forPattern("yyyy-MMM-dd")
    .withLocale(new Locale("et", "EE"));
LocalDate dateTime = dateTimeFormat.parseLocalDate(input);
System.out.println(dateTime);

或者您可以尝试使用系统的默认设置(根据SimpleDateFormat与法语区域设置一起使用的评论,因此上面的代码也可能有效。)

Java新日期/时间API

Joda-Time处于维护模式,正在被新的API取代,因此我不建议使用它来启动新项目。即使在joda's website中它也说:“请注意,Joda-Time被认为是一个很大程度上”完成“的项目。没有计划大的增强。如果使用Java SE 8,请迁移到java.time(JSR) -310)。“即可。

如果您不能(或不想)从Joda-Time迁移到新API,则可以忽略此部分。

在Android中,您可以使用ThreeTen Backport,这是Java 8新日期/时间类的绝佳后端。为了使其有效,您还需要ThreeTenABP(更多关于如何使用它here)。

您可以创建格式化程序,设置区域设置并将其解析为LocalDate

import org.threeten.bp.LocalDate;
import org.threeten.bp.format.DateTimeFormatter;
import org.threeten.bp.format.DateTimeFormatterBuilder;

DateTimeFormatter f = new DateTimeFormatterBuilder()
    // case insensitive (so it accepts Sept, sept, and so on)
    .parseCaseInsensitive()
    // pattern
    .appendPattern("yyyy-MMM-dd")
    // set locale
    .toFormatter(new Locale("et", "EE"));
System.out.println(LocalDate.parse("2017-Sept-14", f));

输出结果为:

  

2017年9月14日

或者只是尝试使用系统的默认语言环境(只需调用toFormatter()不带参数,它将使用系统默认值。)

或者,您可以创建自定义月份名称的地图,并在格式化程序中使用它。唯一的细节是你必须用所有月份的值填充它。我在9月份放了Sept,你可以相应填写其他月份:

// map of custom names for month
Map<Long, String> monthNames = new HashMap<>();
// put the names used in your input
monthNames.put(1L, "Jan");
monthNames.put(2L, "Feb");
monthNames.put(3L, "Mar");
monthNames.put(4L, "Apr");
monthNames.put(5L, "May");
monthNames.put(6L, "Jun");
monthNames.put(7L, "Jul");
monthNames.put(8L, "Aug");
monthNames.put(9L, "Sept");
monthNames.put(10L, "Oct");
monthNames.put(11L, "Nov");
monthNames.put(12L, "Dec");

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // case insensitive (so it accepts Sept, sept, and so on)
    .parseCaseInsensitive()
    // year
    .appendPattern("yyyy-")
    // month, using custom names
    .appendText(ChronoField.MONTH_OF_YEAR, monthNames)
    // day
    .appendPattern("-dd")
    // create formatter
    .toFormatter();

String input = "2017-Sept-14";
System.out.println(LocalDate.parse(input, fmt));