如何在Java中计算时间总和?

3
我正在处理一个报告,其中需要计算其中的数据总和,其中一些数据是时间戳,例如:

 ----------------------
|  活动     |   时间   |
 ----------------------
|     1     | 11:00:00 |
 -----------------------
|     2     | 12:00:00 |
 -----------------------
|     3     | 13:00:00 |
 -----------------------
| 总计      | 36:00:00 |
 ----------------------

我试图按照以下方式对时间戳求和:

final DateFormat dt = new SimpleDateFormat("HH:mm:ss");
final Calendar c = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault());
c.setTimeInMillis(0);
for (final String t : timestampsList) {
    c.add(Calendar.MILLISECOND, (int) dt.parse(t).getTime());
}

变量timestampsList是一个ArrayList,其中包含所有符合SimpleDateFormat对象使用的模式的String。给定代码的问题在于,我无法通过使用相同的SimpleDateFormat生成时间戳的总和,得到的只是未来日期中模式所示小时数。

我也看过Joda Time Duration类,但我不熟悉这个库,也不知道我是否走上了通向正确答案的正确路径。

有人知道如何使用J2SE或Joda Time处理它吗?


你的方法存在一个问题,就是在不保留起始日期的情况下对日期进行加法操作。你需要在添加时间后获取结束日期与起始日期之间的差值。 - Jonathan Drapeau
timestamps想象成整数。你不需要初始值来计算总和,只需循环遍历这些值并计算总和即可。@JonathanDrapeau - Philippe Gioseffi
并不是因为时间戳代表的是日期,而不是经过的时间。 - Jonathan Drapeau
你真正需要的是java.time,但不幸的是,只有在Java 8发布时才能使用。 - AJMansfield
@JonathanDrapeau 或许我没有用最好的方式解释清楚。实际上,我需要的确实是经过的时间。就像我要求你告诉我自 Unix 纪元以来有多少小时,格式为 HH:mm:ss - Philippe Gioseffi
显示剩余2条评论
4个回答

5
我会将这些字符串解析并转换为秒或毫秒,然后将它们加起来。请参见下面的第2个答案。
答案1:
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;


public class Test051 {

    public static void main(String[] args) throws Exception {
        String pt = "1970-01-01-";
        ArrayList<String> timestampsList = new ArrayList<String>();
        timestampsList.add("01:00:05");
        timestampsList.add("01:00:05");
        timestampsList.add("10:00:05");
        final DateFormat dt = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");
        final Calendar sum = Calendar.getInstance();
        sum.setTimeInMillis(0);

        long tm0 = new SimpleDateFormat("yyyy-MM-dd").parse(pt).getTime();

        System.out.println("tm0 = " + tm0);

        for (final String t : timestampsList) {
            // System.out.println(dt.parse(pt + t).getTime());
            Date x = dt.parse(pt + t);
            // System.out.println(x.getTime());
            sum.add(Calendar.MILLISECOND, (int)x.getTime());
            sum.add(Calendar.MILLISECOND, (int)-tm0);
        }

        long tm = sum.getTime().getTime();
        System.out.println("tm = " + tm);

        tm = tm / 1000;


        long hh = tm / 3600;
        tm %= 3600;
        long mm = tm / 60;
        tm %= 60;
        long ss = tm;
        System.out.println(format(hh) + ":" + format(mm) + ":" + format(ss));
    }

    private static String format(long s){
        if (s < 10) return "0" + s;
        else return "" + s;
    }
}

ANSWER 2

import java.util.ArrayList;

public class Test051 {

    public static void main(String[] args) throws Exception {
        ArrayList<String> timestampsList = new ArrayList<String>();
        timestampsList.add("01:00:05");
        timestampsList.add("01:00:05");
        timestampsList.add("10:00:05");

        long tm = 0;
        for (String tmp : timestampsList){
            String[] arr = tmp.split(":");
            tm += Integer.parseInt(arr[2]);
            tm += 60 * Integer.parseInt(arr[1]);
            tm += 3600 * Integer.parseInt(arr[0]);
        }

        long hh = tm / 3600;
        tm %= 3600;
        long mm = tm / 60;
        tm %= 60;
        long ss = tm;
        System.out.println(format(hh) + ":" + format(mm) + ":" + format(ss));
    }

    private static String format(long s){
        if (s < 10) return "0" + s;
        else return "" + s;
    }
}

ANSWER 3

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;

public class Test051 {

