Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама
Посібники 30 жовтня 2026

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

diffForHumans() з Carbon є майже в кожному Laravel-проєкті: «2 години тому» читається краще за повну дату. Але в нього є поведінка, про яку мало хто думає: він не округлює, а відкидає залишок. Подія 47 годин тому для нього - «1 день тому». На цю деталь нагадав Джоел Клермонт (Joel Clermont) у Mastering Laravel; нижче - як це працює і що з цим робити, з рядками, які Carbon справді повертає українською.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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, а не українською і:

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

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

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

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

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() у новий екран, варто на секунду спитати себе, що побачить людина, яка прийде туди з'ясовувати, коли щось востаннє спрацювало.

10

Читати в документації

Коментарі

Увійдіть, щоб залишити коментар

Будьте першим, хто залишить коментар!

Читайте також

Патерн Null Object
Посібники 10 вересня 2026
Патерни проєктування

Null Object - поведінка за замовчуванням

Замінює null безпечним об'єктом із тим самим інтерфейсом і "порожньою" поведінкою. Прибирає перевірки на null - і де він може приховати справжню проблему.

17

Вакансії за темою

Senior PHP Developer / Software Architect

Senior PHP-розробник та архітектор для розвитку й модернізації платформи публічних закупівель з фінансовими процесами та інтеграціями. Роль поєднує проєктування складних backend-систем, рефакторинг legacy-компонентів і безпосередню розробку на PHP 8+ із Symfony або Laravel. Ключові вимоги: 10+ років PHP-розробки, 5+ років у FinTech, активне використання AI-інструментів у розробці, досвід REST API, Redis/RabbitMQ, Docker і CI/CD.

EpicentrK Нова
2 дні тому

Backend PHP Developer (Middle+/Senior)

Розробник бекенду на PHP, який інтегрує внутрішні та зовнішні системи, проєктує й оптимізує складні SQL-запити та бере участь у плануванні архітектури сервісів. Потрібен досвід комерційної розробки на PHP 7.x/8.x від 4 років, робота з Symfony та/або Laravel, REST API, PostgreSQL/MySQL, MongoDB/Redis, RabbitMQ та Elasticsearch, а також глибоке знання OOP, SOLID і проєктування індексів.

EpicentrK Нова
2 дні тому

PHP Backend Developer (Middle+ / Senior)

Розробка та підтримка backend-сервісів на PHP: новий функціонал, REST API, інтеграції зі сторонніми сервісами, фонові завдання та черги. Ключовий фокус на PostgreSQL: проєктування схем, оптимізація складних SQL-запитів і індексів, аналіз продуктивності через EXPLAIN ANALYZE. Вимоги: від 4 років комерційного досвіду з PHP 8.x, фреймворки Laravel, Symfony або Yii2, робота зі складною бізнес-логікою, тести та Code Review.

Пакети за темою

Kalimera

welldigit/kalimera

Interactive installer that scaffolds Laravel applications the Well Digit way.

v1.0.0 16

Bagisto

bagisto/bagisto

Bagisto — це платформа для електронної комерції, побудована на Laravel. Вона надає готове рішення для створення та управління інтернет-магазинами з підтримкою каталогу товарів, замовлень, платежів та клієнтів.

28,284 v2.5.0 13 26