🐘 PHP

PHP中的日期与时间处理:详细指南

Что это такое и зачем? (аналогия из реальной жизни)

Представьте, что вы ведёте дневник. Каждый день вы записываете, что произошло: «Сегодня, 15 марта, я встретился с другом» или «Завтра, в 14:30, у меня тренировка». Без даты и времени ваши записи превратились бы в кашу: вы бы не знали, когда что случилось и что планировать.

В программировании то же самое. Любой сайт или приложение постоянно работает с датами и временем:

  • Когда пользователь зарегистрировался? (дата регистрации)
  • Когда был опубликован пост в блоге? (дата публикации)
  • Через сколько дней заканчивается акция? (разница между датами)
  • Сколько времени прошло с момента отправки сообщения? («5 минут назад»)

PHP — это язык, который работает на сервере. Когда вы заходите на сайт, сервер «думает» в своём времени, а пользователь может находиться в другом часовом поясе. Например, сервер стоит в Москве (MSK, UTC+3), а пользователь живёт во Владивостоке (UTC+10). Если не учитывать это, пользователь увидит, что сообщение отправлено «в 2 часа ночи», хотя на самом деле он писал его в 10 утра по своему времени.

Важно: Работа с датой и временем — это не просто «вывести текущую дату». Это целая наука: перевод между часовыми поясами, вычисление разницы, форматирование для разных стран (в США пишут «03/15/2025», а в России — «15.03.2025»).

В этой статье мы разберём, как PHP помогает решать все эти задачи. Вы научитесь не только получать текущее время, но и управлять им, как настоящий повелитель времени.

Теория: как это работает изнутри

Чтобы понять, как PHP работает с датами, нужно заглянуть «под капот». В основе всего лежит одно число — Unix timestamp (метка времени Unix).

Что такое Unix timestamp?

Представьте себе огромный секундомер, который запустили 1 января 1970 года в 00:00:00 по Гринвичу (UTC). С тех пор он тикает каждую секунду. Текущее значение этого секундомера — это и есть Unix timestamp.

Например:

  • Timestamp = 0 — это 1 января 1970 года, 00:00:00 UTC.
  • Timestamp = 86400 — это 2 января 1970 года, 00:00:00 UTC (потому что в сутках 86400 секунд).
  • Timestamp = 1 700 000 000 — это примерно 14 ноября 2023 года.

Почему это удобно? Компьютеры обожают числа. Сравнить два числа (какой timestamp больше) — это одна операция. Прибавить 7 дней — это прибавить 7 × 24 × 60 × 60 = 604800 секунд. Всё просто и быстро.

Часовые пояса и UTC

Земля круглая, и Солнце встаёт в разное время в разных местах. Чтобы не было путаницы, придумали UTC (Coordinated Universal Time — Всемирное координированное время). Это «нулевой» часовой пояс, от которого пляшут все остальные.

Москва: UTC+3 (зимой) или UTC+4 (летом, но в России отменили переход на летнее время). Владивосток: UTC+10. Нью-Йорк: UTC-5 (зимой).

Когда вы работаете с датами в PHP, лучше всего хранить всё в UTC, а показывать пользователю в его часовом поясе. Так вы избежите путаницы.

Объекты и функции: два подхода

В PHP есть два способа работать с датами:

  1. Функции (старый стиль): date(), time(), strtotime(). Простые, но имеют ограничения. Появились в PHP 4.
  2. Класс DateTime (новый стиль): Объектно-ориентированный подход. Появился в PHP 5.2. Гораздо мощнее и удобнее. Мы будем использовать его.
Совет профессионала: Если вы пишете новый код, всегда используйте класс DateTime. Функции date() и time() оставьте для очень простых скриптов или для поддержки старого кода.

Синтаксис в PHP: все формы и варианты

Давайте разберём основные инструменты для работы с датами. Мы сфокусируемся на классе DateTime, но покажем и функции для понимания.

1. Получение текущего времени

Функции (старый стиль):

<?php
// time() возвращает текущий Unix timestamp (число секунд с 1970 года)
$timestamp = time();
echo "Текущий timestamp: " . $timestamp . "<br>";

