Як я можу “гарненько надрукувати” тривалість на Java?


93

Хтось знає про бібліотеку Java, яка може надрукувати число за мілісекунди так само, як це робить C #?

Наприклад, довжина 123456 мс надрукується як 4d1h3m5s.


1
FYI, формат, який ви, напевно, описуєте, Durationвизначений розумним стандартом ISO 8601 : PnYnMnDTnHnMnSде Pозначає "Період" і позначає початок, Tвідокремлює дату від часової частини, а між ними - необов'язкові випадки зустрічальності числа з одним -буквене скорочення. Наприклад, PT4H30M= чотири з половиною години.
Василь Бурк

1
Якщо все інше не вдається, зробити це самостійно дуже просто. Просто використовуйте послідовні програми %та, /щоб розділити число на частини. Майже простіше, ніж деякі із запропонованих відповідей.
Hot Licks

@HotLicks Залиште його бібліотеки методів набагато чистіше і більш чіткого коду , ніж при використанні /і %.
Оле В. В.

Так, за останні 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
З відповіді нижче випливає, що екземпляр Періоду можна створити безпосередньо, не створюючи спочатку екземпляр Тривалості, а потім перетворюючи його на Період. Наприклад, Період періоду = новий Період (міліс); Рядок відформатований = formatter.print (крапка);
Basil Vandegriend

7
Остерігайтеся цього перетворення "duration.toPeriod ()". Якщо тривалість досить велика, денна порція надалі залишатиметься рівною 0. Часова частина буде продовжувати зростати. Ви отримаєте 25h10m23s, але ніколи не отримаєте "d". Причина в тому, що немає абсолютно правильного способу перевести години в дні строгими способами Джоди. У більшості випадків, якщо ви порівнюєте два моменти і хочете його надрукувати, ви можете зробити новий Період (t1, t2) замість нового Duration (t1, t2) .toPeriod ().
Бун,

@Boon, чи знаєте ви, як перевести тривалість у період відносно днів? Я використовую тривалість, з якої я можу замінити другий за моїм таймером. Налаштування події PeriodType.daysTime()або .standard()не допомогло
мармування

4
@murt Якщо ви дійсно хочете і можете прийняти стандартне визначення дня, тоді ви можете "formatter.print (duration.toPeriod (). normalizedStandard ())" у коді вище. Веселіться!
Бун

74

Я створив просте рішення, використовуючи 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.


6
Ніколи не слід покладатися на результати toStrong (), оскільки це може змінитися в наступних версіях.
Weltraumschaf

14
@Weltraumschaf це, швидше за все, не зміниться, оскільки це вказано в javadoc
aditsu кидає, оскільки SE є ЗЛО

9
@Weltraumschaf Навряд чи це зміниться, оскільки вихідні дані Duration::toStringвідформатовані відповідно до чітко визначеного та зношеного стандарту ISO 8601 .
Василь Бурк

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 (В: м: с.millis) DurationFormatUtils.formatDurationISO( 15362 * 1000 ) )=> P0Y0M0DT4H16M2.000S, пор. ISO8601


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().
Томас Бове,

9

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


4
Зауважте, що це не друкується днів
Вім Деблауве

58
Не впевнений, що цей формат відповідає моєму визначенню гарного друку. :-)
james.garriss

@ james.garriss Щоб зробити його трохи гарнішим , див . Відповідь erickdeoliveiraleal.
Василь Бурке

7

Ось як це можна зробити, використовуючи чистий код 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()); 

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
Ого, приємно знати, я не бачив цієї функції. Хоча є одна головна вада: відсутність оксфордської коми .
Василь Бурк,

4
@BasilBourque Оксфордська кома типова для США, але цікаво не для Великобританії. Мій lib Time4J (який має набагато кращу інтернаціоналізацію) підтримує цю відмінність у класі net.time4j.PrettyTime, а також дозволяє контролювати компактний вивід за бажанням.
Мено Хохшильд,

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 с


Як ви отримуєте "toHoursPart" і вище?
Ghoti and Chips

4

Альтернативою будівельному підходу 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 мс буде надруковано як 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, тоді як у Великобританії - ні. Остання версія v5.5 Time4J підтримує понад 90 мов і використовує переклади на основі репозиторію CLDR (галузевий стандарт).


2
цей PrettyTime набагато кращий, ніж той, що в іншій відповіді! шкода, що я не знайшов цього першим.
ycomp

Я не знаю, чому зараз існує два голоси проти цього добре працюючого рішення, тому я вирішив розширити відповідь, наводячи більше прикладів також про синтаксичний аналіз (протилежний форматуванню) та виділити неправильне очікування ОП.
Мено Хохшильд

4

Версія Java 8 на основі відповіді користувача678573 :

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

з Тривалість d1 = Тривалість днів (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

@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 хвилин"?
Джонні,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.