DateAgo
        У меня возникли сложно с DateAgo, поэтому решил написать свой снипет, может кому пригодиться.
В чанке
format:
    
    
                                                                                
            <?php
/**
 * Форматирует дату публикации в относительном или полном формате с поддержкой мультиязычности
 */
if (!defined('MODX_CORE_PATH')) die('Unauthorized access');
$debug = (bool)$modx->getOption('debug', $scriptProperties, true);
if ($debug) {
    $modx->setLogLevel(modX::LOG_LEVEL_INFO);
    $modx->log(modX::LOG_LEVEL_INFO, 'formatDateRussian: Начало работы сниппета');
}
$date = $modx->getOption('publishedon', $scriptProperties, '');
$lang = htmlspecialchars($modx->getOption('lang', $scriptProperties, $modx->getOption('cultureKey', null, 'ru')), ENT_QUOTES, 'UTF-8');
$forceAbsolute = (bool)$modx->getOption('forceAbsolute', $scriptProperties, false);
$showYear = (bool)$modx->getOption('showYear', $scriptProperties, true);
$useCurrentDateFallback = (bool)$modx->getOption('useCurrentDateFallback', $scriptProperties, true);
$defaultOutput = $modx->getOption('defaultOutput', $scriptProperties, 'Нет даты');
// Валидация языка
$allowedLangs = ['ru', 'en'];
if (!in_array($lang, $allowedLangs)) {
    $lang = 'ru';
    if ($debug) $modx->log(modX::LOG_LEVEL_WARN, "formatDateRussian: Некорректный язык '$lang', установлен 'ru'");
}
if ($debug) {
    $modx->log(modX::LOG_LEVEL_INFO, "formatDateRussian: Параметры scriptProperties: " . print_r($scriptProperties, true));
    $modx->log(modX::LOG_LEVEL_INFO, "formatDateRussian: Получены параметры - Date: '$date'");
}
if (empty($date) && $useCurrentDateFallback) {
    $date = date('Y-m-d H:i:s');
    if ($debug) $modx->log(modX::LOG_LEVEL_WARN, "formatDateRussian: Использована текущая дата: '$date'");
}
$locales = [
    'ru' => [
        'months' => [
            '01' => ['января', 'янв'], '02' => ['февраля', 'фев'],
            '03' => ['марта', 'мар'], '04' => ['апреля', 'апр'],
            '05' => ['мая', 'мая'], '06' => ['июня', 'июн'],
            '07' => ['июля', 'июл'], '08' => ['августа', 'авг'],
            '09' => ['сентября', 'сен'], '10' => ['октября', 'окт'],
            '11' => ['ноября', 'ноя'], '12' => ['декабря', 'дек']
        ],
        'relative' => [
            'now' => 'Только что',
            'yesterday' => 'Вчера',
            'ago' => 'назад',
            'units' => [
                'minutes' => ['минуту', 'минуты', 'минут'],
                'hours' => ['час', 'часа', 'часов'],
                'days' => ['день', 'дня', 'дней']
            ]
        ]
    ],
    'en' => [
        'months' => [
            '01' => ['January', 'Jan'], '02' => ['February', 'Feb'],
            '03' => ['March', 'Mar'], '04' => ['April', 'Apr'],
            '05' => ['May', 'May'], '06' => ['June', 'Jun'],
            '07' => ['July', 'Jul'], '08' => ['August', 'Aug'],
            '09' => ['September', 'Sep'], '10' => ['October', 'Oct'],
            '11' => ['November', 'Nov'], '12' => ['December', 'Dec']
        ],
        'relative' => [
            'now' => 'Just now',
            'yesterday' => 'Yesterday',
            'ago' => 'ago',
            'units' => [
                'minutes' => ['minute', 'minutes', 'minutes'],
                'hours' => ['hour', 'hours', 'hours'],
                'days' => ['day', 'days', 'days']
            ]
        ]
    ]
];
$locale = $locales[$lang] ?? $locales['ru'];
$pluralForm = function($number, $unit) use ($locale) {
    $forms = $locale['relative']['units'][$unit] ?? ['','',''];
    $n = abs($number) % 100;
    $remainder = $n % 10;
    if ($n > 10 && $n < 20) return $forms[2];
    if ($remainder > 1 && $remainder < 5) return $forms[1];
    if ($remainder == 1) return $forms[0];
    return $forms[2];
};
$timestamp = 0;
if (is_numeric($date) && $date > 0) {
    $timestamp = (int)$date;
} elseif (!empty($date)) {
    $timestamp = strtotime($date);
}
if ($timestamp <= 0) {
    if ($debug) $modx->log(modX::LOG_LEVEL_WARN, "formatDateRussian: Некорректная дата. Input='$date'");
    return $defaultOutput;
}
$now = time();
$diff = $now - $timestamp;
$days = floor($diff / 86400);
$formatAbsoluteDate = function($timestamp, $locale) use ($showYear) {
    $monthIndex = date('m', $timestamp);
    $monthNames = $locale['months'][$monthIndex] ?? ['', ''];
    return date('d', $timestamp) . ' ' . $monthNames[0] . ($showYear ? ' ' . date('Y', $timestamp) : '');
};
if ($forceAbsolute || $timestamp > $now || $days > 7) {
    $result = $formatAbsoluteDate($timestamp, $locale);
    if ($debug) $modx->log(modX::LOG_LEVEL_INFO, "formatDateRussian: Абсолютная дата, результат: '$result'");
    return $result;
}
if ($diff < 60) {
    if ($debug) $modx->log(modX::LOG_LEVEL_INFO, "formatDateRussian: Менее минуты назад");
    return $locale['relative']['now'];
}
if ($diff < 3600) {
    $minutes = floor($diff/60);
    if ($minutes == 1 && $lang === 'en') {
        $result = "a minute {$locale['relative']['ago']}";
    } else {
        $unit = $pluralForm($minutes, 'minutes');
        $result = "$minutes $unit {$locale['relative']['ago']}";
    }
    if ($debug) $modx->log(modX::LOG_LEVEL_INFO, "formatDateRussian: Минуты, результат: '$result'");
    return $result;
}
if ($diff < 86400) {
    $hours = floor($diff/3600);
    if ($hours == 1) {
        $result = $lang === 'en' ? "an hour {$locale['relative']['ago']}" : "час {$locale['relative']['ago']}";
    } else {
        $unit = $pluralForm($hours, 'hours');
        $result = "$hours $unit {$locale['relative']['ago']}";
    }
    if ($debug) $modx->log(modX::LOG_LEVEL_INFO, "formatDateRussian: Часы, результат: '$result'");
    return $result;
}
if ($days == 1) {
    $result = $lang === 'en' ? "a day {$locale['relative']['ago']}" : $locale['relative']['yesterday'];
    if ($debug) $modx->log(modX::LOG_LEVEL_INFO, "formatDateRussian: Один день, результат: '$result'");
    return $result;
}
$unit = $pluralForm($days, 'days');
$result = "$days $unit {$locale['relative']['ago']}";
if ($debug) $modx->log(modX::LOG_LEVEL_INFO, "formatDateRussian: Дни, результат: '$result'");
return $result;Вывод на основании системных настроек{$_modx->resource.publishedon | formatDateRussian}В чанке
{'formatDateRussian' | snippet : [
    'publishedon' => $publishedon,
    'format' => 'full',
    'lang' => 'en',
    'showYear' => true,
    'debug' => 1
]}Описаниеformat:
- relative (по умолчанию): выводит относительные интервалы («Только что», «5 минут назад», «Вчера» и т.д.).
 - full: выводит полную дату («09 мая, 2025»).
 
