Java:计算持续时间

时间:2009-01-25 15:44:31

标签: java datetime duration

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

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;
}
}

它生成的输出是:

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

我的问题是:

  1. 为什么结果总是错误的 (总是+ 1小时)?
  2. 什么是更好的 无需识别时间戳的方法 天? == 70看起来并不好看 getDay& setDay函数是 也弃用了。
  3. 非常感谢,这个问题让我疯了几个小时。

4 个答案:

答案 0 :(得分:4)

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

您可能希望使用Calendar代替Date。或Joda-Time

答案 1 :(得分:2)

  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是否失败(您发现解析错误),这就是您知道没有年份的方式。

答案 2 :(得分:1)

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

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

SimpleDateFormat假设您为其提供了有效的时间戳,并将本地化转换应用于日期和时间。我怀疑你的地方距离格林尼治标准时间(欧洲大陆)一小时,所以这就是为什么你看到的结果是一小时的结果。

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

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

答案 3 :(得分:0)

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

不受欢迎的字符串格式

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

您的输入字符串还有另一个严重问题:没有offset-from-UTC或时区的指示。如果没有抵消或时区,我们必须回到假设通用的24小时工作日。这会忽略夏令时(DST)等异常情况,导致长达23或25小时的漫长天数。

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

java.time

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

示例类

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

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 );

    }
}

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

首先是一对未指定时区的呼叫。因此,将使用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 );

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

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 in IdeOne.com中的此课程。

  

dShort:PT56M

     

dLong:PT13H

     

dShortZoned:PT56M

     

dLongZoned:PT13H

正如本页其他地方所述,使用00:56等时间样式的输出格式含糊不清,应该避免使用。而Duration类则使用标准ISO 8601 format for durations。在上面,我们看到了56分钟和13分钟的结果。

关于java.time

java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendar和& SimpleDateFormat

现在位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuartermore