【问题标题】:Getting error java.text.ParseException: Unparseable date: (at offset 0) even if the Simple date format and string value are identical出现错误 java.text.ParseException: Unparseable date: (at offset 0) 即使简单日期格式和字符串值相同
【发布时间】:2018-02-27 08:24:26
【问题描述】:

即使要检查的格式和字符串值相同,我也总是遇到解析异常。 代码如下:

String format = "EEE MMM dd HH:mm:ss z yyyy";
String value = "Mon Sep 18 10:30:06 MST 2017";
public static boolean isValidFormat(String format, String value) {
    Date date = null;
    try {
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        date = sdf.parse(value); // here it breaks
        if (!value.equals(sdf.format(date))) {
            date = null;
        }
    } catch (ParseException ex) {
        ex.printStackTrace(); //java.text.ParseException: Unparseable date: 
                                "Mon Sep 18 10:30:06 MST 2017" (at offset 0)
    }
    return date != null;
}

【问题讨论】:

  • 你想做什么?告诉我然后我会看看如何给出答案的解决方案
  • 实际上是我从数据库中获取的上述日期“值”,我需要将上述日期值转换为不同的格式。所以首先我将上面的字符串值更改为日期,然后使用另一个 sdf 将获得的日期转换为我需要的格式
  • 要获取dateFormatter()的代码吗?
  • 我认为问题出在时区上,当我在没有 z 和 MST 的情况下运行您的代码时,它返回 true,也许 MST 它不是 SimpleDateFormatter 所期望的方式。
  • 我给出了日期格式化程序的代码,请检查它是否适合您。如果有效,请不要忘记将其标记为正确答案。

标签: android date simpledateformat


【解决方案1】:

它表示您的日期时间字符串在索引 0 处不可解析。索引 0 是它表示 Mon 的位置,因此三个字母的时区缩写不是第一个嫌疑人。语言环境是。 “星期一”在英语中是星期一的缩写,但在许多其他语言中却不是。因此,如果您的设备具有非英语语言设置(甚至可能最近已更改),这将充分解释您的观察结果。

短视的解决办法是

        SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.ROOT);

我使用Locale.ROOT 表示不应进行任何语言特定的处理。如果您的字符串是英语,因为英语通常是全球计算中使用的语言,我会认为这个选择是合适的。另一方面,如果它是英文的,因为它来自说英语的区域设置,那么该区域设置将是正确的使用。

通过此更改,您的代码在我的计算机上将您的日期格式化为Mon Sep 18 11:30:06 MDT 2017,正如您所见,它与我们开始时的值不同,因此您的方法返回 false。我的 JVM 将 MST 理解为山区标准时间,然后假定夏令时 (DST) 为 9 月并相应地格式化了字符串。

ThreeTenABP

也就是说,DateSimpleDateFormat 是早已过时的课程。您应该考虑摆脱它们并改用现代 Java 日期和时间 API。在 Android 上,您可以在 ThreeTenABP 中获得它,请参阅 this question: How to use ThreeTenABP in Android Project。现在你可以这样做了:

    DateTimeFormatter dtf = DateTimeFormatter.ofPattern(format, Locale.ROOT);
    try {
        return ZonedDateTime.parse(value, dtf).format(dtf).equals(value); 
    } catch (DateTimeParseException dtpe) {
        dtpe.printStackTrace();
        return false;
    }

这与上面的行为相同。

三个字母的时区缩写

您应该尽可能避免使用三个和四个字母的时区缩写。它们不是标准化的并且通常是模棱两可的。例如,MST 可能表示马来西亚标准时间或山地标准时间。后者甚至不是一个完整的时区,因为 MDT 用于一年中的大部分时间,这导致了我在上面所说的观察到的问题。

相反,看看您是否可以获得 ISO 8601 格式的字符串,例如 2017-09-18T10:30:06+08:00。第二好的,只是得到一些明确的东西。一种方法是包含与 UTC 的偏移量,而不是时区 ID(或两者兼有)。

