我有一个字符串值映射,表示不同组件的停机时间。

dependencyMap.put ("sut", "14:26:12,14:27:19,00:01:07;15:01:54,15:02:54,00:01:00;15:44:30,15:46:30,00:02:00;16:10:30,16:11:30,00:01:00");
dependencyMap.put ("jms", "14:26:12,14:28:12,00:02:00;15:10:50,15:12:55,00:02:05;15:42:30,15:43:30,00:01:00;16:25:30,16:27:30,00:02:00");


字符串代表停机时间的开始,结束和持续时间。

(start)14:26:12,(end)14:27:19,(duration)00:01:07


我读取了其中的值,然后将它们添加到包含DependencyDownTime值startTime,endTime和duration的Long对象列表中。

jArray.forEach (dependency ->{
            String downTimeValues = knownDowntimesMap.get(dependency);
            final String[] downtime = downTimeValues.split (";");
            for (final String str : downtime) {
                final DependencyDownTime depDownTime = new DependencyDownTime ();
                final String[] strings = str.split (",");
                if (strings.length == 3) {
                    final DateFormat dateFormat = new SimpleDateFormat ("HH:mm:ss");
                    try {
                        depDownTime.setStartTime(dateFormat.parse (strings[0]).getTime ());
                        depDownTime.setEndTime (dateFormat.parse (strings[1]).getTime ());
                        depDownTime.setDuration (dateFormat.parse (strings[2]).getTime ());
                        downTimes.add (depDownTime);
                    } catch (final ParseException e) {
                        //logger.warn (e.getMessage (), e);
                    }
                } else {
                    //logger.warn ("");
                }
            }


然后,我对这些值执行简单的算术运算,以计算每个组件的总停机时间。

// sort the list by start time
        Collections.sort(downTimes, Comparator.comparing (DependencyDownTime::getStartTime));

        int i = 1;
        Long duration = 0L;
        for(DependencyDownTime dts: downTimes){
            Long curStart = dts.getStartTime ();
            Long curEnd = dts.getEndTime();

            Long nextStart = downTimes.get(i).getStartTime ();
            Long nextEnd = downTimes.get(i).getEndTime ();

            if(duration == 0){
                duration = dts.getDuration();
            }
            if(curStart.equals(nextStart) && curEnd < nextEnd){
                duration += (nextEnd - curEnd);
            }
            else if(nextStart > curEnd){
                duration += downTimes.get(i).getDuration();
            }
            else if( curStart < nextStart && curEnd > nextStart){
                duration += (nextEnd - curEnd);
            }
            else if(curEnd == nextStart){
                duration += downTimes.get(i).getDuration();
            }
            i++;
            if(i == downTimes.size ()){
                componentDTimeMap.put (application, duration);
                return;
            }


期望值应类似于1970-01-01T 00:14:35 .000+0100,只需几分钟。实际结果通常非常高,相差1969-12-31T 15:13:35 .000+0100几个小时

我有两个问题。


我是否正确解析值?
如果我的计算与长值相减时有些偏差。当我将值转换回日期格式时,期望值会有很大的不同吗?

最佳答案

your other question中所述,请不要误解这两个不同的概念:


一天中的某个时间:代表一天中的特定时间点,例如10 AM14:45:50
持续时间:代表amount of time,例如“ 1小时10分钟”或“ 2年3个月4天”。持续时间不会告诉您开始或结束的时间(相对于什么是“ 1小时10分钟”?),它未附加到时间顺序中,也不对应于时间轴中的特定点。只是时间本身。


在输入中,您具有:

(start)14:26:12,(end)14:27:19,(duration)00:01:07


startend代表一天中的时间,而duration代表时间。 SimpleDateFormat旨在用于一天中的日期和时间,但不能用于持续时间。将持续时间视为一天中的某个时间可能会起作用,但这是this answer中说明的问题。

另一个问题是,当SimpleDateFormat仅解析时间时,它将默认日期设置为1970年1月1日的JVM默认时区,从而导致您看到所有奇怪的结果。不幸的是,没有办法避免这种情况,因为java.util.Date具有完整的时间戳。更好的选择是使用新的日期/时间API。

就像在your other question中使用Java 8一样,我假设您也可以在此处使用它(但是,如果您使用的是Java ThreeTen Backport,这是Java 8新日期的一个很好的反向端口/ time类。唯一的区别是包名称(在Java 8中为java.time,在ThreeTen Backport中(或Android的ThreeTenABP)为org.threeten.bp),但类和方法的名称相同。

由于您只使用时间,因此无需考虑日期字段(日/月/年),因此可以使用LocalTime代替。您可以直接解析字符串,因为它们在ISO861 compliant format中:

LocalTime start = LocalTime.parse("14:26:12");
LocalTime end = LocalTime.parse("14:27:19");


不幸的是,在一段时间内没有内置的解析器,因此您必须手动解析它:

// parse the duration manually
String[] parts = "00:01:07".split(":");
Duration d = Duration
    // get hours
    .ofHours(Long.parseLong(parts[0]))
    // plus minutes
    .plusMinutes(Long.parseLong(parts[1]))
    // plus seconds
    .plusSeconds(Long.parseLong(parts[2]));


另一种选择是从输入中删除持续时间(或忽略它们)并使用开始和结束进行计算:

Duration d = Duration.between(start, end);


两者都会给您1分钟7秒的持续时间。

我的建议是更改DependencyDownTime以将开始和结束存储为LocalTime对象,将持续时间存储为Duration对象。这样,您的算法将如下所示:

Duration total = Duration.ZERO;
for (...) {
    LocalTime curStart = ...
    LocalTime curEnd = ...
    LocalTime nextStart = ...
    LocalTime nextEnd = ...

    if (total.toMillis() == 0) {
        duration = dts.getDuration();
    }
    if (curStart.equals(nextStart) && curEnd.isBefore(nextEnd)) {
        total = total.plus(Duration.between(curEnd, nextEnd));
    } else if (nextStart.isAfter(curEnd)) {
        total = total.plus(downTimes.get(i).getDuration());
    } else if (curStart.isBefore(nextStart) && curEnd.isAfter(nextStart)) {
        total = total.plus(Duration.between(curEnd, nextEnd));
    } else if (curEnd.equals(nextStart)) {
        total = total.plus(downTimes.get(i).getDuration());
    }
    i++;
    if (i == downTimes.size()) {
        // assuming you want the duration as a total of milliseconds
        componentDTimeMap.put(application, total.toMillis());
        return;
    }
}


您可以存储Duration对象,也可以存储相应的毫秒值。不要尝试将其转换为Date,因为日期并非设计成也不应该与持续时间一起使用。如果需要,您可以adapt this code格式化持续时间(不幸的是,持续时间没有本机格式化程序)。



局限性

上面的代码假定所有startend时间都在同一天。但是,如果您在start处有23:50并在end处有00:10,则持续时间应为20分钟吗?

如果真是这样,那就有点棘手了,因为LocalTime不知道日期(因此它认为23:50 > 00:10并且它们之间的持续时间为“减去23小时40分钟”)。

在这种情况下,您可以做一个技巧,并假设日期都在当前日期,但是当start大于end时,这意味着end时间在第二天:

LocalTime start = LocalTime.parse("23:50");
LocalTime end = LocalTime.parse("00:10");
// calculate duration
Duration d;
if (start.isAfter(end)) {
    // start is after end, it means end is in the next day

    // current date
    LocalDate now = LocalDate.now();
    // start is at the current day
    LocalDateTime startDt = now.atTime(start);
    // end is at the next day
    LocalDateTime endDt = now.plusDays(1).atTime(end);
    d = Duration.between(startDt, endDt);
} else {
    // both start and end are in the same day
    // just calculate the duration in the usual way
    d = Duration.between(start, end);
}


在上面的代码中,结果将为20分钟的Duration



不要将日期格式化为持续时间

这是为什么SimpleDateFormatDate不好处理持续时间的一些示例。

假设我的持续时间为10秒。如果我尝试将值10转换为日期,将其转换为java.util.Date(又称持续时间为日期):

// a 10 second duration (10000 milliseconds), treated as a date
Date date = new Date(10 * 1000);
System.out.println(date);


这将获得一个对应于“ unix纪元(1970-01-01T00:00Z)之后10000毫秒”的日期,即1970-01-01T00:00:10Z。但是当我打印日期对象时,toString()方法被隐式调用(如here所述)。并且此方法将此Millis值转换为JVM默认时区。

在我使用的JVM中,默认时区为America/Sao_Paulo,因此上面的代码输出:


  1969年12月31日星期三21:00:10


这不是所期望的:UTC即时时间1970-01-01T00:00:10Z对应于1969年12月31日晚上9点在圣保罗时区。

发生这种情况是因为我将持续时间错误地视为一个日期(并且输出将有所不同,具体取决于JVM中配置的默认时区)。

java.util.Date不能(必须)用于持续时间。实际上,既然我们有了better API's,则应尽可能避免使用它。有too many problemsdesign issues,只要可以就不要使用。



如果将持续时间作为日期来处理,SimpleDateFormat也将无法正常工作。在此代码中:

SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
Date d = dateFormat.parse("10:00:00");


输入仅具有时间字段(小时,分钟和秒),因此SimpleDateFormat将日期设置为JVM默认时区的1970年1月1日。如果我System.out.println这个日期,结果将是:


  1970年1月1日星期四10:00:00


那是1970年1月1日在圣保罗时区的上午10点,在UTC中它等效于1970-01-01T13:00:00Z-因此d.getTime()返回46800000

如果我将JVM默认时区更改为Europe/London,它将创建一个对应于1970年1月1日在伦敦(或UTC 1970-01-01T09:00:00Z)上午10点的日期-并且d.getTime()现在返回32400000(因为在上午10点)伦敦和圣保罗的上午10点发生在不同的时刻。

SimpleDateFormat不是适合持续时间的正确工具-实际上是it isn't even the best tool to work with dates

10-08 07:34
查看更多