Към съдържанието
PN Scripts

Разработка

Работно време с българските празници и schema.org LocalBusiness в WordPress

  • PN Scripts
  • 9 мин четене

Работното време на сайта на малка фирма обикновено е ред текст в долната част на страницата: „Пон до пет 8 до 19, съб 9 до 14“. През по-голямата част от годината е вярно. Грешно е на 3 март, по Великден, по Коледа, в понеделника, който замества празник, паднал се в събота, и всеки ден по обяд, ако магазинът затваря от 13:00 до 14:00. Клиентите, които идват и намират заключена врата, не се връщат, а търсачките повтарят каквото пише на страницата. Тук показваме как да опишете истинското работно време в WordPress, да включите българските празници, да показвате „отворено сега“, което е вярно дори от кеширана страница, и да дадете на търсачките същата информация като данни schema.org. Всички примери с код са пуснати на PHP 8.4 и Node.

#Описвайте времето като интервали, а не като текст

Всичко останало зависи от това. Денят е списък от интервали, а не низ: два интервала за обедна почивка, празен списък за почивен ден и интервал, чийто край е преди началото, за бар, който затваря след полунощ.

<?php

$week = [
    'mon' => [['08:00', '13:00'], ['14:00', '19:00']],
    'tue' => [['08:00', '13:00'], ['14:00', '19:00']],
    'wed' => [['08:00', '13:00'], ['14:00', '19:00']],
    'thu' => [['08:00', '13:00'], ['14:00', '19:00']],
    'fri' => [['08:00', '13:00'], ['14:00', '19:00']],
    'sat' => [['09:00', '14:00']],
    'sun' => [],
];

function weekly_spec(array $week): array
{
    $names = ['mon' => 'Monday', 'tue' => 'Tuesday', 'wed' => 'Wednesday', 'thu' => 'Thursday',
        'fri' => 'Friday', 'sat' => 'Saturday', 'sun' => 'Sunday'];
    $groups = [];
    foreach ($names as $key => $name) {
        foreach ($week[$key] ?? [] as [$opens, $closes]) {
            $groups["$opens-$closes"][] = $name;
        }
    }
    $out = [];
    foreach ($groups as $span => $days) {
        [$opens, $closes] = explode('-', $span);
        $out[] = ['@type' => 'OpeningHoursSpecification', 'dayOfWeek' => $days,
            'opens' => $opens, 'closes' => $opens === '00:00' && $closes === '00:00' ? '23:59' : $closes];
    }
    return $out;
}

weekly_spec() превръща седмицата в записи OpeningHoursSpecification на schema.org, като групира дните с еднакъв интервал. За пекарната по-горе дава два записа за понеделник до петък (сутрин и следобед) и един за събота; неделята просто липсва, което значи „затворено“. Функцията следва правилата в документацията на Google за местни фирми: нощ, която продължава след полунощ, остава един запис (петък, отваря 18:00, затваря 02:00), а денонощно отворен ден се пише от 00:00 до 23:59.

#Празници: решете правило, а датите да имат предимство

Българският календар има празници на постоянни дати, великденски празници по православната дата, почивни дни, които заместват празник в събота или неделя (Кодекс на труда, чл. 154, ал. 2), и дни, които правителството обявява. Обяснихме тези правила в ръководството ни за работните дни и празниците в България. Например през 2026 г. 26 декември се пада в събота, затова понеделник, 28 декември, е почивен.

Календарът казва кои дни са празници. Дали фирмата ви затваря, решавате вие и това е различно за всяка фирма: пекарна може да затвори на Коледа, но да работи кратко сутрин на Бъдни вечер; аптека може да работи обичайно; ресторант може да е отворен по-дълго. Затова пазете правило за официалните празници (затворено, обичайно време или празнично време) и списък със свои дати, който винаги има предимство:

<?php

// $policy for public holidays: 'closed', 'usual' or a list of intervals. Your own dates always win.
function special_days(array $holidays, string|array $policy, array $own, string $from, int $days): array
{
    $out = [];
    $day = new DateTimeImmutable($from);
    for ($i = 0; $i < $days; $i++, $day = $day->modify('+1 day')) {
        $date = $day->format('Y-m-d');
        if (array_key_exists($date, $own)) {
            $out[$date] = $own[$date];
        } elseif (isset($holidays[$date]) && $policy !== 'usual') {
            $out[$date] = $policy === 'closed' ? [] : $policy;
        }
    }
    return $out;
}

