Java:计算持续时间

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/477842/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 12:33:55  来源:igfitidea点击:

Java: calculating duration

javadatetimeduration

提问by MrG

I created the following code to calculate the duration between two timestamps which can come in two different formats:

我创建了以下代码来计算可以采用两种不同格式的两个时间戳之间的持续时间:

public class dummyTime {
public static void main(String[] args) {
    try {
        convertDuration("2008-01-01 01:00 pm - 01:56 pm");
        convertDuration("2008-01-01 8:30 pm - 2008-01-02 09:30 am");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private static String convertDuration(String time) throws Exception {
    String ts[] = time.split(" - ");
    SimpleDateFormat formatNew = new SimpleDateFormat("HH:mm");
    Date beg, end;
    String duration = null;

    beg = getDateTime(ts[0]);
    end = getDateTime(ts[1], beg);

    duration = formatNew.format(end.getTime() - beg.getTime());
    System.out.println(duration + " /// " + time + " /// " + beg + " /// "
            + end);

    return duration;
}

private static Date getDateTime(String dateTime) throws ParseException {
    DateFormat formatOldDateTime = new SimpleDateFormat(
            "yyyy-MM-dd hh:mm aa");
    DateFormat formatOldTimeOnly = new SimpleDateFormat("hh:mm aa");
    Date date = null;

    try {
        date = formatOldDateTime.parse(dateTime);
    } catch (ParseException e) {
        date = formatOldTimeOnly.parse(dateTime);
    }

    return date;
}

private static Date getDateTime(String dateTime, Date orig)
        throws ParseException {
    Date end = getDateTime(dateTime);

    if (end.getYear() == 70) {
        end.setYear(orig.getYear());
        end.setMonth(orig.getMonth());
        end.setDate(orig.getDate());
    }

    return end;
}
}

The output it generates is:

它生成的输出是:

01:56 /// 2008-01-01 01:00 pm - 01:56 pm /// Tue Jan 01 13:00:00 CET 2008 /// Tue Jan 01 13:56:00 CET 2008
14:00 /// 2008-01-01 8:30 pm - 2008-01-02 09:30 am /// Tue Jan 01 20:30:00 CET 2008 /// Wed Jan 02 09:30:00 CET 2008

My questions are:

我的问题是:

  1. Why are the results always wrong (always +1h)?
  2. What is a better way to identify timestamps without day? == 70 doesn't look good and the getDay & setDay functions are deprecated too.
  1. 为什么结果总是错误的(总是+1h)?
  2. 识别没有日期的时间戳的更好方法是什么?== 70 看起来不太好,而且 getDay 和 setDay 函数也被弃用了。

Many many thanks, this issue has been driving me crazy for several hours.

非常感谢,这个问题已经让我发疯了几个小时。

回答by Tom Hawtin - tackline

You are formatting time of day, not number of hours and minutes. As you are in the CET timezone [Central European Time] in winter, that is one hour different from UTC ("GMT").

您正在格式化一天中的时间,而不是小时数和分钟数。由于您在冬季处于 CET 时区 [中欧时间],因此与 UTC(“GMT”)相差一小时。

You probably want to be using Calendarinstead of Date. Or Joda-Time.

您可能想要使用Calendar而不是Date. 或乔达时间

回答by Yoni Roit

  1. At my computer this is off by 2 hours, because I'm at GMT+2, and you're probably at GMT+1. Note that formatNew.format(end.getTime() - beg.getTime());receives date, i.e. treats your 56 minutes as 1970-01-01-00:56:00 GMT+1. To fix this quickly, call formatNew.setTimeZone( TimeZone.getTimeZone( "GMT" ) );

  2. For the 2nd item, you can check if format-yyyy-MM-dd failed (you catch a parse error), and this is how you know that there's no year.

  1. 在我的电脑上,这关闭了 2 小时,因为我在 GMT+2,而你可能在 GMT+1。请注意formatNew.format(end.getTime() - beg.getTime());接收日期,即将您的 56 分钟视为 1970-01-01-00:56:00 GMT+1。要快速解决此问题,请致电formatNew.setTimeZone( TimeZone.getTimeZone( "GMT" ) );

  2. 对于第二项,您可以检查 format-yyyy-MM-dd 是否失败(您发现解析错误),这就是您知道没有年份的方式。

回答by kdgregory

Simple answer: it's inappropriate to use SimpleDateFormat to format values that represent time of day without date.

简单回答:使用 SimpleDateFormat 来格式化表示一天中没有日期的时间的值是不合适的。

Longer answer: Java time values are a count of milliseconds since the "epoch": midnight, January 1 1970, UTC.

更长的答案:Java 时间值是自“纪元”以来的毫秒数:1970 年 1 月 1 日午夜,UTC。

SimpleDateFormat assumes that you're giving it a valid timestamp, and applies a localized conversion to a date and time. I suspect that your locale is one hour off GMT (continental Europe), so that's why you're seeing results that are one hour off.

SimpleDateFormat 假定您为其提供了有效的时间戳,并将本地化转换应用于日期和时间。我怀疑您的语言环境与格林威治标准时间(欧洲大陆)相差一小时,这就是为什么您看到的结果与格林威治标准时间相差一小时。

While you could fool SimpleDateFormat by setting the timezone GMT, you're probably better off displaying durations using explicit math:

虽然您可以通过设置时区 GMT 来欺骗 SimpleDateFormat,但您最好使用显式数学显示持续时间:

int duration = 90;
System.out.printf("%02d:%02d", duration / 60, duration % 60);

回答by Basil Bourque

First, your example strings are not consistent: 8:30 pmlacks a padding zero. I will assume that is a typo, and should have been 08:30 pm.

首先,您的示例字符串不一致:8:30 pm缺少填充零。我会假设这是一个错字,应该是08:30 pm.

Undesirable string formats

不需要的字符串格式

By the way, these input string formats are not desirable. - A much better way is to use standard ISO 8601formats. - 12-hour clocks with AM/PM are troublesome. The standard formats use 24-hour clock, with hours 0-23.
- The standard notation for an interval is the pair of date-time strings separated by a slash: 2008-01-01T13:00/2008-01-01T13:56.

顺便说一下,这些输入字符串格式是不可取的。- 更好的方法是使用标准ISO 8601格式。- 带有 AM/PM 的 12 小时制很麻烦。标准格式使用 24 小时制,小时数为 0-23。
- 间隔的标准表示法是由斜杠分隔的一对日期时间字符串:2008-01-01T13:00/2008-01-01T13:56

Your input strings have another serious problem: No indication of offset-from-UTCor time zone. Without an offset or time zone, we must fall back to assuming generic 24-hour days. This ignores anomalies such as Daylight Saving Time (DST) that can result in 23 or 25 hour long days.

您的输入字符串有另一个严重的问题:没有从 UTC或时区偏移的指示。如果没有偏移量或时区,我们必须退回到假设通用的 24 小时天。这会忽略诸如夏令时 (DST) 之类的异常情况,这些异常会导致一天长达 23 或 25 小时。

If you know the time zone intended for the incoming strings, pass that as a second argument to get a correct result.

如果您知道传入字符串的时区,请将其作为第二个参数传递以获得正确的结果。

java.time

时间

This Question is quite old. Since then Java has supplanted the troublesome old date-time classes (Date, Calendar, etc.) with modern java.time classes. We use java.time in the example code below.

这个问题很老了。从那时起,Java已经取代了麻烦旧日期时间类(DateCalendar拥有现代化的java.time类等)。我们在下面的示例代码中使用 java.time。

Example class

示例类

Here is a complete class for processing these strings as given in your Question. A Durationis produced.

这是一个完整的类,用于处理您的问题中给出的这些字符串。ADuration产生。

package javatimestuff;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Locale;

/**
 *
 * @author Basil Bourque
 */
public class DurationProcessor {

    static final int SHORT = 30;
    static final int LONG = 41;

    static final DateTimeFormatter FORMATTER_LOCALDATETIME = DateTimeFormatter.ofPattern ( "uuuu-MM-dd hh:mm a" );
    static final DateTimeFormatter FORMATTER_LOCALTIME = DateTimeFormatter.ofPattern ( "hh:mm a" );

    static public Duration process ( String input ) {
        return DurationProcessor.process ( input , ZoneOffset.UTC );
    }

    static public Duration process ( String input , ZoneId zoneId ) {
        Duration d = Duration.ZERO;  // Or maybe null. To be generated by the bottom of this code.

        if ( null == input ) {
            // …
            System.out.println ( "ERROR - Passed null argument." );
            return d;
        }
        if ( input.length () == 0 ) {
            // …
            System.out.println ( "ERROR - Passed empty string as argument." );
            return d;
        }

        String inputModified = input.toUpperCase ( Locale.ENGLISH ); // Change `am` `pm` to `AM` `PM` for parsing.

        String[] parts = inputModified.split ( " - " );
        String inputStart = parts[ 0 ]; // A date-time sting.
        String inputStop = parts[ 1 ]; // Either a date-time string or a time-only string (assume the same date).

        ZonedDateTime start = null;  // To be generated in this block of code.
        try {
            LocalDateTime ldt = LocalDateTime.parse ( inputStart , DurationProcessor.FORMATTER_LOCALDATETIME );
            start = ldt.atZone ( zoneId );
        } catch ( DateTimeParseException e ) {
            // …
            System.out.println ( "ERROR - The start failed to parse. inputStart: " + inputStart );
            return d;
        }

        ZonedDateTime stop = null; // To be generated in this block of code.
        switch ( input.length () ) {
            case DurationProcessor.SHORT:  // Example: "2008-01-01 01:00 pm - 01:56 pm"
                try {
                    LocalTime stopTime = LocalTime.parse ( inputStop , DurationProcessor.FORMATTER_LOCALTIME );
                    stop = ZonedDateTime.of ( start.toLocalDate () , stopTime , zoneId );
                } catch ( DateTimeParseException e ) {
                    // …
                    System.out.println ( "ERROR - The stop time failed to parse." );
                    return d;
                }
                break;
            case DurationProcessor.LONG:  // "2008-01-01 8:30 pm - 2008-01-02 09:30 am"
                try {
                    LocalDateTime ldt = LocalDateTime.parse ( inputStop , DurationProcessor.FORMATTER_LOCALDATETIME );
                    stop = ldt.atZone ( zoneId );
                } catch ( DateTimeParseException e ) {
                    // …
                    System.out.println ( "ERROR - The stop date-time failed to parse." );
                    return d;
                }
                break;
            default:
                // …
                System.out.println ( "ERROR - Input string is of unexpected length: " + input.length () );
                break;
        }

        d = Duration.between ( start , stop );
        return d;
    }

    public static void main ( String[] args ) {
        // Run with out time zone (assumes UTC).
        Duration dShort = DurationProcessor.process ( "2008-01-01 01:00 pm - 01:56 pm" );
        System.out.println ( "dShort: " + dShort );

        Duration dLong = DurationProcessor.process ( "2008-01-01 08:30 pm - 2008-01-02 09:30 am" );
        System.out.println ( "dLong: " + dLong );

        // Run with specified time zone.
        ZoneId z = ZoneId.of ( "America/Montreal" );
        Duration dShortZoned = DurationProcessor.process ( "2008-01-01 01:00 pm - 01:56 pm" , z );
        System.out.println ( "dShortZoned: " + dShortZoned );

        Duration dLongZoned = DurationProcessor.process ( "2008-01-01 08:30 pm - 2008-01-02 09:30 am" , z );
        System.out.println ( "dLongZoned: " + dLongZoned );

    }
}

Note the mainmethod within the class for example usages.

请注意main类中的方法,例如用法。

First a pair of calls without specifying a time zone. So UTC and 24-hour days will be used.

首先是一对不指定时区的调用。因此将使用 UTC 和 24 小时制。

Duration dShort = DurationProcessor.process ( "2008-01-01 01:00 pm - 01:56 pm" );
System.out.println ( "dShort: " + dShort );

Duration dLong = DurationProcessor.process ( "2008-01-01 08:30 pm - 2008-01-02 09:30 am" );
System.out.println ( "dLong: " + dLong );

Another pair of calls where we do specify the intended time zone.

另一对调用,我们确实指定了预期的时区。

ZoneId z = ZoneId.of ( "America/Montreal" );
Duration dShortZoned = DurationProcessor.process ( "2008-01-01 01:00 pm - 01:56 pm" , z );
System.out.println ( "dShortZoned: " + dShortZoned );

Duration dLongZoned = DurationProcessor.process ( "2008-01-01 08:30 pm - 2008-01-02 09:30 am" , z );
System.out.println ( "dLongZoned: " + dLongZoned );

Live code

实时代码

See this class run in live code in IdeOne.com.

在 IdeOne.com 中查看以实时代码运行的此类

dShort: PT56M

dLong: PT13H

dShortZoned: PT56M

dLongZoned: PT13H

dShort: PT56M

dLong:PT13H

dShortZoned:PT56M

dLongZoned:PT13H

As noted elsewhere on this page, your output format using time-of-day style such as 00:56is ambiguous and confusing, and should be avoided. The Durationclass instead uses standard ISO 8601 format for durations. Above, we see results of fifty-six minutes and thirteen minutes.

如本页其他地方所述,使用诸如此类的时间样式的输出格式00:56模棱两可且令人困惑,应避免使用。本Duration类,而不是使用标准的ISO为8601持续时间格式。上面,我们看到了五十六分钟和十三分钟的结果。



About java.time

关于 java.time

The java.timeframework is built into Java 8 and later. These classes supplant the troublesome old legacydate-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

java.time框架是建立在Java 8和更高版本。这些类取代了麻烦的旧的遗留日期时间类,例如java.util.Date, Calendar, & SimpleDateFormat

The Joda-Timeproject, now in maintenance mode, advises migration to the java.timeclasses.

现在处于维护模式Joda-Time项目建议迁移到java.time类。

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

要了解更多信息,请参阅Oracle 教程。并在 Stack Overflow 上搜索许多示例和解释。规范是JSR 310

Where to obtain the java.time classes?

从哪里获得 java.time 类?

The ThreeTen-Extraproject extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

ThreeTen-额外项目与其他类扩展java.time。该项目是未来可能添加到 java.time 的试验场。你可能在这里找到一些有用的类,比如IntervalYearWeekYearQuarter,和更多