// date() форматирует timestamp в понятную строку
// Первый параметр — формат, второй (необязательный) — timestamp
echo "Сегодня: " . date('Y-m-d H:i:s') . "<br>";
echo "Сегодня (с указанием timestamp): " . date('Y-m-d H:i:s', $timestamp);
?>

Объектно-ориентированный стиль (рекомендуется):

<?php
// Создаём объект DateTime для текущего момента
$now = new DateTime();
echo "Текущее время: " . $now->format('Y-m-d H:i:s') . "<br>";

// Можно передать строку с датой в конструктор
$specificDate = new DateTime('2025-03-15 14:30:00');
echo "Конкретная дата: " . $specificDate->format('d.m.Y H:i');
?>

2. Форматы даты и времени

Формат задаётся с помощью символов. Вот самые популярные:

Символ Значение Пример
Y Год (4 цифры) 2025
y Год (2 цифры) 25
m Месяц (01-12) 03
n Месяц (1-12, без нуля) 3
d День месяца (01-31) 15
j День месяца (1-31, без нуля) 15
H Часы (00-23) 14
i Минуты (00-59) 30
s Секунды (00-59) 45
l День недели (полное название) Saturday
D День недели (3 буквы) Sat
F Месяц (полное название) March
M Месяц (3 буквы) Mar

3. Изменение даты (прибавить/отнять)

Самый удобный способ — использовать метод modify() или DateInterval.

<?php
$date = new DateTime('2025-03-15');

// Прибавляем 7 дней с помощью modify
$date->modify('+7 days');
echo "Через неделю: " . $date->format('Y-m-d') . "<br>";

// Отнимаем 1 месяц
$date->modify('-1 month');
echo "Месяц назад: " . $date->format('Y-m-d') . "<br>";

// Более сложный пример: прибавляем 2 года, 3 месяца и 10 дней
$date2 = new DateTime('2025-01-01');
$interval = new DateInterval('P2Y3M10D'); // P — period, Y — years, M — months, D — days
$date2->add($interval);
echo "Через 2 года 3 месяца 10 дней: " . $date2->format('Y-m-d');
?>

4. Разница между датами

Метод diff() возвращает объект DateInterval, из которого можно извлечь годы, месяцы, дни и т.д.

<?php
$start = new DateTime('2025-01-01');
$end = new DateTime('2025-12-31');

$diff = $start->diff($end);
echo "Разница: " . $diff->y . " лет, " . $diff->m . " месяцев, " . $diff->d . " дней<br>";
echo "Всего дней: " . $diff->days; // days — общее количество дней
?>

5. Часовые пояса

Класс DateTime умеет работать с часовыми поясами через класс DateTimeZone.

<?php
// Создаём дату в часовом поясе Москвы
$moscow = new DateTime('now', new DateTimeZone('Europe/Moscow'));
echo "Время в Москве: " . $moscow->format('Y-m-d H:i:s') . "<br>";

// Переводим в часовой пояс Нью-Йорка
$newYork = clone $moscow; // Копируем объект
$newYork->setTimezone(new DateTimeZone('America/New_York'));
echo "Время в Нью-Йорке: " . $newYork->format('Y-m-d H:i:s');
?>

Примеры кода — от простого к сложному

Теперь применим теорию на практике. Каждый пример — это реальная задача, которая может встретиться в разработке.

Пример 1: Вывод текущей даты в русском формате

<?php
// Задача: показать сегодняшнюю дату как "15 марта 2025 года"
// Создаём объект DateTime для текущего момента
$now = new DateTime();

// Форматируем: d — день, F — полное название месяца, Y — год
echo "Сегодня: " . $now->format('d F Y года');
// Вывод: Сегодня: 15 March 2025 года
// Проблема: месяц будет на английском (March)
// Чтобы получить русские названия, нужно использовать setlocale и strftime,
// но это тема отдельного разговора. Пока оставим так.
?>

Пример 2: Сколько дней осталось до Нового года?