function special_spec(array $special): array
{
    $out = [];
    foreach ($special as $date => $intervals) {
        foreach ($intervals ?: [['00:00', '00:00']] as [$opens, $closes]) {
            $out[] = ['@type' => 'OpeningHoursSpecification', 'validFrom' => $date,
                'validThrough' => $date, 'opens' => $opens, 'closes' => $closes];
        }
    }
    return $out;
}

За нашата пекарна, с правило „затворено“ и собствен кратък ден на 24 декември от 10:00 до 14:00, следващите шестдесет дни от 20 декември 2026 г. дават пет специални записа: 24 декември от 10:00 до 14:00, а 25, 26 и 28 декември и 1 януари затворено, записано като отваряне и затваряне в 00:00, както документацията на Google отбелязва ден, затворен през целия ден. Те отиват в specialOpeningHoursSpecification, свойството на schema.org за изключения, които заместват обичайното време.

Две практически неща. Генерирайте специалното време за период напред (шестдесет дни са разумни) и го обновявайте всеки ден, за да не свършват данните. А когато правителството обяви допълнителен почивен ден, след като сте настроили сайта, нищо няма да знае за него: добавете го като своя дата.

#„Отворено сега“, което оцелява в кеша

Редът „Отворено сега, затваря в 13:00“ е най-полезното нещо на страницата за контакти и най-лесното за объркване. Сметнат в PHP при изграждането на страницата, той се замразява от кеша: страницата, кеширана в 07:55, казва „Затворено“, докато кешът изтече. Сметнат в JavaScript по часовника на посетителя, той ползва часовата зона на посетителя, така че клиент в чужбина вижда магазина отворен в грешните часове.

Решението е работата да се раздели. PHP записва в страницата работните часове за следващите две седмици като Unix времена, които не зависят от часова зона, а няколко реда JavaScript избират изречението в момента, в който посетителят чете:

<?php

// Opening times from now on, as Unix timestamps, for the page to carry.
function spans(array $week, array $special, DateTimeImmutable $now, int $days = 14): array
{
    $out = [];
    for ($i = -1; $i < $days; $i++) {
        $day = $now->modify("$i day");
        $date = $day->format('Y-m-d');
        $intervals = array_key_exists($date, $special) ? $special[$date] : ($week[strtolower($day->format('D'))] ?? []);
        foreach ($intervals as [$opens, $closes]) {
            $start = new DateTimeImmutable("$date $opens", $now->getTimezone());
            $end = new DateTimeImmutable("$date $closes", $now->getTimezone());
            if ($end <= $start) {
                $end = $end->modify('+1 day');   // past midnight, or 00:00 to 00:00 = all day
            }
            if ($end > $now) {
                $out[] = [$start->getTimestamp(), $end->getTimestamp()];
            }
        }
    }
    return $out;
}
function statusText(spans, now = Date.now(), timeZone = "Europe/Sofia") {
  const time = (s) => new Intl.DateTimeFormat("en-GB", { timeZone, hour: "2-digit", minute: "2-digit" }).format(s * 1000);
  const day = (ms) => new Intl.DateTimeFormat("en-CA", { timeZone }).format(ms); // YYYY-MM-DD
  for (const [start, end] of spans) {
    if (now >= start * 1000 && now < end * 1000) return `Open now, closes at ${time(end)}`;
    if (start * 1000 > now) {
      const days = Math.round((Date.parse(day(start * 1000)) - Date.parse(day(now))) / 86400000);
      const when = days === 0 ? "today" : days === 1 ? "tomorrow"
        : new Intl.DateTimeFormat("en-GB", { timeZone, weekday: "long" }).format(start * 1000);
      return `Closed, opens ${when} at ${time(start)}`;
    }
  }
  return "Closed";
}

Цикълът в PHP започва от вчера, защото нощ, започнала вчера, може още да тече. В WordPress му подайте current_datetime(), което носи часовата зона на сайта от Настройки, Общи; задайте я на града на фирмата, а не на UTC. JavaScript показва всеки час в часовата зона на фирмата, където и да е посетителят. За сайт на български сменете текстовете и езика на форматите, например bg-BG.

