ISO 8601
我们必须解析你的字符串。 java.time 类在解析/生成字符串时默认使用标准 ISO 8601 格式。您的输入接近合规;我们只需要将中间的空格替换为T。
String inputStart = "2017-01-23 05:25:00".replace ( " " , "T" );
String inputStop = "2018-03-20 07:29:50".replace ( " " , "T" );
LocalDateTime
您的输入没有任何与 UTC 或时区偏移的指示。所以他们确实不代表时间线上的实际时刻,只是时间线上可能时刻的模糊概念。所以我们将其解析为LocalDateTime 对象。我们为确定时间跨度而执行的数学运算将基于一般的 24 小时工作日,并将忽略 Daylight Saving Time (DST) 等异常情况。
LocalDateTime start = LocalDateTime.parse ( inputStart );
LocalDateTime stop = LocalDateTime.parse ( inputStop );
Period & Duration
java.time 类将不附加到时间线的时间跨度定义为两个部分。 Period 类代表年-月-日,而Duration 类代表小时-分钟-秒。
将时间跨度分为这两部分可以避免某些问题,这些问题是我们必须解决的问题才能解决您的请求。您究竟想如何处理第一天可能不完整的一天,以及最后可能不完整的一天?一天的中午到第二天的中午是一天还是24小时?如果我们将第一天的小时数与最后一天的小时数相加,如果这个数字超过 24 小时,我们是否应该挤出一整天?
计算
这里我采取了我们计算全天数的方法,忽略了第一天和最后一天。然后我将第一天的小时数与最后一天的小时数相加。正如我们从您的示例数据中看到的那样,这可能会导致我们的Duration 超过 24 小时。这种方法可能会也可能不会满足您未说明的意图/定义。
Period p = Period.between ( start.toLocalDate () , stop.toLocalDate () ).minusDays ( 1 ); // Subtract one, as we account for hours of first day.
// Get the Duration of first day's hours-minutes-seconds.
LocalDateTime startNextDay = start.toLocalDate ().plusDays ( 1 ).atStartOfDay ();
Duration dStart = Duration.between ( start , startNextDay );
// Get the Duration of first day's hours-minutes-seconds.
LocalDateTime stopStartOfDay = stop.toLocalDate ().atStartOfDay ();
Duration dStop = Duration.between ( stopStartOfDay , stop );
// Combine the pair of partial days into a single Duration.
Duration d = dStart.plus ( dStop );
转储到控制台。 Period 和 Duration 类上的 toString 方法在 ISO 8601 的 standard durations format 中生成字符串:PnYnMnDTnHnMnS 其中P 标记开始,T 将年月日与小时分开-分-秒。
System.out.println ( "start/stop: " + start + "/" + stop );
System.out.println ( "p: " + p );
System.out.println ( "dStart: " + dStart + " and dStop: " + dStop );
System.out.println ( "d: " + d );
开始/停止:2017-01-23T05:25/2018-03-20T07:29:50
p: P1Y1M24D
dStart:PT18H35M 和 dStop:PT7H29M50S
d: PT26H4M50S
要获得所需的文本输出,您需要询问Period 的每个部分和Duration 的每个部分。
int years = p.getYears ();
int months = p.getMonths ();
int days = p.getDays ();
Java 8 莫名其妙地缺少从Duration 中方便地获取每个小时、分钟或秒部分的方法。 Java 9 增加了这样的方法。更多讨论请见this Question。
long durationDays = d.toDaysPart();
long hours = d.toHoursPart();
long minutes = d.toMinutesPart();
long seconds = d.toSecondsPart();
int nanos = d.toNanosPart();
实时代码
请参阅此示例code working live in IdeOne.com。
ZonedDateTime
上面的讨论是针对一般 24 小时制的,忽略了与 UTC/时区的偏移问题。
如果您打算跟踪时间轴上的实际时刻,则必须应用用于这些日期时间字符串的时区。
ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdtStart = start.atZone( z );
…
其余代码类似,但将ZoneId 传递给atStartOfDay 调用以生成ZonedDateTime 对象而不是LocalDateTime 对象。
请记住,使用 ZonedDateTime 对象而不是 LocalDateTime 对象时,您可能会得到不同的结果。
参见示例code live in IdeOne.com。
String inputStart = "2017-01-23 05:25:00".replace ( " " , "T" );
String inputStop = "2018-03-20 07:29:50".replace ( " " , "T" );
LocalDateTime ldtStart = LocalDateTime.parse ( inputStart ); // LocalDateTime used only briefly here, to parse inputs. Then we switch to `ZonedDateTime`.
LocalDateTime ldtStop = LocalDateTime.parse ( inputStop );
ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdtStart = ldtStart.atZone( z ); // Invalid values may be adjusted, such as during the hour of a "Spring-forward" Daylight Saving Time (DST) switch-over.
ZonedDateTime zdtStop = ldtStop.atZone( z );
Period p = Period.between ( zdtStart.toLocalDate () , zdtStop.toLocalDate () ).minusDays ( 1 ); // Subtract one, as we account for hours of first day.
// Get the Duration of first day's hours-minutes-seconds.
ZonedDateTime zdtStartNextDay = zdtStart.toLocalDate ().plusDays ( 1 ).atStartOfDay ( z );
Duration dStart = Duration.between ( zdtStart , zdtStartNextDay );
// Get the Duration of first day's hours-minutes-seconds.
ZonedDateTime zdtStopStartOfDay = zdtStop.toLocalDate ().atStartOfDay ( z );
Duration dStop = Duration.between ( zdtStopStartOfDay , zdtStop );
// Combine the pair of partial days into a single Duration.
Duration d = dStart.plus ( dStop );
System.out.println ( "zdtStart: " + zdtStart );
System.out.println ( "zdtStop: " + zdtStop );
System.out.println ( "p: " + p );
System.out.println ( "dStart: " + dStart + " and dStop: " + dStop );
System.out.println ( "d: " + d );
int years = p.getYears ();
int months = p.getMonths ();
int days = p.getDays ();
System.out.println( "Years: " + years + "\nMonths: " + months + "\nDays: " + days );
// Enable if in Java 9 or later (but not in Java 8)
// long durationDays = d.toDaysPart();
// long hours = d.toHoursPart();
// long minutes = d.toMinutesPart();
// long seconds = d.toSecondsPart();
// int nanos = d.toNanosPart();
提示:ThreeTen-Extra 库提供了一个类 Interval 来跟踪一对 Instant 对象。 Instant 是 UTC 时间线上的时刻。您可以通过调用toInstant 从ZonedDateTime 中提取Instant。
关于java.time
java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.Date、Calendar 和 SimpleDateFormat。
Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。
要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。
从哪里获得 java.time 类?
ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如Interval、YearWeek、YearQuarter 和more。