Java 使用 SimpleDateFormat.parse() 解析日期时出现 NumberFormatException

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21017502/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 05:59:35  来源:igfitidea点击:

NumberFormatException while parsing date with SimpleDateFormat.parse()

javadate

提问by peterh

Have a function that creates a time-only Dateobject. (why this is required is a long story which is irrelevant in this context but I need to compare to some stuff in XML world where TIME (i.e. time-only) is a valid concept).

有一个函数来创建一个仅限时间的Date对象。(为什么需要这样做是一个很长的故事,在这种情况下无关紧要,但我需要与 XML 世界中的某些内容进行比较,其中 TIME(即仅时间)是一个有效概念)。

private static final SimpleDateFormat DF_TIMEONLY = new SimpleDateFormat("HH:mm:ss.SSSZ");    

public static Date getCurrentTimeOnly() {

    String onlyTimeStr = DF_TIMEONLY.format(new Date());  // line #5
    Date  onlyTimeDt = null;
    try {
        onlyTimeDt = DF_TIMEONLY.parse(onlyTimeStr);  // line #8
    } catch (ParseException ex) { 
        // can never happen (you would think!)
    }
    return onlyTimeDt;
}

There are probably at least a couple other ways to create a time-only Date in Java (or more precisely one where the date part is 1970-01-01) but my question is really not about that.

可能至少有其他几种方法可以在 Java 中创建仅限时间的日期(或更准确地说,日期部分是 1970-01-01),但我的问题实际上与此无关。

My question is that this piece of code starts randomly throwing NumberFormatExceptionon line #8 after having run in production for long time. Technically I would say that this should be impossible, right ?

我的问题是这段代码在生产环境中运行了很长时间后,开始在第 8 行随机抛出NumberFormatException。从技术上讲,我会说这应该是不可能的,对吧?

Here's an extract of random NumberFormatExceptions that come from above piece of code:

这是来自上述代码段的随机 NumberFormatExceptions 的摘录:

java.lang.NumberFormatException: multiple points
java.lang.NumberFormatException: For input string: ".11331133EE22"
java.lang.NumberFormatException: For input string: "880044E.3880044"
java.lang.NumberFormatException: For input string: "880044E.3880044E3"

First of all I hope we can agree that formally this should be impossible? The code uses the same format (DF_TIMEONLY) as output and then input. Let me know if you disagree that it should be impossible.

首先,我希望我们能同意正式这应该是不可能的?代码使用相同的格式 ( DF_TIMEONLY) 作为输出然后输入。如果您不同意,请告诉我这应该是不可能的。

I haven't been able to re-produce the problem in a standalone environment. The problem seems to come when the JVM has run for a long time (>1 week). I cannot find a pattern to the problem, i.e. summer time / winter time, AM/PM, etc. The error is sporadic, meaning that one minute it will throw NumberFormatException and the next minute it will run fine.

我无法在独立环境中重现该问题。当JVM运行很长时间(> 1周)时,问题似乎出现了。我找不到问题的模式,即夏令时/冬令时、AM/PM 等。错误是零星的,这意味着一分钟它会抛出 NumberFormatException,下一分钟它会运行良好。

I suspect that there's some kind of arithmetic malfunction somewhere in either the JVM or perhaps even in the CPU. The above exceptions suggests that there's floating point numbers involved but I fail to see where they would come from. As far as I know Java's Date object is a wrapper around a longwhich holds the number of millis since the epoch.

我怀疑 JVM 甚至 CPU 中的某处存在某种算术故障。上述例外表明涉及浮点数,但我看不出它们来自哪里。据我所知,Java 的 Date 对象是围绕 a 的包装器,long它包含自纪元以来的毫秒数。

I'm guessing what is happening is that there's an unexpected string onlyTimeStrcreated in line #5 so the problem really lies here rather than in line #8.

我猜正在发生的事情是onlyTimeStr在第 5 行中创建了一个意外的字符串,因此问题真正出在此处而不是第 8 行。