<?php
// Задача: вывести сообщение "До Нового года осталось X дней"
$today = new DateTime(); // Сегодня
$newYear = new DateTime('2026-01-01'); // 1 января следующего года

// Вычисляем разницу
$diff = $today->diff($newYear);

// diff->days содержит общее количество дней
echo "До Нового года осталось " . $diff->days . " дней.";
// Вывод: До Нового года осталось 291 день. (зависит от текущей даты)
?>

Пример 3: Определение дня недели для любой даты

<?php
// Задача: узнать, каким днём недели было 1 сентября 2025 года
$date = new DateTime('2025-09-01');

// l — полное название дня недели на английском
echo "1 сентября 2025 года — это " . $date->format('l') . "<br>";

// D — сокращённое название (3 буквы)
echo "Сокращённо: " . $date->format('D') . "<br>";

// N — номер дня недели (1 = понедельник, 7 = воскресенье)
echo "Номер дня недели: " . $date->format('N');
// Вывод: 1 сентября 2025 года — это Monday; Сокращённо: Mon; Номер дня недели: 1
?>

Пример 4: Прибавляем рабочие дни (пропускаем выходные)

<?php
// Задача: к текущей дате прибавить 10 рабочих дней (пн-пт)
$date = new DateTime(); // Начинаем с сегодня
$workingDaysToAdd = 10;
$added = 0;

while ($added < $workingDaysToAdd) {
    $date->modify('+1 day'); // Прибавляем один день
    // Проверяем, не выходной ли это (N = 6 (суббота) или 7 (воскресенье))
    if ($date->format('N') < 6) {
        $added++; // Если рабочий день — увеличиваем счётчик
    }
}

echo "Через 10 рабочих дней будет: " . $date->format('Y-m-d (l)');
// Вывод: Через 10 рабочих дней будет: 2025-04-01 (Tuesday) (пример)
?>

Пример 5: Сравнение дат

<?php
// Задача: проверить, истекла ли дата (например, срок действия купона)
$couponExpiry = new DateTime('2025-06-01');
$today = new DateTime();

// DateTime можно сравнивать операторами <, >, ==
if ($today > $couponExpiry) {
    echo "Купон истёк. Срок действия закончился " . $couponExpiry->format('d.m.Y');
} elseif ($today == $couponExpiry) {
    echo "Купон истекает сегодня!";
} else {
    echo "Купон действителен. Истекает " . $couponExpiry->format('d.m.Y');
}
?>

Пример 6: Вывод времени в формате «только что», «5 минут назад»

<?php
// Задача: показать относительное время (как в соцсетях)
function timeAgo(DateTime $pastTime): string {
    $now = new DateTime();
    $diff = $now->getTimestamp() - $pastTime->getTimestamp();

    if ($diff < 60) {
        return "только что";
    } elseif ($diff < 3600) {
        $minutes = floor($diff / 60);
        return $minutes . " минут назад";
    } elseif ($diff < 86400) {
        $hours = floor($diff / 3600);
        return $hours . " часов назад";
    } elseif ($diff < 2592000) { // 30 дней
        $days = floor($diff / 86400);
        return $days . " дней назад";
    } else {
        return $pastTime->format('d.m.Y');
    }
}

// Пример использования
$postTime = new DateTime('-2 hours'); // 2 часа назад
echo "Пост опубликован: " . timeAgo($postTime);
// Вывод: Пост опубликован: 2 часов назад
?>

Пример 7: Генерация календаря на месяц

<?php
// Задача: вывести календарь на март 2025 года
$year = 2025;
$month = 3;

// Первый день месяца
$firstDay = new DateTime("$year-$month-01");
// Количество дней в месяце
$daysInMonth = (int) $firstDay->format('t');
// Номер дня недели для первого дня (1 = пн, 7 = вс)
$firstDayOfWeek = (int) $firstDay->format('N');

echo "<h3>Календарь на " . $firstDay->format('F Y') . "</h3>";
echo "<table border='1'>";
echo "<tr><th>Пн</th><th>Вт</th><th>Ср</th><th>Чт</th><th>Пт</th><th>Сб</th><th>Вс</th></tr>";

