java.time无法解析小数部分吗?

时间:2014-03-23 07:08:07

标签: java java-time

在Mac OS X(Mavericks)上首次发布Java 8(b132)时,使用新java.time package的此代码有效:

String input = "20111203123456"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

渲染:

2011-12-03T12:34:56

但是当我按照DateTimeFormatter class doc中的指定添加“SS”表示秒数(和“55”作为输入)时,会抛出异常:

java.time.format.DateTimeParseException: Text '2011120312345655' could not be parsed at index 0

文档说默认使用严格模式,并且需要与输入数字相同数量的格式字符。所以我很困惑为什么这段代码失败了:

String input = "2011120312345655"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

另一个使用文档示例(“978”)(失败)的例子:

String input = "20111203123456978"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

此示例有效,添加小数点(但我在doc中找不到这样的要求):

String input = "20111203123456.978"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss.SSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

渲染:

localDateTime: 2011-12-03T12:34:56.978

从输入字符串中省略句点字符会导致格式失败。

失败:

String input = "20111203123456.978"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

失败:

String input = "20111203123456978"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss.SSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

3 个答案:

答案 0 :(得分:30)

错误 - 在Java 9

中修复

此问题已在JDK-bug-log中报告。 Stephen Colebourne提到了解决方案:

DateTimeFormatter dtf = 
  new DateTimeFormatterBuilder()
  .appendPattern("yyyyMMddHHmmss")
  .appendValue(ChronoField.MILLI_OF_SECOND, 3)
  .toFormatter();

注意:此解决方法不包括仅两个模式符号SS的用例。调整可能只是使用其他字段,如MICRO_OF_SECOND(6次SSSSSS)或NANO_OF_SECOND(9次SSSSSSSSS)。有关两位数的数字,请参阅下面的更新。

@PeterLawrey关于模式符号的含义" S"见this documentation

  

分数:以秒为单位输出纳秒级字段。   纳秒值具有九位数,因此是图案的计数   字母是从1到9.如果它小于9,那么纳秒   值被截断,只有最高位数   输出。在严格模式下解析时,解析的数字必须是   匹配模式字母的数量。在宽松模式下解析时,   解析数字的数量必须至少是模式字母的数量,   最多9位数。

所以我们看到S代表秒的任何一小部分(包括纳秒),而不仅仅是几毫秒。此外,不幸的是,小数部分目前在相邻值解析中表现不佳。

修改

作为背景,这里有一些关于相邻值解析的评论。只要字段由小数点或时间部分分隔符(冒号)等文字分隔,对要解析的文本中字段的解释就不难了,因为解析器很容易知道何时停止,即字段部分结束时当下一个字段开始时。因此,如果指定小数点,JSR-310解析器可以处理文本序列。

但是如果你有一系列相邻数字跨越多个领域,那么就会出现一些实施困难。为了让解析器知道字段在文本中何时停止,有必要事先指示解析器给定字段由固定宽度的数字字符表示。这适用于所有appendValue(...) - 假定数值表示的方法。

不幸的是,JSR-310使用小数部分(appendFraction(...))也无法做到这一点。如果你找到关键字"相邻"在类DateTimeFormatterBuilder的javadoc中,您会发现此功能仅由appendValue(...) - 方法实现。请注意,模式字母S的规范略有不同,但内部委托给appendFraction() - 方法。我假设我们至少要等到Java 9(如JDK-bug-log中报告的那样,或者稍后???),直到小数部分也可以管理相邻的值解析。


2015-11-25更新:

以下仅使用两个小数位的代码不起作用并抛出DateTimeParseException

DateTimeFormatter dtf = 
  new DateTimeFormatterBuilder()
  .appendPattern("yyyyMMddHHmmssSS")
  .appendValue(ChronoField.MILLI_OF_SECOND, 2)
  .toFormatter();
String input = "2011120312345655"; 
LocalDateTime.parse(input, dtf); // abort

解决方法

String input = "2011120312345655"; 
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSS");
Date d = sdf.parse(input);
System.out.println(d.toInstant()); // 2011-12-03T12:34:56.055Z

不起作用,因为SimpleDateFormat以错误的方式解释分数(请参阅输出,55 ms而不是550 ms)。

剩下的解决方案是要么长时间等待Java 9(或更高版本?),要么编写自己的hack或使用第三方库作为解决方案。

基于脏黑客的解决方案:

String input = "2011120312345655"; 
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
int len = input.length();
LocalDateTime ldt = LocalDateTime.parse(input.substring(0, len - 2),  dtf);
int millis = Integer.parseInt(input.substring(len - 2)) * 10;
ldt = ldt.plus(millis, ChronoUnit.MILLIS);
System.out.println(ldt); // 2011-12-03T12:34:56.550