    public static void main(String[] args) throws Exception {
        ArrayList<String> timestampsList = new ArrayList<String>();
        timestampsList.add("01:00:00");
        timestampsList.add("02:00:00");
        timestampsList.add("03:00:00");
        timestampsList.add("04:00:00");
        timestampsList.add("02:00:00");
        timestampsList.add("04:00:00");

        Date dt0 = new SimpleDateFormat("yyyy-MM-dd").parse("1970-01-01");

        // Check very carefully the output of this one.
        System.out.println(dt0.getTime());

        final DateFormat dt = new SimpleDateFormat("HH:mm:ss");
        final Calendar c = Calendar.getInstance();
        c.setTimeInMillis(0);
        for (final String t : timestampsList) {
            c.add(Calendar.MILLISECOND, (int) dt.parse(t).getTime());
            c.add(Calendar.MILLISECOND, (int)-dt0.getTime());
        }

        // We need to add this back. This is basically the time zone offset.
        c.add(Calendar.MILLISECOND, (int)dt0.getTime());

        System.out.println(c.getTime().getTime());
        System.out.println(c.getTimeInMillis());

        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(c.getTime()));
        System.out.println(new SimpleDateFormat("HH:mm:ss").format(c.getTime()));
    }

}

@ChristopheRoussy 由于提供的模式是 HH:mm:ss,J2SE本身将忽略毫秒。 - Philippe Gioseffi
并不是因为由HH:mm:ss提供的模式,J2SE将始终将日期视为1970年1月1日,因此我将始终获得0到86399999之间的值。 - Philippe Gioseffi
@PhilippeGioseffi 请尝试回答问题1和问题2。两个答案都应该没问题。我觉得答案2更简单。 - peter.petrov
我现在会检查你的答案,@peter.petrov。 - Philippe Gioseffi
你的第二种解决方案正好符合我的需求。我接受了你的答案并点了一个赞。我只是没有将其作为数组使用,而是将字符串日期解析为日历,并逐个获取字段。非常感谢! - Philippe Gioseffi
显示剩余9条评论

1
如果这些数据输入字符串表示小时:分钟:秒的持续时间,没有日期或时间,那么其他答案工作过于艰难。通常,旧的java.util.Date和.Calendar类非常麻烦,应该避免使用。特别是在这里,这些类没有时间跨度的概念。相反,您应该使用Joda-Time或者java.time。

Joda-Time

Joda-Time提供了三个类来表示时间跨度:Interval,Period和Duration。第一个与宇宙时间线上的点相关联。另外两个则不是。

Period和Duration类非常接近。Period是一个包含年、月、日、小时、分钟和秒数的元组。Duration是一个毫秒数,没有天数或秒数等字段的概念。

Joda-Time使用ISO 8601标准来解析和生成字符串。对于期间/持续时间,这意味着PnYnMnDTnHnMnS格式。其中P表示“期间”,T是日期和时间部分之间的分隔符。
以下是Joda-Time 2.3中的示例代码。主要包括parsePerioddurationSum.plus两行代码。
模拟输入字符串。
List<String> durationStrings = new ArrayList<String>();
durationStrings.add( "11:00:00" ); // Number of hours/minutes/seconds. Not time-of-day.
durationStrings.add( "12:00:00" );
durationStrings.add( "13:00:00" ); // Expect sum of 36 hours = 11 + 12 + 13.

定义一个格式化程序来解析这些字符串。Joda-Time 可能已经内置了这样的格式化程序,但我找不到它。因此,我自己定义了一个。

PeriodFormatter formatter = new PeriodFormatterBuilder()
        .appendHours()
        .appendSeparator( ":" )
        .appendMinutes()
        .appendSeparator( ":" )
        .appendSeconds()
        .toFormatter();

循环输入的字符串,解析每个字符串,然后将其持续时间加入总和。
Duration durationSum = Duration.ZERO;  // Initializing to empty amount. Add to this in loop below.
for ( String durationString : durationStrings ) {
    Period period = formatter.parsePeriod( durationString );
    Duration duration = period.toStandardDuration();
    durationSum = durationSum.plus( duration );
    System.out.println( "period: " + period );
    System.out.println( "duration: " + duration );
}
System.out.println( "durationSum: " + durationSum );
System.out.println( "durationSum as Period: " + durationSum.toPeriod() );

当运行时...
period: PT11H
duration: PT39600S
period: PT12H
duration: PT43200S
period: PT13H
duration: PT46800S
durationSum: PT129600S
durationSum as Period: PT36H

1
如果您不想使用Peter Petrov的解决方案来自己解析字符串,那么使用Calendar和SimpleDateFormat的方法如下:
List<String> timestampsList = new ArrayList<String>();
timestampsList.add("11:00:00");
timestampsList.add("12:00:00");
timestampsList.add("13:00:00");
final DateFormat dt = new SimpleDateFormat("HH:mm:ss");
final Calendar c = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault());
long milliseconds = 0;
c.clear();
long startingMS = c.getTimeInMillis();
for (final String t : timestampsList) {
  milliseconds = milliseconds + (dt.parse(t).getTime() - startingMS);
}