echo "<tr>";
// Пустые ячейки до первого дня
for ($i = 1; $i < $firstDayOfWeek; $i++) {
    echo "<td></td>";
}

// Заполняем дни
for ($day = 1; $day <= $daysInMonth; $day++) {
    echo "<td>$day</td>";
    // Если текущий день — воскресенье (7), начинаем новую строку
    if (($day + $firstDayOfWeek - 1) % 7 == 0) {
        echo "</tr><tr>";
    }
}

// Заполняем пустые ячейки в конце
$lastDayOfWeek = ($daysInMonth + $firstDayOfWeek - 1) % 7;
if ($lastDayOfWeek != 0) {
    for ($i = $lastDayOfWeek + 1; $i <= 7; $i++) {
        echo "<td></td>";
    }
}

echo "</tr>";
echo "</table>";
?>

Сравнение с Python: чем похоже и чем отличается

Если вы уже знакомы с Python, вам будет легче. Но есть важные различия.

Характеристика PHP Python
Основной класс DateTime datetime.datetime
Текущее время new DateTime() datetime.now()
Форматирование format('Y-m-d') strftime('%Y-%m-%d') или isoformat()
Разница между датами diff() возвращает DateInterval Вычитание дат возвращает timedelta
Прибавить время modify('+7 days') или add(DateInterval) timedelta(days=7)
Часовые пояса DateTimeZone pytz (сторонняя библиотека) или zoneinfo (Python 3.9+)
Unix timestamp time() или getTimestamp() time.time() или timestamp()

Главное отличие: В Python работа с датами более «математическая». timedelta — это просто количество секунд, и вы можете складывать его с датами как числа. В PHP DateInterval — это более сложный объект, который учитывает особенности календаря (например, прибавление месяца к 31 января даст 28 февраля, а не 31 февраля, которого не существует).

Важно: В PHP метод modify('+1 month') работает не так, как вы могли бы подумать. Если сегодня 31 января, то +1 месяц даст 3 марта (потому что PHP «переполняет» дни). Будьте осторожны! Для точных расчётов используйте DateInterval с явным указанием дней.

Типичные ошибки и подводные камни

Даже опытные программисты иногда спотыкаются на датах. Вот список самых частых ошибок.

Ошибка 1: Забыть про часовые пояса

<?php
// Плохо: сервер в Москве, пользователь во Владивостоке
$serverTime = new DateTime();
echo "Время на сервере: " . $serverTime->format('H:i:s'); // Покажет московское время

// Хорошо: хранить в UTC, показывать в часовом поясе пользователя
$utcTime = new DateTime('now', new DateTimeZone('UTC'));
$userTimezone = new DateTimeZone('Asia/Vladivostok');
$userTime = clone $utcTime;
$userTime->setTimezone($userTimezone);
echo "Время пользователя: " . $userTime->format('H:i:s');
?>

Ошибка 2: Неправильное форматирование

Путаница между m (месяц с нулём) и n (месяц без нуля), d и j. Или использование Y (4 цифры) вместо y (2 цифры).

Ошибка 3: Сравнение строк вместо объектов

<?php
// Плохо: сравнение строк
$date1 = '2025-03-15';
$date2 = '2025-03-20';
if ($date1 < $date2) { // Работает, но только для формата Y-m-d
    echo "Дата1 раньше";
}

// Хорошо: сравнение объектов
$d1 = new DateTime('2025-03-15');
$d2 = new DateTime('2025-03-20');
if ($d1 < $d2) {
    echo "Дата1 раньше";
}
?>

Ошибка 4: Игнорирование високосных годов

Февраль 2024 года имеет 29 дней, а февраль 2025 — 28. PHP учитывает это автоматически, но если вы работаете с timestamp вручную, легко ошибиться.

Ошибка 5: Предположение, что в месяце 30 дней

Прибавляя «1 месяц» к 31 января, вы получите 3 марта. Если вам нужно «ровно через 30 дней», используйте modify('+30 days').

Лучшие практики и советы профессионалов