【讨论】:

  • 关于 MST 与 MDT,我进行了一些测试,在解析时,MST 默认为 America/Denver(有 DST) - 您可以在解析后调用 sdf.getTimeZone().getID() 进行检查(是的,已解析的时区在格式化程序中设置,为什么?)。但也许 OP 的默认时区是没有 DST 的(例如 America/Phoenix),这可以解释为什么输入在 9 月有 MST。
  • @Ole V.V 是的,客户报告他将设备语言更改为西班牙语,这导致了错误。当他再次将设备更改为英语时,它起作用了。
【解决方案2】:

这是 dateformatter 的代码,它将帮助您将日期转换为任何时间格式。

public void setDate(String date) {
            dateInput = (TextView) itemView.findViewById(R.id.dateText);
            DateFormat inputFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy");
            try {
                dateData = inputFormat.parse(date);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            DateFormat outputFormat = new SimpleDateFormat("pur your desirable format");
            String outputString = outputFormat.format(dateData);
            dateInput.setText(outputString);
        }

【讨论】:

  • 感谢您的快速回复。但我看到我的代码和你的一样。你觉得我的代码有问题吗?
  • 尽量不要将 DateFormat 放在您的 try 和 catch 块中,并请上面的代码,因为它有区别。你自己看我用过这个DateFormat inputFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy");
  • 唯一的变化是您将 SDF 更改为 DateFormat。我尝试使用相同的代码仍然会引发错误
【解决方案3】:

我使用的代码几乎与您使用的代码相同,只是在 SimpleDateFormat 实例化方面略有不同。

public static final String DATE_FORMAT = "EEE MMM d yyyy z HH:mm:ss";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.ROOT);
simpleDateFormat.format(date);

它返回 2017 年 9 月 18 日星期一 GMT+03:00 23:04:10。

