Как я могу «красиво напечатать» продолжительность на Java?

95

Кто-нибудь знает о библиотеке Java, которая может печатать число в миллисекундах так же, как это делает C #?

Например, длина 123456 мс будет напечатана как 4d1h3m5s.

phatmanace
источник
1
К вашему сведению, формат, который вы, кажется, описываете, Durationопределен в разумном стандарте ISO 8601 : PnYnMnDTnHnMnSгде Pозначает «Период» и отмечает начало, Tотделяет часть даты от части времени, а между ними - необязательные вхождения числа с одним -буквенное сокращение. Например, PT4H30M= четыре с половиной часа.
Basil Bourque
1
Если ничего не помогает, это очень просто сделать самому. Просто используйте последовательные приложения %и, /чтобы разделить число на части. Почти проще, чем некоторые из предложенных ответов.
Hot Licks
@HotLicks Оставьте это библиотечным методам для более чистого и ясного кода, чем использование /и %.
Ole VV
Да, за прошедшие 8 лет с тех пор, как я задал этот вопрос (!), Я перешел на joda time, что очень хорошо подходит для моего варианта использования
phatmanace
@phatmanace Проект Joda-Time сейчас находится в режиме обслуживания и рекомендует перейти на классы java.time, встроенные в Java 8 и новее.
Basil Bourque

Ответы:

91

В Joda Time есть довольно хороший способ сделать это с помощью PeriodFormatterBuilder .

Быстрая победа: PeriodFormat.getDefault().print(duration.toPeriod());

например

//import org.joda.time.format.PeriodFormatter;
//import org.joda.time.format.PeriodFormatterBuilder;
//import org.joda.time.Duration;

Duration duration = new Duration(123456); // in milliseconds
PeriodFormatter formatter = new PeriodFormatterBuilder()
     .appendDays()
     .appendSuffix("d")
     .appendHours()
     .appendSuffix("h")
     .appendMinutes()
     .appendSuffix("m")
     .appendSeconds()
     .appendSuffix("s")
     .toFormatter();
String formatted = formatter.print(duration.toPeriod());
System.out.println(formatted);
Роб Хруска
источник
2
Из ответа ниже видно, что экземпляр Period можно создать напрямую, без предварительного создания экземпляра Duration и последующего преобразования его в Period. Например, период периода = новый период (миллисекунды); Форматированная строка = formatter.print (период);
Basil Vandegriend,
7
Остерегайтесь этого преобразования "duration.toPeriod ()". Если продолжительность достаточно велика, часть дня и далее останется равной нулю. Часть часов будет продолжать расти. Вы получите 25х10м23, но никогда не получите "d". Причина в том, что не существует полностью правильного способа конвертировать часы в дни в строгом смысле слова Джоды. В большинстве случаев, если вы сравниваете два момента и хотите его распечатать, вы можете сделать новый период (t1, t2) вместо новой длительности (t1, t2) .toPeriod ().
Boon
@Boon вы знаете, как преобразовать продолжительность в период относительно дней? Я использую продолжительность, из которой я могу подставить секунду в свой таймер. Настройка события PeriodType.daysTime()или .standard()не помогло
murt
4
@murt Если вы действительно хотите и можете принять стандартное определение дня, вы можете "formatter.print (duration.toPeriod (). normalizedStandard ())" в приведенном выше коде. Радоваться, веселиться!
Boon
78

Я построил простое решение, используя Java 8 Duration.toString()и немного регулярного выражения:

public static String humanReadableFormat(Duration duration) {
    return duration.toString()
            .substring(2)
            .replaceAll("(\\d[HMS])(?!$)", "$1 ")
            .toLowerCase();
}

Результат будет выглядеть так:

- 5h
- 7h 15m
- 6h 50m 15s
- 2h 5s
- 0.1s

Если вам не нужны пробелы между ними, просто удалите replaceAll.

Lucasls
источник
6
Вы никогда не должны полагаться на вывод toStrong (), потому что он может измениться в следующих версиях.
Weltraumschaf
14
@Weltraumschaf вряд ли изменится, так как это указано в javadoc
aditsu quit, потому что SE is EVIL
9
@Weltraumschaf Вряд ли изменится, поскольку вывод Duration::toStringотформатирован в соответствии с четко определенным и хорошо зарекомендовавшим себя стандартом ISO 8601 .
Basil
1
Если вам не нужны дроби, добавьте .replaceAll("\\.\\d+", "")перед вызовом toLowerCase().
zb226
1
@Weltraumschaf В целом ваша точка зрения верна, и я бы сам не стал полагаться на вывод toString () большинства классов. Но в этом очень конкретном случае в определении метода указано, что его выходные данные соответствуют стандарту ISO-8601, как вы можете видеть в документации Javadoc метода . Так что это не деталь реализации, как в большинстве классов, поэтому я не ожидал бы, что такой зрелый язык, как Java, изменится таким образом,
lucasls
13

