За последние 24 часа нас посетили 8772 программиста и 457 роботов. Сейчас ищет 121 программист ...

Работа с датой в MySQL + PHP [заготовка FAQ]

Тема в разделе "MySQL", создана пользователем artoodetoo, 21 ноя 2014.

  1. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.321
    Симпатии:
    1.033
    Адрес:
    там-сям
    Это заготовка, я буду ее редактировать! Не спешите срать в каменты.

    Первое, что должен усвоить новичек, это то, что существуют разные типы данных. Почти любое значение можно преобразовать в строку и наоборот из строки получить значение какого-то типа. PHP очень хорошо умеет работать со строками и обычно неявно преобразует строки в числа и обратно так, что вы даже не догадываетесь. Но с датами случай особый!

    Запомните, для значений типа "дата" или "дата+время" в PHP используются целые числа! Это число секунд, прошедших с полуночи 1 января 1970г по гринвичскому времени ("дата рождения Unix").

    Дата-время в виде числа дает нам массу возможностей: можно прибавлять значения, находить разницу между двумя значениями и корректно сравнивать их на больше-меньше.
    Со строковой записью даты вы такого проделать не сможете!!!


    Пример 1: несколько понятных выражений
    Код (PHP):
    1. <?php
    2. // получим целое число, соответствующее "сегодня", т.е. 00:00:00 сегодняшней даты
    3. $d1 = strtotime('today'); 
    4.  
    5. // для отладки выведем как есть
    6. echo $d1, "\n"; // выведется большое целое число
    7.  
    8. // узнаем сколько секунд прошло с полуночи
    9. echo time() - $d1, "\n"; // выведется не очень большое целое число
    10.  
    11. // получим дату "завтра"
    12. $d2 = $d1 + 60*60*24;
    13.  
    14. // сравним даты
    15. if ($d1 < $d2) { echo "Сегодня наступает раньше чем завтра\n"; }
    16. else { echo "Случился провал во времени\n"; }
    17.  
    18. // наконец мы вычислили всё, что нужно и хотим красиво вывести дату и время на страницу
    19. echo date('d.m.Y H:i:s', $d2), "\n"; // что-то вроде '22.11.2014 00:00:00'
    20.  
    В базе MySQL тоже используются различные типы, но шлюз между базой и скриптом на PHP устроен так, что вы всегда получаете данные уже сформатированными в строки. Не забывайте об этом и, когда нужно, преобразуйте типы явно!

    Поясню:
    Если вы совершили ошибку и для хранения даты завели колонку типа VARCHAR, то там может оказаться, например, такое значение:
    '20.11.2014 года'. Это геморой, который можно показать на странице, но невозможно как-то использовать в вычислениях/сравнениях ни на стороне БД, ни на стороне PHP!

    А если вы хороший человек и храните дату в колонке типа DATE, то ваше значение после считывания в PHP будет выглядеть как
    '2014-11-20'. В текстах по базам данных такой формат называют "ISO date" (см. стандарт ISO 9075 про язык SQL). В MySQL вы можете напрямую работать с такими значениями как с датой. В PHP её распознает strtotime().

    Пример 2: MySQL. пусть колонка test.bithday имеет тип DATE. нам надо выбрать все записи пользователей, которые родились не позже чем 7 февраля 2014г (старт Олимпиады в Сочи)
    Код (Text):
    1. SELECT * FROM test WHERE birthday <= '2014-02-07'
    Пример 3: В PHP вы можете сделать так:
    Код (PHP):
    1. // Дата церемонии открытия
    2. $dateX = strtotime('2014-02-07');
    3. // читаем запись из БД как массив строк, например ['id' =>101, 'name'=>'John', 'birthday' => '1992-10-12']
    4. $row = mysqli_fetch_assoc($result);
    5. // а теперь у будет настоящая дата, которую можно использовать в вычислениях
    6. $row['birthday'] = strtotime($row['birthday']);
    7. if ($row['birthday'] > $dateX) {
    8.     printf("%s слишком молод!\n", $row['name']);
    9. } else {
    10.     printf("%s мог присутствовать.\n", $row['name']);
    11. }
    Не всякая строка может корректно преобразовываться в дату. Например здесь ошибка:
    Код (PHP):
    1. var_dump(strtotime('20.11.2014 года')); // выведет bool(false). ВНЕЗАПНО!             
    Теперь для упертых д*в, которые скажут "мне удобно работать с датой именно в таком виде".
    Окей, попробуем насколько удобно. Строки ведь можно сравнивать, проверим результат:
    Код (PHP):
    1. if ('12.10.1992' > '07.02.2014') {
    2.     echo "John слишком молод!\n";
    3. } else {
    4.     echo "John мог присутствовать.\n";
    5. } 
    Угадайте что выведется? ))) Получается, что день важнее чем год, круто, да?! С американским стандартом m/d/Y было бы тоже весело.
    Или попробуйте к "дате" '12.10.1992' прибавить один день. Приходит на ум такое:
    (опять же синтаксической ошибки не будет, поэтому нуб будет долго думать что же пошло не так)
    Код (PHP):
    1. echo '12.10.1992' + 1; // = 13.1 внезапно!    
    Общий совет: следуйте парадигме MVC.
    Храните данные в базе в колонках с подходящим типом: DATE для даты, DATETIME для даты+время. NUMERIC для денежных величин, VARCHAR или TEXT для настоящих строк. После считывания из базы в PHP, сразу преобразуйте не-строковые данные к нужному типу. В Модели и Контроллере работайте с данными в правильном типе, чтобы все вычисления и преобразования проходили легко и непринужденно.
    И только при выводе на страницу (то есть в Представлении) форматируйте данные так, как вам хочется их видеть.

    Пример 4:
    Код (PHP):
    1. <?php
    2.  
    3. // эти данные какбы считаны в mysqli_row() :) то есть изначально это массив строковых значений
    4. $row = [
    5.   'name'        => 'John',
    6.   'start_date'  => '2012-02-08',
    7.   'amount'      => '100.50'
    8. ];
    9. // Приводим в рабочий вид: 
    10. //   честная дата
    11. $row['start_date'] = strtotime($row['start_date']);
    12. //   деньги как вещественный тип
    13. $row['amount'] = floatval($row['amount']); 
    14. // Банки считают немного сложнее, но для нашего примера пойдёт:
    15. // начислим процент на разницу в годах
    16. $today = strtotime('today');
    17. $years = idate('Y', $today)
    18.        - idate('Y', $row['start_date']);
    19. $amountWithPct = $row['amount'] + 0.01 * $years;
    20. // Результат красиво выведем
    21.     "%s открыл счет %s и теперь у него $%.2F",
    22.     $row['name'],
    23.     date('d.m.Y', $row['start_date']),
    24.     $amountWithPct
    25. ); 
    -----
    • Кое-что по русски про типы данных MySQL DATETIME, DATE и TIMESTAMP[/*:m]
    • Mysql Date and Time Functions (english)[/*:m]
    • Популярное на Stackoverflow с тегами php+date (english)[/*:m]
    • Популярное на Stackoverflow с тегами mysql+php+date (english)[/*:m][/list:u]
     
    [vs] нравится это.
  2. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.321
    Симпатии:
    1.033
    Адрес:
    там-сям
    Обычная ошибка нуба, это то, что php-шная функция date() якобы возвращает текущую дату. На самом деле она возвращает строку и не обязательно по текущей дате — это зависит от аргументов. А текущую дату+время возвращает функция time() !!!

    Функции, которые возвращают тип дата-время как целое: time, strtotime, mktime, gmmktime, filemtime, filectime, getlastmod

    Функции, которые использую такую дату как аргумент, а возвращают что-то иное: date, getdate, idate, localtime, strftime.
    Все они, как date(), допускают краткий вариант с параметром по умолчанию — неявным вызовом time()

    Справку по любой функции вы можете получить набрав в адресной строке http://php.net/имя_функции

    (Есть еще прикольные встроенные в PHP классы DateTime и DateInterval. А к ним куча функций-алиасов для краткого обращения к методам. Но я бы не советовал нубам браться за них пока не освоились с классическим Unix timestamp, только мешанина в голове возникнет.)

    Добавлено спустя 20 минут 12 секунд:

    Отдельного респекта заслуживает мега-функция strtotime(). С ее помощью можно вычислить многие значения типа дата очень лаконично и наглядно. Чтобы сделать то же самое самостоятельно, вам бы понадобилось немало букв (с возможностью накосячить).

    Код (PHP):
    1. $d1 = strtotime('today'); // "сегодня". отличается от time() тем, что время равно 0.
    2. $d2 = strtotime(date('Y-m-1')); // первое число текущего месяца
    3. $d3 = strtotime('+1 month', $d2); // первое число следующего месяца
    4. $d4 = strtotime('+1 month -1 day', $d2); // последнее число текущего месяца
    5. $d5 = strtotime('today next Monday'); // следующий понедельник      
    ВАЖНО: strtotime('01:00:00') означает не время равное 3600 секунд, как может показаться, а дату-время "сегодня в час ночи в текущем часовом поясе", т.е. довольно большое целое число )))

    Некоторые полезные преобразования:
    Код (PHP):
    1. $stime24 = date('H:i', strtotime('04:25 PM'));  // строку времени с AM/PM превращает в 24ч формат
    2. $timediff = strtotime('01:00:00')-strtotime('00:00:00'); // 3600 независимо от даты и часового пояса  
    ссылки на интересные "особенности" дат в php:
    https://php.ru/forum/threads/php-gljuki.60966/#post-591867
    https://php.ru/forum/threads/oshibka-funkcii-vfeu.75040/
     
    [vs] нравится это.
  3. mkramer

    mkramer Суперстар
    Команда форума Модератор

    С нами с:
    20 июн 2012
    Сообщения:
    7.754
    Симпатии:
    1.506
    artoodetoo, я как раз всё собирался задать вопрос, как кто с датой работает. Хотя явных проблем, как у чела, после поста которого вы это написали, у меня не было, но мне так лениво из ISO переводить дату при выводе всегда... Я себе в модели ActiveRecord ставил перекрытие метода __get(), чтоб поля-даты сразу приводил к нужному виду. Хотя для тех проектов это работало - там ничего с датами делать было не нужно.
     
  4. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.321
    Симпатии:
    1.033
    Адрес:
    там-сям
    ну вобщем-то я уже описал как это у меня. я бы тоже непрочь полениться, но как-то не получается ))) чуть проще выходит там, где вместо истинной даты в базе используется INT, то есть такой неродной для mysql вариант timestamp. тогда преобразования остаются только в представлении, а select и update без конвертации.

    activerecord я не использую. есть простой dbal - чуть больше удобства чем pdo. преобразования даты не на автомате, а вручную.
    и сейчас пытаюсь нарабатывать простой datamaper. там уже в методах load и save можно мутить что-то под конкретные поля.
    я на форуме уже пытался получить фидбек по теме dbal+orm, но как-то незаметно прошло.
     
  5. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.419
    Симпатии:
    1.741
    Я за таймштамп.
     
    glorsh66 нравится это.
  6. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.321
    Симпатии:
    1.033
    Адрес:
    там-сям
    Еще пару слов про мега-функцию strtotime. В мире используются несколько стандартов написания даты. Во многих европейских странах и в России принято писать так
    05.12.2016 - то есть дд.мм.гггг
    В США и её приспешниках так
    12/05/2016 - то есть мм/дд/гггг
    а в международные стандарты, в т.ч. по языку SQL, заложено такое написание
    2016-12-05 - то есть гггг-мм-дд
    функция strtotime() распознаёт все эти варианты правильно. то есть она обращает внимание на разделитель между группами цифр и делает вывод о формате

    Проверка
    Код (Text):
    1. C:\> php -r "var_dump(strtotime('05.12.2016') === strtotime('12/05/2016'));"
    2. bool(true)
     
  7. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.321
    Симпатии:
    1.033
    Адрес:
    там-сям
    1 день не всегда равен 86400 секунд :) демонстрация проблемы:
    https://gist.github.com/artoodetoo/2915dc07d1904a173e76f0bf5966e00f

    в Москве
    '2018-10-06 10:00:00' + 24*60*60 == '2018-10-07 10:00:00'
    время дня осталось то же самое

    в Мельбурне
    '2018-10-06 10:00:00' + 24*60*60 == '2018-10-07 11:00:00'
    время изменилось

    Решение: strtotime('+1 day', $ts) учитывает возможную смену летнего/зимнего времени. Надо только не забыть установить временну́ю зону.
    Класс DateTime (и производный от него Carbon) тоже учитывает временну́ю зону и DST.
     
  8. [vs]

    [vs] Суперстар
    Команда форума Модератор

    С нами с:
    27 сен 2007
    Сообщения:
    10.450
    Симпатии:
    582
    А можно напишу сюда про класс DateTime?
     
    acho и denis01 нравится это.
  9. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.321
    Симпатии:
    1.033
    Адрес:
    там-сям
    Жги, Вася!