用毫秒解析 CIM_DateTime 到 Java 日期

Parse CIM_DateTime with milliseconds to Java Date

我正在尝试将从 Windows 管理界面检索到的 DateTime 值转换为 Java (1.7) 日期;最终到纪元以来的毫秒数。 format is specified here

我尝试解析的一个示例是 20160513072950.782000-420,即 2016-05-13 07:29:50 加 782 毫秒,在我的本地时区(-420 分钟 = UTC-7 小时) .小数点后的数字是小数秒;理论上最多 6 位微秒,但实际上只有前 4 位非零。

我最初尝试使用 SimpleDateFormat 指定我想要解析的三位毫秒数进行解析:

SimpleDateFormat cimDateFormat = new SimpleDateFormat("yyyyMMddHHmmss.SSS");
Date date = cimDateFormat.parse(s, new ParsePosition(0));

我的理由是用 SSS 指定毫秒的三位数会停止解析。不幸的是,这没有用;在上面的示例中添加了超过 782 毫秒。

我最终通过将字符串修剪为所需字符使其按预期工作:

SimpleDateFormat cimDateFormat = new SimpleDateFormat("yyyyMMddHHmmss.S");
Date date = cimDateFormat.parse(s.substring(0, 18), new ParsePosition(0));

在这种情况下,我只包含一个 S 毫秒,但它解析了所有三个。

我在 SimpleDateFormat javadoc 中找不到任何内容可以清楚地解释此解析结束时发生的情况。具体问题:

  1. 为什么在 SSS 的情况下,它一直解析超过指定的位数?
  2. 为什么单个 S 解析所有 3 毫秒数字?
  3. 除了像我那样截断字符串之外,还有其他方法可以让 SimpleDateFormat 停止在指定位置解析字符串吗?

修改输入

据我所知,Java 的三种常见日期时间框架中的 none(旧的捆绑 java.util.Date/.Calendar/java.text.SimpleDateFormat 类、Joda-Time 框架或 Java 8 及更高版本中内置的 java.time 框架)允许从 UTC 偏移量作为总分钟数。

按照 Sotirios Delimanolis, you must modify the offset-from-UTC 的建议,将总分钟数转换为标准的小时数和分钟数(和秒数——这种可能性被奇怪的 Microsoft 格式忽略了)。所以-420应该变成-07:00或者-07:00:00

java.time

您正在使用与 Java 的最早版本捆绑在一起的麻烦的旧日期时间 类。旧的 类 现在是遗留的,并已被 java.time framework built into Java 8 and later, and largely back-ported to Java 6 & 7 by the ThreeTen-Backport project and further adapted to Android.

取代

java.time 类 的分辨率为 nanoseconds,最多九位小数秒。因此,处理您的输入 4-6 位小数秒没有问题。

我们的策略分为两部分:(a) 修改输入以转换该偏移量与 UTC,以及 (b) 将修改后的输入字符串解析为日期时间对象。

修改输入

首先我们将输入从 20160513072950.782000-420 更改为 20160513072950.782000-07:00:00。我们通过提取 +- 之后的字符来完成此操作,在本例中为 420

// Modify the input to replace offset as a number of minutes to the standard format, a number of hours, minutes, and seconds.
String input = "20160513072950.782000-420";
String offsetInMinutesAsString = input.substring ( 22 );

将其转换为 long,并创建一个 LocalTime 对象,以便我们可以生成 HH:mm:ss 格式的字符串。

long offsetInMinutes = Long.parseLong ( offsetInMinutesAsString );
LocalTime offsetAsLocalTime = LocalTime.MIN.plusMinutes ( offsetInMinutes );
String offsetAsString = offsetAsLocalTime.format ( DateTimeFormatter.ISO_LOCAL_TIME );

用我们生成的字符串替换那些尾随字符。

String inputModified = ( input.substring ( 0 , 22 ) + offsetAsString );

将字符串解析为日期时间对象

定义一个 custom formatting pattern by which to parse that string into a OffsetDateTime 对象。

// Parse the modified input as an OffsetDateTime.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern ( "yyyyMMddHHmmss.SSSSSSZZZZZ" , Locale.US );
OffsetDateTime odt = OffsetDateTime.parse ( inputModified , formatter );

转储到控制台。

System.out.println ( "input: " + input + " | inputModified: " + inputModified + " | odt: " + odt );

input: 20160513072950.782000-420 | inputModified: 20160513072950.782000-07:00:00 | odt: 2016-05-13T07:29:50.782-07:00

转换

我强烈建议避免使用旧的日期时间 类。但是,如果您必须使用 java.util.Date 对象与旧日期时间代码进行互操作,则可以进行转换。

寻找添加到旧 类 的新方法进行转换。对于此转换,我们使用 java.util.Date.from。我们需要为该转换方法提供一个 Instant 对象,UTC 时间轴上的一个时刻,分辨率为 nanoseconds。我们可以从 OffsetDateTime.

中提取一个
Instant instant = odt.toInstant();
java.util.Date utilDate = java.util.Date.from( instant );

有关转换的更多信息(包括漂亮的图表),请参阅 to another Question. Keep in mind that we are working with only a mere offset-from-UTC in our input strings and our OffsetDateTime, not a full time zone. A time zone 是一个偏移量 加上 用于处理夏令时 (DST) 等异常的规则。 Instantjava.util.Date 均为 UTC(偏移量为零)。

虽然我接受了 Basil Bourque 的回答,并且在过去两年中一直在我的项目中保留 threetenbp 依赖项,但我设法使用 Calendar 进行了快速解析,结果相同东西,可以满足我的需要。添加异常检查等,但这使用 JDK1.1 代码给出了相同的结果,并且可能对其他人有用。但是请注意,这种方法不是线程安全的,应该首选已接受的答案。

    Calendar c = Calendar.getInstance();
    c.set(Calendar.YEAR, Integer.parseInt(cimDate.substring(0, 4)));
    // Calendar uses 0-indexed months
    c.set(Calendar.MONTH, Integer.parseInt(cimDate.substring(4, 6)) - 1);
    c.set(Calendar.DATE, Integer.parseInt(cimDate.substring(6, 8)));
    c.set(Calendar.HOUR, Integer.parseInt(cimDate.substring(8, 10)));
    c.set(Calendar.MINUTE, Integer.parseInt(cimDate.substring(10, 12)));
    c.set(Calendar.SECOND, Integer.parseInt(cimDate.substring(12, 14)));
    c.set(Calendar.MILLISECOND, Integer.parseInt(cimDate.substring(15, 18)));
    c.setTimeZone(TimeZone.getTimeZone("UTC"));
    // Offset from UTC is in minutes
    return c.getTimeInMillis() + Integer.parseInt(cimDate.substring(22)) * 60_000L;*