---
title: "diffForHumans() не округлює: як отримати точний відносний час"
url: https://laravelukraine.com/blog/diffforhumans-ne-okrugliuje-iak-otrimati-tocnii-vidnosnii-cas
date: 2026-10-30
---

# diffForHumans() не округлює: як отримати точний відносний час

`diffForHumans()` з Carbon є майже в кожному Laravel-проєкті: «2 години тому» читається краще за повну дату. Але в нього є поведінка, про яку мало хто думає: він не округлює, а відкидає залишок. Подія 47 годин тому для нього - «1 день тому». На цю деталь нагадав [Джоел Клермонт (Joel Clermont) у Mastering Laravel](https://masteringlaravel.io/daily/2026-10-29-why-does-diffforhumans-say-1-day-ago-for-something-47-hours-old); нижче - як це працює і що з цим робити, з рядками, які Carbon справді повертає українською.

### Що відбувається за замовчуванням

Carbon рахує різницю, бере найбільшу ненульову одиницю і показує тільки її. 47 годин - це 1 день і 23 години, тож лишається «1 день»:

```php
now()->subHours(47)->diffForHumans();
// 1 день тому

now()->subHours(2)->subMinutes(59)->diffForHumans();
// 2 години тому
```

Тобто 2 години 59 хвилин - це «2 години», а не «3». Для часу коментаря чи посту така грубість нікому не шкодить. А от у рядку «Останній бекап: 1 день тому» на адмінці вона приховує, що бекапу насправді майже дві доби. Те саме з «останньою невдалою задачею» в черзі чи «останнім входом» користувача.

### Округлення: `ROUND` і `CEIL`

Якщо потрібна одна одиниця, але чесна, передайте опцію округлення. Усі параметри йдуть масивом у перший аргумент (див. [документацію Carbon](https://carbon.nesbot.com/docs/#api-humandiff)):

```php
use Carbon\CarbonInterface;

now()->subHours(47)->diffForHumans(['options' => CarbonInterface::ROUND]);
// 2 дні тому

now()->subHours(35)->diffForHumans(['options' => CarbonInterface::ROUND]);
// 1 день тому

now()->subHours(25)->diffForHumans(['options' => CarbonInterface::CEIL]);
// 2 дні тому
```

`ROUND` округлює до найближчого: 36 годин уже стають «2 дні», 35 ще лишаються «1 день». `CEIL` округлює завжди вгору - зручно там, де недооцінка гірша за переоцінку: «скільки часу без бекапу» краще трохи перебільшити, ніж применшити.

### Точність: `parts`, `short` і `join`

Коли одна одиниця замало, попросіть кілька:

```php
now()->subHours(47)->diffForHumans(['parts' => 2]);
// 1 день 23 години тому

now()->subDays(3)->subHours(5)->diffForHumans(['parts' => 3]);
// 3 дні 5 годин тому
```

`parts` - це максимум одиниць: якщо хвилин нуль, третьої частини не буде. Для таблиць і бейджів, де місця мало, є `short`:

```php
now()->subHours(2)->subMinutes(30)->diffForHumans([
  'parts' => 2,
  'short' => true,
]);
// 2г 30хв тому
```

Опція `join` задає, чим склеювати частини. `'join' => true` бере сполучник із перекладу локалі, а рядок чи масив `[роздільник, останній роздільник]` - ваш власний:

```php
now()->subDays(3)->subHours(5)->subMinutes(7)->diffForHumans([
  'parts' => 3,
  'join' => [', ', ' і '],
]);
// 3 дні, 5 годин і 7 хвилин тому
```

### Українська локаль: на що зважати

Laravel передає Carbon свою локаль сам: пакет `nesbot/carbon` реєструє сервіс-провайдер, який при старті бере `config('app.locale')`, а потім слухає подію `LocaleUpdated`. Тому достатньо `'locale' => 'uk'` у `config/app.php` або `app()->setLocale('uk')` у middleware. Поза Laravel - `Carbon::setLocale('uk')` або `->locale('uk')` на конкретному об'єкті.

Відмінки українська локаль Carbon 3 обробляє правильно: «1 годину тому», «21 годину тому», «2 години тому», «5 годин тому». Є й `CarbonInterface::ONE_DAY_WORDS`, з яким різниця в один день стає «вчора».

Одна несподіванка - у `'join' => true`. Сполучник у файлі перекладу `uk` записано латинською `i`, а не українською `і`:

```php
now()->subHours(2)->subMinutes(30)->diffForHumans([
  'parts' => 2,
  'join' => true,
]);
// 2 години i 30 хвилин тому  <- тут латинська "i"
```

На око різниці немає, але пошук сторінкою, перевірка правопису і точне порівняння рядків у тесті її помітять. Якщо вам потрібен сполучник, передайте його явно масивом, як у прикладі вище. (Перевірено на Carbon 3.15.1.)

### Закріпіть рядок тестом

Відносний час залежить від «зараз», тож без замороженого годинника тест на нього або нестабільний, або нічого не перевіряє. У Laravel для цього є `travelTo()` (або `Carbon::setTestNow()` поза тестовим класом Laravel):

```php
use Carbon\CarbonInterface;
use Illuminate\Support\Carbon;

public function test_last_backup_is_rounded(): void
{
  $this->travelTo(Carbon::parse('2026-10-30 12:00'));
  app()->setLocale('uk');

  $finishedAt = now()->subHours(47);

  $this->assertSame(
    '2 дні тому',
    $finishedAt->diffForHumans(['options' => CarbonInterface::ROUND]),
  );
}
```

Такий тест ловить і зміну опцій, і сюрпризи при оновленні Carbon, бо файли перекладів у ньому теж змінюються.

### Що обрати

- Стрічка коментарів, дата посту: типова поведінка цілком годиться.
- Моніторинг, бекапи, черги, «востаннє онлайн»: `ROUND` або `CEIL`, залежно від того, яка помилка дешевша.
- Таблиці й дашборди, де важить точність: `'parts' => 2` разом із `short`.
- Якщо рядок бачить користувач і від нього щось залежить - закріпіть його тестом із замороженим часом.

Перед тим як додати `diffForHumans()` у новий екран, варто на секунду спитати себе, що побачить людина, яка прийде туди з'ясовувати, коли щось востаннє спрацювало.