使用Joda-Time的解决方案:

String input = "2011120312345655"; 
DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyyMMddHHmmssSS");
System.out.println(dtf.parseLocalDateTime(input)); // 2011-12-03T12:34:56.550

使用我的库Time4J的解决方案:

String input = "2011120312345655"; 
ChronoFormatter<PlainTimestamp> f = 
  ChronoFormatter.ofTimestampPattern("yyyyMMddHHmmssSS", PatternType.CLDR, Locale.ROOT);
System.out.println(f.parse(input)); // 2011-12-03T12:34:56.550

2016-04-29更新:

正如人们可以通过上面提到的JDK问题看到的那样,它现在被标记为已解决 - 对于 Java 9

答案 1 :(得分:0)

这是一种算法,用于调整传统上从格式化日期String返回的尾随零的顺序。

/**
 * Takes a Date and provides the format whilst compensating for the mistaken representation of sub-second values.
 * i.e. 2017-04-03-22:46:19.000991 -> 2017-04-03-22:46:19.991000
 * @param pDate Defines the Date object to format.
 * @param pPrecision Defines number of valid subsecond characters contained in the system's response.
 * */
private static final String subFormat(final Date pDate, final SimpleDateFormat pSimpleDateFormat, final int pPrecision) throws ParseException {
    // Format as usual.
    final String lString        = pSimpleDateFormat.format(pDate);
    // Count the number of characters.
    final String lPattern       = pSimpleDateFormat.toLocalizedPattern();
    // Find where the SubSeconds are.
    final int    lStart         = lPattern.indexOf('S');
    final int    lEnd           = lPattern.lastIndexOf('S');
    // Ensure they're in the expected format.
    for(int i = lStart; i <= lEnd; i++) { if(lPattern.charAt(i) != 'S') {
        // Throw an Exception; the date has been provided in the wrong format.
       throw new ParseException("Unable to process subseconds in the provided form. (" + lPattern + ").", i);
    } }
    // Calculate the number of Subseconds. (Account for zero indexing.)
    final int lNumSubSeconds = (lEnd - lStart) + 1;
    // Fetch the original quantity.
    String lReplaceString = lString.substring(lStart + (lNumSubSeconds - pPrecision), lStart + lNumSubSeconds);
    // Append trailing zeros.
    for(int i = 0; i < lNumSubSeconds - pPrecision; i++) { lReplaceString += "0"; }
    // Return the String.
    return lString.substring(0, lStart) + lReplaceString;
}

答案 2 :(得分:0)

 <android.support.design.widget.CoordinatorLayout 
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
    <android.support.v7.widget.Toolbar 
        android:id="@+id/toolbar"
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="0dp"
        android:background="@color/colorAccent"
        android:elevation="0dp"
        android:minHeight="0dp"
        android:padding="0dp"
        app:collapseIcon="@drawable/logo_mini">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="top"
            android:dividerPadding="100dp"
            android:gravity="end"
            android:orientation="vertical">

            <LinearLayout
                android:id="@+id/main_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:orientation="vertical"
                android:padding="5dp"
                android:paddingLeft="50dp">

                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal">

                    <ImageView
                        android:id="@+id/center_icon"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:layout_marginTop="5dp"
                        android:layout_weight="0"
                        android:src="@drawable/logo_mini" />

                </LinearLayout>

                <ImageView
                    android:id="@+id/bar"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="5dp"
                    android:background="@android:color/white"
                    android:src="@mipmap/mic" />

                <TextView
                    android:id="@+id/emp_name"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Hi ,"
                    android:textSize="30sp" />

                <TextView
                    android:id="@+id/textView5"
                    android:layout_width="match_parent"

                    android:layout_height="wrap_content"
                    android:text="Wellcome back,"
                    android:visibility="gone" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="here ae you assigned sites"
                    android:visibility="gone" />
            </LinearLayout>

        </LinearLayout>

    </android.support.v7.widget.Toolbar>

        <ImageView
            android:id="@+id/icon"
            android:layout_width="@dimen/dim150"
            android:layout_height="@dimen/dim150"
            android:layout_below="@+id/home_txt"
            android:layout_marginTop="@dimen/dim20"
            android:src="@drawable/user_photo"
            app:layout_anchor="@id/toolbar"
            app:layout_anchorGravity="bottom|center_horizontal" />
    </android.support.design.widget.CoordinatorLayout>

这样的事情帮助了我