Apache commons-lang предоставляет полезный класс, чтобы сделать это, а также DurationFormatUtils

например DurationFormatUtils.formatDurationHMS( 15362 * 1000 ) )=> 4: 16: 02.000 (H: m: s.millis) DurationFormatUtils.formatDurationISO( 15362 * 1000 ) )=> P0Y0M0DT4H16M2.000S, ср. ISO8601

noleto
источник
9

JodaTime имеет Periodкласс, который может представлять такие количества и может быть отображен (через IsoPeriodFormat) в формате ISO8601 , например PT4D1H3M5S, например

Period period = new Period(millis);
String formatted = ISOPeriodFormat.standard().print(period);

Если этот формат не тот, который вам нужен, он PeriodFormatterBuilderпозволяет вам собирать произвольные макеты, включая ваш стиль C # 4d1h3m5s.

Скаффман
источник
3
Так же , как примечание, new Period(millis).toString()использует ISOPeriodFormat.standard()по умолчанию.
Thomas Beauvais
9

В Java 8 вы также можете использовать toString()метод java.time.Durationформатирования без внешних библиотек с использованием представления на основе ISO 8601 секунды, такого как PT8H6M12.345S.

Конрад Хёффнер
источник
4
Обратите внимание, что здесь не печатаются дни
Вим Деблауве,
60
Не уверен, что этот формат соответствует моему определению красивого шрифта. :-)
james.garriss 02
@ james.garriss Чтобы сделать его немного красивее, см. Ответ erickdeoliveiraleal.
Basil Bourque
8

Вот как это можно сделать, используя чистый код JDK:

import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.Duration;

long diffTime = 215081000L;
Duration duration = DatatypeFactory.newInstance().newDuration(diffTime);

System.out.printf("%02d:%02d:%02d", duration.getDays() * 24 + duration.getHours(), duration.getMinutes(), duration.getSeconds()); 
user678573
источник
7

org.threeten.extra.AmountFormats.wordBased

Проект ThreeTen-Extra , поддерживаемый Стивеном Колебурном, автором JSR 310, java.time и Joda-Time , имеет AmountFormatsкласс, который работает со стандартными классами времени даты Java 8. Тем не менее, он довольно подробный, без возможности для более компактного вывода.

Duration d = Duration.ofMinutes(1).plusSeconds(9).plusMillis(86);
System.out.println(AmountFormats.wordBased(d, Locale.getDefault()));

1 minute, 9 seconds and 86 milliseconds

Адриан Бейкер
источник
2
Вау, приятно знать, я не видел этой функции. Однако есть один серьезный недостаток: отсутствует оксфордская запятая .
Basil
4
@BasilBourque Оксфордская запятая типична для США, но, что интересно, не для Великобритании. Моя библиотека Time4J (которая имеет гораздо лучшую интернационализацию) поддерживает это различие в классе net.time4j.PrettyTime, а также позволяет при желании контролировать компактный вывод.
Meno Hochschild
6

Java 9+

Duration d1 = Duration.ofDays(0);
        d1 = d1.plusHours(47);
        d1 = d1.plusMinutes(124);
        d1 = d1.plusSeconds(124);
System.out.println(String.format("%s d %sh %sm %ss", 
                d1.toDaysPart(), 
                d1.toHoursPart(), 
                d1.toMinutesPart(), 
                d1.toSecondsPart()));

2 д 1ч 6м 4с

erickdeoliveiraleal
источник
Как вы получаете toHoursPart и выше?
Готи и Чипс,
5