Here's an example of a full stacktrace:

这是完整堆栈跟踪的示例:

java.lang.NumberFormatException: For input string: "880044E.3880044E3"
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1241)
    at java.lang.Double.parseDouble(Double.java:540)
    at java.text.DigitList.getDouble(DigitList.java:168)
    at java.text.DecimalFormat.parse(DecimalFormat.java:1321)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2086)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1455)
    at java.text.DateFormat.parse(DateFormat.java:355)
    at org.mannmann.zip.Tanker.getCurrentTimeOnly(Tanker.java:746)

Environment: Java 7

环境:Java 7

采纳答案by Clockwork-Muse

The likely cause is the fact that SimpleDateFormatisn't threadsafe, and you're referencing it from multiple threads. While extremelydifficult to prove (and about as hard to test for), there is some evidence this is the case:

可能的原因是它SimpleDateFormat不是线程安全的,并且您从多个线程引用它。虽然极难证明(也同样难以测试),但有一些证据表明情况确实如此:

  1. .11331133EE22- notice how everything is doubled
  2. 880044E.3880044E3- same here
  1. .11331133EE22- 注意一切是如何翻倍的
  2. 880044E.3880044E3- 同样在这里

You probably have at least two threads interleaving. The Ewas throwing me, I was thinking it was attempting to deal with scientific notation (1E10, etc), but it's likely part of the time zone.

您可能至少有两个线程交错。将E被扔我,我想它试图处理科学记数法(1E10等),但它是对可能部分时区

Thankfully, the (formatting) basic fix is simple:

值得庆幸的是,(格式化)基本修复很简单:

private static final String FORMAT_STRING = "HH:mm:ss.SSSZ";    

public static Date getCurrentTimeOnly() {

    SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_STRING);

    String onlyTimeStr = formatter.format(new Date());
    return formatter.parse(onlyTimeStr);
}


There's a couple of other things you could be doing here, too, with a few caveats:

你也可以在这里做一些其他的事情,但有一些警告:

1 - If the timezone is UTC (or any without DST), this is trivial

1 - 如果时区是 UTC(或任何没有 DST),这是微不足道的

public static Date getCurrentTimeOnly() {

    Date time = new Date();

    time.setTime(time.getTime() % (24 * 60 * 60 * 1000));

    return time;
}

2 - You're going to have trouble testing this method, because you can't safely pause the clock (you can change the timezone/locale). For a better time dealing with date/time in Java, use something like JodaTime. Note that LocalTimedoesn't have a timezone attached, but Dateonly returns an offset in integer hours(and there are zones not on the hour); for safety, you need to either return a Calendar(with the full timezone), or just return something without it:

2 - 您将无法测试此方法,因为您无法安全地暂停时钟(您可以更改时区/区域设置)。为了更好地处理 Java 中的日期/时间,请使用类似JodaTime 的东西。请注意,LocalTime没有附加时区,但Date只返回整数小时的偏移量(并且有不在小时内的区域);为了安全起见,您需要返回一个Calendar(带有完整时区),或者只返回一些没有它的东西:

// This method is now more testable.  Note this is only safe for non-DST zones
public static Calendar getCurrentTimeOnly() {

    Calendar cal = new Calendar();

    // DateTimeUtils is part of JodaTime, and is a class allowing you to pause time!
    cal.setTimeInMillis(DateTimeUtils.currentTimeMillis() % (24 * 60 * 60 * 1000));

    return cal;
}

回答by peterh