Вот несколько правил, которые помогут вам писать надёжный код.

  1. Всегда храните даты в UTC. В базе данных, в логах, в кэше — везде используйте UTC. Преобразуйте в локальное время только при показе пользователю.
  2. Используйте объекты DateTime, а не строки. Строки обманчиво просты, но они не умеют учитывать часовые пояса, високосные годы и прочие сложности.
  3. Не изобретайте велосипед. Для сложных операций (например, «каждый второй вторник месяца») используйте библиотеки, такие как Carbon (популярное расширение для DateTime в PHP).
  4. Проверяйте даты на валидность. Пользователь может ввести «30 февраля». Используйте checkdate() или попробуйте создать DateTime и отловить исключение.
  5. Используйте формат ISO 8601 для обмена данными. Формат Y-m-dTH:i:sP (например, 2025-03-15T14:30:00+03:00) понятен всем системам.
  6. Будьте осторожны с modify('+1 month'). Если вам нужно «следующий месяц, тот же день», используйте более сложную логику или библиотеку Carbon.
  7. Тестируйте на граничных значениях. Проверьте, как ваш код работает 31 декабря, 29 февраля, в день перехода на летнее время (если вы работаете с часовыми поясами, где он есть).
Совет: Установите библиотеку Carbon через Composer (composer require nesbot/carbon). Она расширяет DateTime и добавляет удобные методы: Carbon::now(), ->addWeekday(), ->diffForHumans() (как раз для формата «5 минут назад»). Это стандарт де-факто в современном PHP.

Вопросы для самопроверки

Проверьте, как вы усвоили материал. Постарайтесь ответить сначала сами, потом сверьтесь с ответами.

  1. Что такое Unix timestamp и от какой даты он отсчитывается?
  2. Какой класс в PHP является основным для работы с датами и временем?
  3. Как получить текущую дату в формате «2025-03-15»?
  4. Как прибавить 7 дней к дате с помощью метода modify?
  5. Как вычислить разницу между двумя датами в днях?
  6. Как перевести дату из одного часового пояса в другой?
  7. Почему не стоит сравнивать даты как строки?
  8. Какая проблема может возникнуть при использовании modify('+1 month') для 31 января?
  9. Какой формат даты рекомендуется использовать для хранения в базе данных и обмена данными?
  10. Назовите хотя бы одну популярную библиотеку для работы с датами в PHP.

Ответы:

  1. Unix timestamp — это количество секунд, прошедших с 1 января 1970 года 00:00:00 UTC.
  2. Класс DateTime.
  3. (new DateTime())->format('Y-m-d');
  4. $date->modify('+7 days');
  5. $diff = $date1->diff($date2); echo $diff->days;
  6. Создать объект DateTimeZone для нужного пояса и вызвать setTimezone().
  7. Строки сравниваются лексикографически (по алфавиту), что может дать неверный результат для разных форматов.
  8. 31 января + 1 месяц = 3 марта, потому что PHP «переполняет» дни (в феврале нет 31 дня).
  9. ISO 8601: Y-m-dTH:i:sP (например, 2025-03-15T14:30:00+03:00).
  10. Carbon (nesbot/carbon).

Итог и что изучить дальше

Мы проделали большой путь. Вы узнали:

  • Что такое Unix timestamp и почему это основа всего.
  • Как работать с классом DateTime: создавать, форматировать, изменять даты.
  • Как вычислять разницу между датами и работать с часовыми поясами.
  • Какие ошибки подстерегают новичков и как их избежать.
  • Чем PHP отличается от Python в работе с датами.

Теперь вы можете решать большинство повседневных задач: от вывода даты публикации до сложных расчётов сроков.

Что изучить дальше?

  • Библиотека Carbon: Это «швейцарский нож» для дат в PHP. Изучите её — она сэкономит вам часы работы.
  • Класс DatePeriod: Позволяет создавать диапазоны дат (например, «все понедельники между 1 марта и 1 апреля»).
  • Функции для работы с календарём: cal_days_in_month(), easter_date() (дата Пасхи) — для специфических задач.
  • Интернационализация (intl):