Альтернативой строительному подходу Joda-Time может быть решение на основе шаблонов . Это предлагает моя библиотека Time4J . Пример использования класса Duration.Formatter (добавлено несколько пробелов для большей читабельности - удаление пробелов даст желаемый стиль C #):

IsoUnit unit = ClockUnit.MILLIS;
Duration<IsoUnit> dur = // normalized duration with multiple components
  Duration.of(123456, unit).with(Duration.STD_PERIOD);
Duration.Formatter<IsoUnit> f = // create formatter/parser with optional millis
  Duration.Formatter.ofPattern("D'd' h'h' m'm' s[.fff]'s'");

System.out.println(f.format(dur)); // output: 0d 0h 2m 3.456s

Это java.timeсредство форматирования также может печатать длительность -API (однако функции нормализации этого типа менее эффективны):

System.out.println(f.format(java.time.Duration.ofMillis(123456))); // output: 0d 0h 2m 3.456s

Ожидание OP о том, что «123456 мс как long будет напечатано как 4d1h3m5s», вычислено явно неверным способом. Я считаю небрежность причиной. Тот же модуль форматирования продолжительности, определенный выше, также может использоваться в качестве анализатора. Следующий код показывает, что «4d1h3m5s» скорее соответствует349385000 = 1000 * (4 * 86400 + 1 * 3600 + 3 * 60 + 5) :

System.out.println(
  f.parse("4d 1h 3m 5s")
   .toClockPeriodWithDaysAs24Hours()
   .with(unit.only())
   .getPartialAmount(unit)); // 349385000

Другой способ - использовать класс net.time4j.PrettyTime (который также подходит для локализованного вывода и печати относительного времени, такого как «вчера», «следующее воскресенье», «4 дня назад» и т. Д.):

String s = PrettyTime.of(Locale.ENGLISH).print(dur, TextWidth.NARROW);
System.out.println(s); // output: 2m 3s 456ms

s = PrettyTime.of(Locale.ENGLISH).print(dur, TextWidth.WIDE);
System.out.println(s); // output: 2 minutes, 3 seconds, and 456 milliseconds

s = PrettyTime.of(Locale.UK).print(dur, TextWidth.WIDE);
System.out.println(s); // output: 2 minutes, 3 seconds and 456 milliseconds

Ширина текста определяет, используются ли сокращения. Форматом списка также можно управлять, выбирая соответствующий языковой стандарт. Например, в стандартном английском используется запятая Oxform, а в Великобритании - нет. Последняя версия Time4J v5.5 поддерживает более 90 языков и использует переводы на основе CLDR-репозитория (отраслевой стандарт).

Мено Хохшильд
источник
2
этот PrettyTime намного лучше, чем тот, что в другом ответе! жаль, что я не нашел это первым.
ycomp 06
Я не знаю, почему теперь есть два отрицательных голоса за это хорошо работающее решение, поэтому я решил расширить ответ, приведя больше примеров также о синтаксическом анализе (противоположном форматированию) и подчеркнув неправильное ожидание OP.
Мено Хохшильд,
4

Версия Java 8 на основе ответа user678573 :

private static String humanReadableFormat(Duration duration) {
    return String.format("%s days and %sh %sm %ss", duration.toDays(),
            duration.toHours() - TimeUnit.DAYS.toHours(duration.toDays()),
            duration.toMinutes() - TimeUnit.HOURS.toMinutes(duration.toHours()),
            duration.getSeconds() - TimeUnit.MINUTES.toSeconds(duration.toMinutes()));
}

... поскольку в Java 8 нет PeriodFormatter и таких методов, как getHours, getMinutes, ...

Я был бы рад увидеть лучшую версию для Java 8.

Торстен
источник
выбрасывает java.util.IllegalFormatConversionException: f! = java.lang.Long
erickdeoliveiraleal
с Duration d1 = Duration.ofDays (0); d1 = d1.plusHours (47); d1 = d1.plusMinutes (124); d1 = d1.plusSeconds (124); System.out.println (String.format ("% s дней и% sh% sm% 1.0fs", d1.toDays (), d1.toHours () - TimeUnit.DAYS.toHours (d1.toDays ()), d1 .toMinutes () - TimeUnit.HOURS.toMinutes (d1.toHours ()), d1.toSeconds () - TimeUnit.MINUTES.toSeconds (d1.toMinutes ())));
erickdeoliveiraleal 08
@erickdeoliveiraleal Спасибо, что указали на это. Я думаю, что изначально я писал код для groovy, поэтому он мог работать на этом языке. Исправил сейчас для java.
Торстен
3

Я понимаю, что это может не соответствовать вашему варианту использования, но PrettyTime может быть здесь полезен.

PrettyTime p = new PrettyTime();
System.out.println(p.format(new Date()));
//prints: “right now”

System.out.println(p.format(new Date(1000*60*10)));
//prints: “10 minutes from now”
Ник
источник
4
Возможно ли иметь только «10 минут»?
Johnny