【讨论】:

    【解决方案4】:

    切勿在没有Locale 的情况下使用SimpleDateFormatDateTimeFormatter

    由于给定的日期时间是英文的,你应该在你的日期时间解析器中使用Locale.ENGLISH;否则解析将在使用非英语类型区域设置的系统(计算机、电话等)中失败。

    另外,请注意 java.util 的日期时间 API 及其格式化 API SimpleDateFormat 已过时且容易出错。建议完全停止使用,转用modern date-time API

    演示:

    import java.time.ZonedDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.Locale;
    
    public class Main {
        public static void main(String[] args) {
            final String strDateTime = "Mon Sep 18 10:30:06 MST 2017";
            DateTimeFormatter dtf = DateTimeFormatter.ofPattern("EEE MMM d H:m:s z uuuu", Locale.ENGLISH);
            ZonedDateTime zdt = ZonedDateTime.parse(strDateTime, dtf);
            System.out.println(zdt);
        }
    }
    

    输出:

    2017-09-18T10:30:06-06:00[America/Denver]
    

    在我们继续之前关于时区的重要说明:

    避免使用 3 个字母的缩写指定时区。时区应以 Region/City 格式指定名称,例如ZoneId.of("Europe/London")。使用此约定,UTCZoneId 可以用ZoneId.of("Etc/UTC") 指定。以UTC[+/-]Offset 指定的时区可以指定为Etc/GMT[+/-]Offset,例如ZoneId.of("Etc/GMT+1")ZoneId.of("Etc/GMT+1")

    也有一些例外情况,例如要指定Turkey 的时区,我们使用

    ZoneId.of("Turkey")
    

    以下代码将为您提供所有可用的ZoneIds:

    // Get the set of all time zone IDs.
    Set<String> allZones = ZoneId.getAvailableZoneIds();
    

    您应该要求您的服务器应用程序使用此约定为您提供日期时间,例如

    Mon Sep 18 10:30:06 America/Denver 2017
    

    上面的代码,没有任何改变,将适用于这个日期时间字符串。

    回到原来的话题:

    默认情况下,DateTimeFormatter#ofPattern 使用 JVM 在启动时根据宿主环境设置的 default FORMAT localeSimpleDateFormat 也是如此。我试图通过下面的演示来说明这个问题:

    import java.time.ZonedDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.Locale;
    
    public class Main {
        public static void main(String[] args) {
            final String strDateTime = "Mon Sep 18 10:30:06 America/Denver 2017";
            DateTimeFormatter dtfWithDefaultLocale = null;
    
            System.out.println("JVM's Locale: " + Locale.getDefault());
            // Using DateTimeFormatter with the default Locale
            dtfWithDefaultLocale = DateTimeFormatter.ofPattern("EEE MMM d H:m:s z uuuu");
            System.out.println("DateTimeFormatter's Locale: " + dtfWithDefaultLocale.getLocale());
            System.out
                    .println("Parsed with JVM's default locale: " + ZonedDateTime.parse(strDateTime, dtfWithDefaultLocale));
    
            // Setting the JVM's default locale to Locale.FRANCE
            Locale.setDefault(Locale.FRANCE);
    
            // Using DateTimeFormatter with Locale.ENGLISH explicitly (recommended)
            DateTimeFormatter dtfWithEnglishLocale = DateTimeFormatter.ofPattern("EEE MMM d H:m:s z uuuu", Locale.ENGLISH);
            System.out.println("JVM's Locale: " + Locale.getDefault());
            System.out.println("DateTimeFormatter's Locale: " + dtfWithEnglishLocale.getLocale());
            ZonedDateTime zdt = ZonedDateTime.parse(strDateTime, dtfWithEnglishLocale);
            System.out.println("Parsed with Locale.ENGLISH: " + zdt);
    
            System.out.println("JVM's Locale: " + Locale.getDefault());
            // Using DateTimeFormatter with the default Locale
            dtfWithDefaultLocale = DateTimeFormatter.ofPattern("EEE MMM d H:m:s z uuuu");
            System.out.println("DateTimeFormatter's Locale: " + dtfWithDefaultLocale.getLocale());
            System.out
                    .println("Parsed with JVM's default locale: " + ZonedDateTime.parse(strDateTime, dtfWithDefaultLocale));
        }
    }
    

    输出:

    JVM's Locale: en_GB
    DateTimeFormatter's Locale: en_GB
    Parsed with JVM's default locale: 2017-09-18T10:30:06-06:00[America/Denver]
    JVM's Locale: fr_FR
    DateTimeFormatter's Locale: en
    Parsed with Locale.ENGLISH: 2017-09-18T10:30:06-06:00[America/Denver]
    JVM's Locale: fr_FR
    DateTimeFormatter's Locale: fr_FR
    Exception in thread "main" java.time.format.DateTimeParseException: Text 'Mon Sep 18 10:30:06 America/Denver 2017' 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.ZonedDateTime.parse(ZonedDateTime.java:598)
        at Main.main(Main.java:32)
    

    以下演示,使用SimpleDateFormat,只是为了完整起见:

    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Locale;
    
    public class Main {
        public static void main(String[] args) throws ParseException {
            final String strDateTime = "Mon Sep 18 10:30:06 MST 2017";
            SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM d H:m:s z yyyy", Locale.ENGLISH);
            Date date = sdf.parse(strDateTime);
            System.out.println(date);
        }
    }
    

    输出:

    Mon Sep 18 18:30:06 BST 2017
    

    注意:java.util.Date 对象不像modern date-time types 那样是真正的日期时间对象;相反,它表示距离Epoch of January 1, 1970 的毫秒数。当你打印一个java.util.Date 的对象时,它的toString 方法返回从这个毫秒值计算的日期时间。由于java.util.Date 没有时区信息,它会应用您的JVM 的时区并显示相同的信息。如果您需要在不同的时区打印日期时间,您需要将时区设置为SimpleDateFomrat 并从中获取格式化字符串。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-21
      相关资源
      最近更新 更多