Пуснахме двете върху декември на пекарната. В 12:30 на 23 декември казва „Open now, closes at 13:00“ (отворено, затваря в 13:00); в 13:30 „отваря днес в 14:00“; в 20:00 „отваря утре в 10:00“ заради краткия Бъдни вечер; а в 15:00 на 24 декември „отваря във вторник в 08:00“, като прескача двата дни на Коледа, неделята и почивния понеделник. Бар, отворен от 18:00 в петък, проверен в 01:00 в събота, е „отворено, затваря в 02:00“. С компютър на посетителя, настроен на часа в Ню Йорк, всеки ред излезе същият.

#Как да го сложите в WordPress

Отпечатайте JSON-LD веднъж за всеки обект в wp_head, в елемент script от тип application/ld+json, кодиран с wp_json_encode(). Ползвайте най-точния тип LocalBusiness, който подхожда (Bakery, Dentist, AutoRepair), с името, пълния адрес, координатите и телефона точно както са на страницата. Сложете работните часове в атрибут с данни до реда със състоянието и пуснете скрипта при зареждане на страницата; редът, написан от PHP, остава като версия за посетителите без JavaScript.

След това проверете: Rich Results Test на Google и Schema Markup Validator четат JSON-LD и показват грешките. Две неща, които данните не правят. Не променят профила ви в Google Business Profile, който показват Картите, затова въведете същото празнично време и там. И дали търсачката ще покаже работното ви време изобщо, решава търсачката.

#Плъгин, който го прави

Вградихме това в Rabotno, плъгин за работно време в WordPress, който познава българския календар: седмично време с до два интервала на ден, нощи след полунощ и денонощни дни, официалните празници с православния Великден, заместващите и обявените почивни дни, правило за празниците за всеки обект и специални дати с предимство, ред „отворено сега“, който остава верен зад кеша, и блок schema.org LocalBusiness със специалното работно време за следващите 60 дни. Работи за един или много обекти, на български и английски. Тестван е с 21 автоматични теста, празниците от 2024 до 2031 г. срещу отделно тестван календар, пълно изпълнение на WordPress 7.1.3 и тест в браузър на скрипта за „отворено сега“ и е обявен като „Очаквайте скоро“.

#Често задавани въпроси

Как да отбележа затворен ден в работното време по schema.org?

За обичаен почивен ден просто го пропуснете в седмичното време. За празник, в който сте затворени, добавете специален запис за датата с отваряне и затваряне в 00:00.

Как да запиша време след полунощ?

Като един запис в деня, в който започва смяната, например петък, отваря 18:00, затваря 02:00. Не го разделяйте на два дни.

Защо „отворено сега“ показва грешно състояние?

Обикновено защото е сметнато, когато страницата е кеширана, или с часовата зона на посетителя. Пращайте работните часове като времена и избирайте текста в браузъра, в часовата зона на фирмата.

Длъжен ли съм да затварям на официалните празници?

Това зависи от фирмата и уговорките с персонала, а не от сайта; попитайте счетоводителя си или консултант по трудово право. Сайтът трябва просто да показва каквото решите.

#Източници

Още по темата

Още по темата

Коментари

Коментари

Напишете първия коментар.

Оставете коментар

Следваща стъпка

pnscripts.com/bg/contact

Пишете на екипа

Попитайте за статия или ни разкажете за проект, който искате да изградим. Отговаряме до един работен ден.

Пишете ни

Семейството PN Scripts

Другите сайтове на PN Scripts

Хостингът, игрите и блогът имат свои сайтове, поддържани от същата компания.

  • pnscripts.com

    PN Scripts

    Софтуерно инженерство

    Уеб и мобилни приложения, API и игри по поръчка, плюс нашите продукти и плъгини с отворен код.

  • games.pnscripts.com

    Игри

    Игри и игрови сървъри

    Домът на игрите на PN Scripts: безплатни браузърни игри, които се играят веднага, а игровите сървъри предстоят.

  • hosting.pnscripts.com

    Хостинг

    Хостинг и инфраструктура

    Споделен хостинг, KVM VPS, самостоятелни сървъри и домейни от същата компания, която изработва проекта ви.

  • blog.pnscripts.com

    Блог

    Статии и бележки

    Практични статии за разработка на софтуер, хостинг, отворен код и игри, писани от хората, които ги създават.

    Вие сте тук