Java: расчет продолжительности - PullRequest
1 голос
/ 25 января 2009

Я создал следующий код для расчета продолжительности между двумя временными метками, которые могут иметь два разных формата:

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 тоже не рекомендуется.

Большое спасибо, эта проблема сводила меня с ума в течение нескольких часов.

Ответы [ 4 ]

4 голосов
/ 25 января 2009

Вы форматируете время суток, а не количество часов и минут. Поскольку вы находитесь в часовом поясе CET [Центральноевропейское время] зимой, этот час отличается от UTC («GMT»).

Возможно, вы хотите использовать Calendar вместо Date. Или Joda-Time .

2 голосов
/ 25 января 2009
  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. Для 2-го элемента вы можете проверить, не удалось ли format-yyyy-MM-dd (вы уловили ошибку разбора), и вот как вы узнаете, что года нет.

1 голос
/ 25 января 2009

Простой ответ: нецелесообразно использовать SimpleDateFormat для форматирования значений, представляющих время суток без даты.

Более длинный ответ: значения времени Java - это количество миллисекунд с начала эпохи: полночь, 1 января 1970 года, UTC.

SimpleDateFormat предполагает, что вы даете ему правильную временную метку, и применяет локализованное преобразование к дате и времени. Я подозреваю, что ваш регион находится на один час от Гринвичского (континентальная Европа), поэтому вы видите результаты на один час.

Хотя вы можете обмануть SimpleDateFormat, установив часовой пояс GMT, вам, вероятно, лучше отображать длительности с использованием явной математики:

int duration = 90;
System.out.printf("%02d:%02d", duration / 60, duration % 60);
0 голосов
/ 29 января 2017

Во-первых, ваши примерные строки не согласованы: 8:30 pm не имеет нулевого отступа. Я предполагаю, что это опечатка, и должно было быть 08:30 pm.

Нежелательные форматы строк

Кстати, эти форматы входных строк нежелательны. - Гораздо лучше использовать стандартные форматы ISO 8601 . - 12-часовые часы с AM / PM хлопотны. Стандартные форматы используют 24-часовые часы, с часами 0-23.
- Стандартным обозначением для интервала является пара строк даты и времени, разделенных косой чертой: 2008-01-01T13:00/2008-01-01T13:56.

В ваших входных строках есть еще одна серьезная проблема: нет указания смещение от UTC или часового пояса. Без смещения или часового пояса мы должны вернуться к предположению об общих 24-часовых днях. При этом игнорируются такие аномалии, как переход на летнее время (DST), которые могут привести к дням продолжительностью 23 или 25 часов.

Если вы знаете часовой пояс, предназначенный для входящих строк, передайте его в качестве второго аргумента, чтобы получить правильный результат.

java.time

Этот вопрос довольно старый. С тех пор Java вытеснила проблемные старые классы даты и времени (Date, Calendar и т. Д.) Современными классами java.time. Мы используем java.time в приведенном ниже примере кода.

Пример класса

Вот полный класс для обработки этих строк, как указано в вашем Вопросе. A 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 );

Живой код

Смотрите этот класс в живой код в IdeOne.com .

дШорт: PT56M

Длина: PT13H

dShortZoned: PT56M

dLongZoned: PT13H

Как отмечено в другом месте на этой странице, ваш формат вывода, использующий стиль времени дня, такой как 00:56, является неоднозначным и запутанным, и его следует избегать. Вместо этого класс Duration использует стандартный формат ISO 8601 для длительностей . Выше мы видим результаты за пятьдесят шесть минут и тринадцать минут.


О java.time

Инфраструктура java.time встроена в Java 8 и более поздние версии. Эти классы заменяют проблемные старые устаревшие классы даты и времени, такие как java.util.Date, Calendar, & SimpleDateFormat.

Проект Joda-Time , который теперь находится в режиме обслуживания , рекомендует перейти на классы java.time .

Чтобы узнать больше, см. Oracle Tutorial . И поиск переполнения стека для многих примеров и объяснений. Спецификация JSR 310 .

Где получить классы java.time?

  • Java SE 8 и SE 9 и более поздние
    • Встроенный.
    • Часть стандартного Java API со встроенной реализацией.
    • Java 9 добавляет некоторые незначительные функции и исправления.
  • Java SE 6 и SE 7
    • Большая часть функциональности java.time перенесена на Java 6 и 7 в ThreeTen-Backport .
  • Android

Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является полигоном для возможных будущих дополнений к java.time. Вы можете найти здесь несколько полезных классов, таких как Interval, YearWeek, YearQuarter и more .

...