System.out.println(milliseconds + " milliseconds");
System.out.println(milliseconds / 1000 + " seconds");
System.out.println(milliseconds / 1000 / 60 + " minutes");
System.out.println(milliseconds / 1000 / 60 / 60 + " hours");

或者使用。
long startingMS = dt.parse("00:00:00").getTime();
for (final String t : timestampsList) {
  milliseconds = milliseconds + (dt.parse(t).getTime() - startingMS);
}

相反,去除了对Calendar的需求。

两者结果均为:

129600000 毫秒 129600 秒 2160 分钟 36 小时

请注意,您可能需要将结果设置为double,以不错过时间的一部分。


并不是说我不想按照 @peter.petrov 的方式去做。实际上,他的方法与我的非常相似,我也会解析字符串并将它们相加。顺便说一下,在你的方法中,清除 Calendar 实例并从 startingMS 变量开始是否比多次清除它并给变量赋相同的值更好呢? - Philippe Gioseffi
@PhilippeGioseffi 你代码的问题在于:'(int) dt.parse(t).getTime()' 返回/包含了时区偏移量,而不仅仅是 HH:mm:ss 字符串中的时间。 - peter.petrov
并不完全是这样,因为由于提供的格式为HH:mm:ss,J2SE总会将日期视为1970年1月1日,所以我将始终获得一个介于0和86399999之间的值。 - Philippe Gioseffi
@PhilippeGioseffi 编辑了我的答案并提供了另一种不需要 Calendar 实例的选项。 - Jonathan Drapeau
@JonathanDrapeau 谢谢你的时间,但是 peter.petrov 的解决方案正好符合我的需求。不过我还是给你的回答点了个赞,感谢你的努力。 - Philippe Gioseffi

1

这是一段由Petrov编写的原始代码,我对其进行了一些编辑。由于在注释中提供大量代码片段进行讨论相对比较困难,所以我将其作为答案发布,以便我们可以讨论Petrov的其他考虑。

public static void somaTempos(final String[] listaTempos) throws ParseException {
    long tm = 0;
    final DateFormat dt = new SimpleDateFormat("HH:mm:ss");
    final Calendar c = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault());
    for (String tmp : listaTempos) {
        c.setTime(dt.parse(tmp));
        tm += c.get(Calendar.SECOND) + 60 * c.get(Calendar.MINUTE) + 3600 * c.get(Calendar.HOUR_OF_DAY);
    }

    final long l = tm % 3600;
    System.out.println(SIGRUtil.format(tm / 3600) + ':' + SIGRUtil.format(l / 60) + ':' + SIGRUtil.format(l % 60));
}

private static String format(long s) {
    if (s < 10) {
        return "0" + s;
    }
    return String.valueOf(s);
}

更新:另外一个同样解决我的问题的替代方案:

public static String sumTimes(final String[] timestampList) {
    long milliseconds = 0;
    final DateFormat dt = new SimpleDateFormat("HH:mm:ss");
    dt.setLenient(false);
    try {
        final long timezoneOffset = dt.parse("00:00:00").getTime();
        for (final String t: timestampList) {
            milliseconds += (dt.parse(t).getTime() - timezoneOffset);
        }
    } catch (final ParseException e) {
        throw new BusinessException(
                "One of the timestamps in the timestamp list cannot be applied to the HH:mm:ss pattern.", e);
    }

    ((SimpleDateFormat) dt).applyPattern(":mm:ss");
    return new StringBuilder(8).append(milliseconds / 3600000).append(
            dt.format(new Date(milliseconds))).toString();
}

实际上,API通过在计算时间戳总和后应用DateFormat中的另一种模式免费为我提供了分钟和秒数,但不要忘记在此计算中考虑时区偏移量,我的真正问题是如何计算小时数,这实际上是较简单的部分。
有任何改进建议吗?

1
Philippe,这对我来说看起来很好。问题在于当您尝试在一个日历实例c中累加值,并最终使用SimpleDateFormat和c实例打印结果时。这就是我想要实现的,我认为我最终在“ANSWER 3”中成功了。如果您希望进一步深入研究,请尝试找到具有这些限制的解决方案,您也会注意到我遇到的问题。 - peter.petrov
再次感谢。我正在按照您的指示进行调查,解决问题。 - Philippe Gioseffi
是的,这对我来说看起来也不错。如果它能正常工作,那就没问题了。现在你有很多选择。祝你好运。 - peter.petrov
@peter.petrov 我确实有一点担心性能问题。不管怎样,感谢你的建议。 - Philippe Gioseffi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接