- true (по умолчанию): включает год в полном формате.
 - false: убирает год (например, «09 мая»).
 
- ru (по умолчанию через cultureKey): русский язык.
 - en: английский язык.
 
Комментарии: 7
                Перенес в готовые решения            
                    showYear:погодите, а разве параметр dateFormat не закрывает этот момент?
true (по умолчанию): включает год в полном формате.
false: убирает год (например, «09 мая»).
там же какой формат укажешь такой и отобразится
добавьте у себя тоже возможность указания формата
if (!defined('MODX_CORE_PATH')) die('Unauthorized access');я конечно все понимаю, но реально зачем эта строка? — это же сниппет… Он без ядра в принципе не запустится или это типа защита от запуска файла напрямую?
                if (!defined('MODX_CORE_PATH')) die('Unauthorized access'); — это мой З#**** (при загруженных проектах я часто разношу все) Если кто-то попытается обратиться к файлу напрямую (например, через URL вроде site.ru/assets/snippets/mysnippet.php. 
можно использовать пространства имён и автозагрузку, но проверка MODX_CORE_PATH остаётся простым и надёжным способом защиты.
(просто как доп. уровень, это наверно из разряда xss csp)
погодите, а разве параметр dateFormat не закрывает этот момент?
Все верно вы говорите, просто я его еще не доделал. Изначально я хотел что при текущем годе показа года не было.
Вот сырой код, если хотите посмотреть, для понимания
 
 
                    можно использовать пространства имён и автозагрузку, но проверка MODX_CORE_PATH остаётся простым и надёжным способом защиты.
(просто как доп. уровень, это наверно из разряда xss csp)
погодите, а разве параметр dateFormat не закрывает этот момент?
Все верно вы говорите, просто я его еще не доделал. Изначально я хотел что при текущем годе показа года не было.
Вот сырой код, если хотите посмотреть, для понимания
$formatAbsoluteDate = function($timestamp, $locale) use ($showYear) {
    $monthIndex = date('m', $timestamp);
    $monthNames = $locale['months'][$monthIndex] ?? ['', ''];
    $year = date('Y', $timestamp);
    $currentYear = date('Y');
    
    // !!!
    $shouldShowYear = $showYear && ($year != $currentYear);
    
    return date('d', $timestamp) . ' ' . $monthNames[0] . ($shouldShowYear ? ' ' . $year : '');
};            
                Можете просто убрать, также там есть лишний код («LOG_LEVEL_INFO») который, который по факту тоже никому не будет нужен, кроме как на этапе проверки.            
                    
                Никогда не храните код .php или шаблоны .tpl в папке /assets, это прям нельзя.
Такие штуки не должны быть доступны пользователю через http запрос. php код может выполнится, что небезопасно, а код шаблона вообще просто отобразится или файл скачается.
Их нужно хранить или в директории core, которая должна быть закрыта от доступа через правила веб-сервера, или же вообще в идеале выше, чем public каталог.
ну и тогда не нужно парится насчет «Если кто-то попытается обратиться к файлу напрямую»
                    Такие штуки не должны быть доступны пользователю через http запрос. php код может выполнится, что небезопасно, а код шаблона вообще просто отобразится или файл скачается.
Их нужно хранить или в директории core, которая должна быть закрыта от доступа через правила веб-сервера, или же вообще в идеале выше, чем public каталог.
ну и тогда не нужно парится насчет «Если кто-то попытается обратиться к файлу напрямую»
                Я опечатался, в assets, только tpl, а core закрыта htaccess (IfModule mod_authz_core.c)            
                    
                            Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.