【问题标题】:Cannot parse date using JodaTime无法使用 JodaTime 解析日期
【发布时间】:2018-02-23 11:37:52
【问题描述】:

从昨天开始,我一直在尝试使用 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-sept-14 时,我想得到的输出只是 2017-09-14

【问题讨论】:

  • 我认为模式中的“MM”期望“Sep”作为月份的缩写。而“MMM”通向“九月”。所以“九月”是你的问题。
  • 但是如果我输入 May/may 怎么办?我还需要砍掉最后一个角色吗? (我记得我的约会是动态的......)
  • 我不知道。但我认为您只需确保该月的长度为 3 个字符。
  • 我刚试过你的提议..还是同样的崩溃:java.lang.IllegalArgumentException: Invalid format: "2017-sep-14" is malformed at "sep-14"

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


【解决方案1】:

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-09-14

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

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);

或者您可以尝试使用系统的默认设置(基于您的 cmets,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-09-14

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


(可选)您可以创建自定义月份名称的地图并在格式化程序中使用它。唯一的细节是你必须用所有月份的值来填充它。我在九月输入了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));

【讨论】:

  • 嗨,我一开始确实尝试过 ThreeTen Backport,虽然我承认我不必写下你刚才所做的所有事情......实际上我做了一些谎言:DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder( ); dateTimeFormatterBuilder.parseCaseSensitive(); dateTimeFormatterBuilder.appendPattern("yyyy-MMM-dd"); DateTimeFormatter dateTimeFormatter = dateTimeFormatterBuilder.toFormatter(); LocalDate localDate = LocalDate.parse(parsed, dateTimeFormatter);但我没有工作,所以我不得不切换到普通的 SimpleDateFormat...
  • @ClaudeHangui 这很奇怪。使用您的代码,我无法解析2017-Sept-14,只能解析sep。您的默认语言环境是什么?
  • 就像我在砍掉一个字符之前所说的那样似乎不是最好的主意......因为像 May 这样的东西只有 3 个字符......当然可以做出一个 switch 声明决定何时删除最后一个字符,但这似乎很乏味(至少在我看来..)..使用 SimpleDateFormat 解决了 4-5 行的问题
  • @ClaudeHangui 我刚刚添加了一些关于语言环境的 cmets,但恐怕我无法在这个问题上添加更多内容。我根本无法在我的环境中重现相同的行为,所以这就是我能提供的所有帮助。但是很好,您找到了SimpleDateFormat 的解决方案。
【解决方案2】:

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,您可以将 Time4J 替换为 Time4A,并将给定的 lambda 表达式替换为实现 ChronoCondition-接口的匿名类。

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

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

【讨论】:

  • 太棒了!我没有考虑扩展 DateTimeParser,这似乎是 Joda-Time 的唯一方法。
【解决方案3】:

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

【讨论】:

  • 我从文本字段中获取 2014-Sept-14,所以我只是尝试更改为 yyyy-MMM-dd,但到目前为止仍然没有结果......我仍然有同样的问题:java.lang.IllegalArgumentException:无效格式:“2017-sept-14”在“sept-14”格式错误
  • “2014-Sept-14”的模式是 yyyy-MMMM-dd 而不是 yyyy-MMM-dd
  • MMMM 只解析“September”,而不是“Sept”
【解决方案4】:

所以我不知道这是否是正确的方法,但这对我有用(我最终使用 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();
    }

【讨论】:

  • 您使用的是哪个 Java 版本?我用 Java 7 和 8 进行了测试,但都没有成功。
  • 我正在使用 Java 7....你真的试过 2017-sept-14 吗??...因为我刚刚用日历的其他月份测试了它,到目前为止一切都很好!
  • 是的,我尝试了 2017-sept-14SimpleDateFormat 没有工作。反正我发了an answer,去看看吧……
  • 这就是我所拥有的.. SimpleDateFormat sdfSource = new SimpleDateFormat("yyyy-MMM-dd"); SimpleDateFormat sdfDestination = new SimpleDateFormat("yyyy-MM-dd");尝试 { Date date = sdfSource.parse(parsed);返回 sdfDestination.format(date); } catch (ParseException e) { e.printStackTrace(); }
  • 打印Locale.getDefault()会得到什么?
猜你喜欢
  • 1970-01-01
  • 2021-02-09
  • 2016-04-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-30
相关资源
最近更新 更多