While the correct answer is the one by Clockwork-Muse (the cause of the problems is the fact that SimpleDateFormatisn't thread safe) I just wanted to deliver another method of creating a time-only Date object:

虽然正确的答案是 Clockwork-Muse 的答案(问题的原因是SimpleDateFormat它不是线程安全的),但我只是想提供另一种创建仅限时间的 Date 对象的方法:

public static Date getCurrentTimeOnly() {

    Calendar rightNow = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
    int hour = rightNow.get(Calendar.HOUR_OF_DAY);
    int minute = rightNow.get(Calendar.MINUTE);
    int second = rightNow.get(Calendar.SECOND);
    int msecond = rightNow.get(Calendar.MILLISECOND);

    long millisSinceMidnight
            = (hour * 60 * 60 * 1000)
            + (minute * 60 * 1000)
            + (second * 1000)
            + (msecond);
    return new Date(millisSinceMidnight);
}

This method is somewhat more formally correct, i.e. it handles leap-seconds. It doesn't assume, like other methods, that all days since epoch has always had 24*60*60*1000 milliseconds in them.

这种方法在形式上更正确,即它处理闰秒。它不像其他方法那样假设自 epoch 以来的所有日子里总是有 24*60*60*1000 毫秒。

It doesn't however handle the case where the leap second is on the current day.

然而,它不处理闰秒在当天的情况。

回答by Basil Bourque

Joda-Time

乔达时间

FYI, the Joda-Time2.3 library provides a class expressly for your purpose, time-only, without any date: LocalTime. And, it is thread-safe(immutable instances). Seems a much better option than manhandling the troublesome java.util.Date class.

仅供参考,Joda-Time2.3 库专门为您的目的提供了一个类,仅 time-only,没有任何日期:LocalTime。而且,它是线程安全的不可变实例)。似乎比处理麻烦的 java.util.Date 类更好的选择。

LocalTime localTime = new LocalTime();

Dump to console…

转储到控制台...

System.out.println( "localTime: " + localTime );

When run…

运行时…

localTime: 16:26:28.065

java.time

时间

Java 8brings the new java.time package, inspired by Joda-Time, defined by JSR 310.

Java 8带来了受JSR 310定义的 Joda-Time 启发的新java.time 包

In java.time, you will find a LocalTimeclass similar to the one in Joda-Time.

在java.time,你会发现一个本地时间类似于一个在乔达时类。

回答by Darwin.Lau

I have got the same question, the cause is SimpleDateFormat is not thread-safe, I just add syncronized in the method, and it doesn't happen again.

我有同样的问题,原因是 SimpleDateFormat 不是线程安全的,我只是在方法中添加了同步,并且不会再次发生。

回答by Shankha Mandal

You can use "sychronized" block to make it thread safe. Something like:

您可以使用“同步”块来使其线程安全。就像是:

synchronized (lastUpdatedFormat) {
            date = 
         lastUpdatedFormat.parse(lastUpdatedFormat.format(currentDate));
        }

回答by Yash

SimpleDateFormatis not thread safe. the following program will reproduce NumberFormatExceptionwhile parsing string represented date to date object.

SimpleDateFormat不是线程安全的。下面的程序将NumberFormatException在解析表示日期到日期对象的字符串时重现。

public class MaintainEqualThreadsPatallel {
    static int parallelCount = 20;
    public static void main(String[] args) throws Exception {
        ExecutorService executorPool = Executors.newFixedThreadPool(parallelCount);

        int numberOfThreads = 150; // Total thread count = 150*2= 300.
        List<Future<Object>> futureReturns = new LinkedList<Future<Object>>();
        for (int i = 0; i < numberOfThreads; i++) {
            int uniqueRandomValues = uniqueRandomValues(1, 10);

            // Callable Thread - call()
            Future<Object> submit = executorPool.submit( new WorkerCallable(uniqueRandomValues) );
            futureReturns.add(submit);

            // Runnable Thread - run()
            executorPool.execute( new WorkerThread(uniqueRandomValues) );
        }

        // WorkerCallable: Blocking main thread until task completes.
        waitTillThreadsCompleteWork(futureReturns);
        // Terminate Pool threads in-order to terminate main thread
        executorPool.shutdown();
    }
    private static final SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
    public static Date numberFormatEx(Date date) throws ParseException { // synchronized
        String dateStr = sdf.format(date);
        Date dateParsed = sdf.parse(dateStr); // NumberFormatException: For input string: "186E.2186E2"
        System.out.println("Date :"+ dateParsed);
        return dateParsed;
    }

