Localizaci贸n temporal en Symfony 4 + Twig

La necesidad de una localizaci贸n temporal del producto surge cuando el producto crece a una escala tal que requiere trabajo en diferentes zonas horarias (evidencia). Me gustar铆a describir una variante de una idea simple para resolver este caso.

El trasfondo es este: desarrollamos un sistema de CRM / ERP de nicho, y luego nos dijeron que ma帽ana trabajar铆an con este sistema en una franquicia de Vladivostok a Kaliningrado. Desafortunadamente, tal escenario no se pens贸 originalmente, y comenzamos a aprender c贸mo hacerlo con un costo m铆nimo y la m谩xima comodidad.

En total, se ampliaron tres tareas: c贸mo sacamos los datos y c贸mo ingresamos, y entre ellas la tarea c贸mo almacenamos todo. Como el tiempo, como saben, es relativamente literal y figurado, se decidi贸 almacenar el tiempo como antes en Mosc煤 UTC + 3, pero procesarlo en la entrada y salida (y tener en cuenta que el punto de referencia es UTC + 3). Por supuesto, entendimos que hay otras soluciones en esta y otras direcciones. Puede convertir todas las entradas existentes a UTC + -0, as铆 como utilizar tipos especializados en el DBMS que almacenan la zona horaria, puede escribir este tipo personalizado usted mismo, si de repente la base de datos no es totalmente compatible con tales caracter铆sticas. Pero guiados por el principio de simplicidad, seguimos el camino propuesto, m谩s a煤n, a primera vista, no perdi贸 mucho con el resto,y la l贸gica para determinar la zona horaria deseada era bastante simple.

Despu茅s de que Mosc煤 se convirti贸 en un punto de referencia, agregamos un par谩metro de zona horaria personalizada a cada usuario, as铆 como una serie de entidades relacionadas (organizaci贸n, ciudad, pedidos, ofertas, etc.). Entonces fue posible establecer inequ铆vocamente en qu茅 zona horaria el usuario o entidad con la que trabaja. La l贸gica all铆 es est谩ndar y precisamente a menudo espec铆fica de los proyectos. Incluimos esta l贸gica en el servicio y obtuvimos una zona horaria en la que necesita

$localizationService->getTimezone();

La decisi贸n de localizar las fechas en las plantillas fue la siguiente: al inicializar las extensiones de Twig, la zona horaria se cambi贸 a la deseada:

function __construct(Environment $twig, LocalizationService $localizationService) {
    $twig->getExtension('Twig_Extension_Core')->setTimezone($localizationService->getTimezone());
}

Nuestra situaci贸n se complic贸 a煤n m谩s por el hecho de que despu茅s de que se muestra cualquier fecha y hora, es necesario hacer un sub铆ndice "01.01.2020 12:30 (Mosc煤)", de modo que, por ejemplo, en un pedido / tarea / transacci贸n condicional que est茅 vinculada a la zona horaria, informaci贸n sobre la hora cintur贸n. Por razones pr谩cticas, esto es necesario para que un 煤nico centro de llamadas pueda trabajar c贸modamente con diferentes zonas horarias dentro del marco de una tarea / aplicaci贸n / transacci贸n.

Toda la l贸gica para determinar la prioridad de las zonas horarias se ha conectado a la mencionada zona getTimezone.

Adem谩s, nos enfrentamos con el hecho de que si realiza su filtro o funci贸n de ramita, deber谩 cambiar un mont贸n de plantillas, pero le gustar铆a evitar esto. Por lo tanto, despu茅s de preguntar un poco, decidimos redefinir la fecha est谩ndar del filtro de ramita

...
new TwigFilter('date', [$this, 'date'], ['needs_environment' => true]),
...

function date(Twig_Environment $env, $date, $format = null, $timezone = null)
{
    $appendix = '';
    if (format && strpos($format, 'H:i') !== false)
        $appendix = ' ('.DateTimeFunctions::getRussianAbbrev($this->localizationService->getTimezone()).')';   
...
    //    date   $result
...
   return $result.$appendix;
}

Adem谩s, desde que tomamos el filtro est谩ndar, la versi贸n anterior se redefini贸:

...
new TwigFilter('native_date', [$this, 'nativeDate'], [ 'needs_environment' => true]),
...
public function nativeDate(Twig_Environment $env, $date, $format = null, $timezone = null)
{
    //    date 
}

El c贸digo de filtro de fecha est谩ndar se puede encontrar en / twig / twig / lib / Twig / Extension / Core :: twig_date_format_filter. Aunque, de hecho, en la mayor铆a de los casos, una opci贸n simple y no muy diferente servir谩:

$date->setTimeZone($timezone)
$result = $date->format($format);

Por supuesto, tambi茅n puede bifurcar o redefinir la parte m谩s sustancial de Twig, pero si la funcionalidad del filtro est谩ndar le conviene, simplemente puede sacarlo por separado y no perder nada.

Queda por resolver el problema de ingresar la fecha y hora. Una soluci贸n:

private function getOffsetHours()
{
    if (!$this->isInit)
        $this->init();

    $local = new \DateTime('now', new \DateTimeZone($this->getTimezone()));
    $user = new \DateTime('now');

    $localOffset = $local->getOffset() / 3600;
    $globalOffset = $user->getOffset() / 3600;

    $diff = $globalOffset - $localOffset;
    return $diff;
}

public function toGlobalTime(\DateTimeInterface $dateTime): \DateTimeInterface 
{
    if (!$this->isInit)
        $this->init();

    $offsetHours = $this->getOffsetHours();

    if ($offsetHours > 0) {
        return $dateTime->modify('+ '.$offsetHours.' hours');
    } else  if($offsetHours < 0){
        return $dateTime->modify($offsetHours.' hours');
    }

    return $dateTime;
}

Luego llame antes de guardar la fecha y hora, por ejemplo, en oyentes. Esta opci贸n se nos ocurri贸, ya que b谩sicamente la hora y la fecha en el proyecto son fijas para ciertos eventos y no se ingresan manualmente. Para otro caso extremo, donde el tiempo se introduce constantemente a trav茅s de formularios, la soluci贸n puede no ser 贸ptima.

Como un bono. El proyecto utiliza Omines datatables-bundle para generar tablas. All铆 la soluci贸n fue a煤n m谩s f谩cil. En lugar de DateTimeColumn para la localizaci贸n, se utiliz贸 lo siguiente:

class CustomDateTimeColumn extends DateTimeColumn
{
    
    private $localizationService;
    private $timeZone;
    
    public function __construct(LocalizationService $localizationService)
    {
        $this->localizationService = $localizationService;
        $this->timeZone = $localizationService->getTimezoneObject();
    }
    
   
    public function normalize($value)
    {
        $value->setTimeZone($this->timeZone);
        return parent::normalize($value);
    }
}

Gracias por tu tiempo. Si alguien ayuda a mejorar las cosas b谩sicas de la soluci贸n, estar茅 muy agradecido. Estamos hablando de los b谩sicos, ya que est谩 claro que el c贸digo es vac铆o y en realidad tiene una DI mucho m谩s grande y todo tipo de golosinas para uso interno en el proyecto.

En resumen. Se presenta la idea de una soluci贸n simple para la r谩pida localizaci贸n temporal del proyecto. No depende de las versiones, o si lo hace, es d茅bil. Esta soluci贸n migr贸 con 茅xito de Symfony 4.2 a 5.

All Articles