    protected void loopFunction(int repeatCount) {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName +":START");
        for (int i = 1; i <= repeatCount; i++) {
            try {
                System.out.println(threadName +":"+ i);
                sleepThread(100);

                numberFormatEx(new Date());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println(threadName +":END");
    }

    public static void waitTillThreadsCompleteWork(List<Future<Object>> futureReturns) throws Exception {
        for (Future<Object> future : futureReturns) {
            int threadReturnVal = (int) future.get();
            System.out.println("Future Response : "+threadReturnVal);
        }
    }

    public static int uniqueRandomValues(int min, int max) {
        int nextInt = ThreadLocalRandom.current().nextInt(min, max);
        System.out.println("Random Vlaue : "+nextInt);
        return nextInt;
    }
    public void sleepThread(long mills) {
        try {
            Thread.sleep(mills);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class WorkerThread extends MaintainEqualThreadsPatallel implements Runnable {
    int randomValue = 0;
    public WorkerThread(int randomValue) {
        this.randomValue = randomValue;
    }

    @Override
    public void run() {
        // As separate stack run() function doesn't accepts parameters, pass to constructor.
        loopFunction(randomValue);
    }
}
class WorkerCallable extends MaintainEqualThreadsPatallel implements Callable<Object> {
    int randomValue = 0;
    public WorkerCallable(int randomValue) {
        this.randomValue = randomValue;
    }

    public Object call() {
        // As separate stack run() function doesn't accepts parameters, pass to constructor.
        loopFunction(randomValue);
        return randomValue;
    }
}

NumberFormatException with different messages:

带有不同消息的 NumberFormatException:

java.lang.NumberFormatException: multiple points
java.lang.NumberFormatException: For input string: ""
java.lang.NumberFormatException: For input string: "186E.2"
java.lang.NumberFormatException: For input string: "186E.2186E2"
java.lang.NumberFormatException: For input string: "22200222E.222002224EE4"

java.lang.NumberFormatException: For input string: "22200222E.222002224EE44"
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
    at java.lang.Double.parseDouble(Double.java:538)
    at java.text.DigitList.getDouble(DigitList.java:169)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)

In Multi-Threading/Web Application with Multi-Requestsconcept parse function leads to NumberFormatExceptionwhich can be handled using synchronized block.

Multi-Threading/Web Application with Multi-Requests概念解析函数导致NumberFormatException可以使用同步块处理。



To overcome NumberFormatException on parse()function use any of the following scenarios.

要克服parse()函数上的NumberFormatException,请使用以下任何一种情况。

  1. Separate Object: Every request/thread works on its own object.
  1. 单独的对象:每个请求/线程都在自己的对象上工作。
public static Date numberFormatEx(Date date) throws ParseException {
    SimpleDateFormat ObjInstance = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
    String dateStr = ObjInstance.format(date);
    Date dateParsed = ObjInstance.parse(dateStr);
    System.out.println("Date :"+ dateParsed);
    return dateParsed;
}

Unnecessary creating reusable object for each thread.

为每个线程创建不必要的可重用对象。

  1. Static Object synchronized block: Every request/thread shares the common object to perform operation. As multiple threads share same object at same time then the object data gets clear/overrride ""/"186E.2186E2"at some point and leads to error.
  1. 静态对象同步块:每个请求/线程共享公共对象来执行操作。由于多个线程同时共享同一个对象,因此对象数据""/"186E.2186E2"在某些时候会被清除/覆盖并导致错误。
static SimpleDateFormat objStatic = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
public static synchronized Date numberFormatEx(Date date) throws ParseException {
    String dateStr = objStatic.format(date);
    Date dateParsed = objStatic.parse(dateStr); // NumberFormatException: For input string: "186E.2186E2"
    System.out.println("Date :"+ dateParsed);
    return dateParsed;
}

NOTE: In case of Memory management it better to use synchronized block with static object which is reusable.

注意:在内存管理的情况下,最好使用带有可重用静态对象的同步块。

回答by Ole V.V.

The diagnosis in the accepted answer is correct. I am providing the modern answer: do use java.time, the modern Java date and time API, for your date and time work. In Java 7 too. SimpleDateFormatis notoriously troublesome, its lack of thread safety is only one of its many problems. So don't use that class.

接受的答案中的诊断是正确的。我提供了现代答案:一定要使用 java.time,现代 Java 日期和时间 API,用于您的日期和时间工作。在 Java 7 中也是如此。SimpleDateFormat是出了名的麻烦,它缺乏线程安全只是它的众多问题之一。所以不要使用那个类。

OffsetTime.now() and ThreeTen Backport

OffsetTime.now() 和 ThreeTen Backport

You want the current time only, though with an offset from UTC, if your format pattern is to be believed. We have got a method exactly for that in java.time, the modern Java date and time API. So no reason to format into a string and parse back.

如果要相信您的格式模式,您只需要当前时间,尽管与 UTC 有一个偏移量。我们在现代 Java 日期和时间 API 中的 java.time 中有一个完全适用的方法。所以没有理由格式化成字符串并解析回来。

    OffsetTime timeOnly = OffsetTime.now(ZoneId.systemDefault());
    System.out.println(timeOnly);

When I ran the code just now in my time zone, Europe/Copenhagen, on jdk.1.7.0_67, the output was:

当我刚刚在我的时区 Europe/Copenhagen 在 jdk.1.7.0_67 上运行代码时,输​​出是:

06:21:55.419+01:00

06:21:55.419+01:00

By the way this is also the XML format for the concept of a time with time zone. Are we done?

顺便说一下,这也是带有时区的时间概念的 XML 格式。我们完了吗?

The Dateclass you were returning is poorly designed and long outdated, so avoid it if you can. If you need one for a legacy API that you cannot afford to change just now, convert like this:

Date您返回的课程设计不佳且已过时,因此请尽可能避免使用。如果您需要一个用于您现在无法更改的遗留 API,请按如下方式转换:

    Instant asInstant = LocalDate.of(1970, Month.JANUARY, 1)
            .atTime(timeOnly)
            .toInstant();
    Date oldfashionedDateObject = DateTimeUtils.toDate(asInstant);

    System.out.println("As java.util.Date: " + oldfashionedDateObject);

As java.util.Date: Thu Jan 01 06:21:55 CET 1970

作为 java.util.Date: Thu Jan 01 06:21:55 CET 1970

Question: Does it work on Java 7?

问题:它适用于 Java 7 吗?

Environment: Java 7

环境:Java 7

java.time just requires at least Java 6.

java.time 至少需要Java 6

  • In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in. In this case use Date.from(asInstant)for converting from Instantto Dateinstead of the way shown in the code above.
  • In non-Android Java 6 and 7 get the ThreeTen Backport, the backport of the modern classes (ThreeTen for JSR 310; see the links at the bottom).
  • On (older) Android use the Android edition of ThreeTen Backport. It's called ThreeTenABP. And make sure you import the date and time classes from org.threeten.bpwith subpackages.
  • 在 Java 8 及更高版本和更新的 Android 设备(从 API 级别 26)中,现代 API 是内置的。在这种情况下使用Date.from(asInstant)从转换 InstantDate而不是在上面的代码中示出的方式。
  • 在非 Android Java 6 和 7 中获得 ThreeTen Backport,现代类的 backport(ThreeTen for JSR 310;请参阅底部的链接)。
  • 在(较旧的)Android 上使用 ThreeTen Backport 的 Android 版本。它被称为 ThreeTenABP。并确保从org.threeten.bp子包中导入日期和时间类。

Links

链接