Solved thread

This post is marked as solved. If you think the information contained on this thread must be part of the official documentation, please contribute submitting a pull request to its repository.

Show custom date in volt

I have a problem trying to show a date with an external library https://github.com/fightbulc/moment.php in volt.

I'm trying to use functions in volt to do it.

I reviewed some old posts that served as a guide but the detail is that I did not know how to implement them for my case.

This is what I tried:

services.php

$di->setShared('view', function () {
    $config = $this->getConfig();

    $view = new View();
    $view->setDI($this);
    $view->setViewsDir($config->application->viewsDir);

    $view->registerEngines([
        '.volt' => function ($view) {
            $config = $this->getConfig();

            $volt = new VoltEngine($view, $this);

            $volt->setOptions([
                'compiledPath' => $config->application->cacheDir,
                'compiledSeparator' => '_',
                'compiledExtension' => '.compiled',                
            ]);

            $compiler = $volt->getCompiler();

            // example test function that works
            $compiler->addFunction('strlen', function($resolvedArgs, $exprArgs) use ($compiler) {
                $string= $compiler->expression($exprArgs[0]['expr']);
                $secondArgument = $compiler->expression($exprArgs[1]['expr']);
                return 'substr(' . $string . ', 0 ,' . $secondArgument . ')';
            });

            // my non-working function
            $compiler->addFunction(
                'dateForHumans',
                    function ($resolvedArgs, $exprArgs) use ($compiler) {

                    //$string= $compiler->expression($exprArgs[0]['expr']);
                    $string = $resolvedArgs;

                    $m = new \Moment\Moment($string, 'Europe/Berlin'); // default is "now" UTC
                    $m->format(); // e.g. 2012-10-03T10:00:00+0000
                    //return 'new \Moment\Moment('.$string.', "Europe/Berlin")->format()'; // e.g. 2012-10-03T10:00:00+0000
                    return $m;
                }
            );

            return $volt;
        },
        //'.phtml' => PhpEngine::class

    ]);

    return $view;
});

controller.php


namespace App\Controllers;

use Phalcon\Validation;
// use Phalcon\Paginator\Adapter\Model as PaginatorModel;
use Phalcon\Paginator\Adapter\NativeArray as PaginatorArray;

use App\Models\Ciclos as Ciclo; 

class CiclosController extends ControllerBase
{

    public function indexAction()
    {
        // Current page to show
        // In a controller/component this can be:
        $currentPage = $this->request->getQuery('page', 'int'); // GET
        // $this->request->getPost('page', 'int'); // POST
        //$currentPage = (int) $_GET['page'];

        // The data set to paginate
        $ciclos = Ciclo::find([
            'order' => 'ciclo_id desc',
        ]);

        $rows = array();

        foreach ($ciclos as $ciclo) {

            $rows[] = [
                'ciclo_id' => $ciclo->ciclo_id,
                'periodo' => $ciclo->periodo,
                'creacion' => $ciclo->creacion
            ];

        }
        //die(var_dump($rows));

        // Create a Model paginator, show 10 rows by page starting from $currentPage
        $paginator = new PaginatorArray(
            [
                'data'  => $rows,
                'limit' => 10,
                'page'  => $currentPage,
            ]
        );

        // Get the paginated results
        $this->view->ciclos = $paginator->getPaginate();

        //$this->view->ciclos = 1;      
        return $this->view->render('ciclos', 'index');
    }

view.volt

            <table class="table table-striped table-condensed table-hover table-sm">
              <caption>
                <b>Total: </b>{{ ciclos.total_items }} registros
                <a href="{{ url('ciclos/crear') }}" class="btn btn-primary btn-xs">
                  <i class="fa fa-plus-circle"></i> Crear
                </a> 
              </caption>
              <thead>
                <tr>
                  <th scope="col">#</th>
                  <th scope="col">DescripciĆ³n</th>
                  <th scope="col">CreaciĆ³n</th>                  
                  <th scope="col" class="text-center">Opciones</th>
                </tr>
              </thead>
              <tbody>
                {% for row in rows %}              
                <tr>
                  <th scope="row">{{ row['ciclo_id'] }}</th>
                  <td>Ciclo {{ row['periodo'] }}</td>

                  <!-- example test line, it works -->
                <!-- <td>{# { strlen('abcdefghifklmnop', 10) } #}</td> -->

                <!-- this does not work -->
                  <td>{{ dateForHumans(row['creacion']) }}</td>

                  <td class="text-center">
                    <a href="{{ url('ciclos/actualizar/' ~ row['ciclo_id']) }}" class="btn-warning btn-xs">
                        <i class="fa fa-pencil"></i>
                    </a>
                  </td>                  
                </tr>
                {% endfor %}
              </tbody>
            </table>

This is the error: Fatal error: Uncaught Exception: DateTime::__construct(): Failed to parse time string ($row['creacion']) at position 0 ($): Unexpected character in A:\wamp3\www\econtrol\vendor\fightbulc\moment\src\Moment.php on line 188.

And if I replace <td>{{ dateForHumans(row['creacion']) }}</td>, for example, to timestamp <td>{{ dateForHumans(1499366585) }}</td> it shows the same error.

I could use php directly in the view to try to solve this but I think it makes no sense to use volt if I'm going to end up writing normal php.

Any hero without a cape to enlighten me?

Thanks



97.2k
Accepted
answer

I think the problem is how you're passing the parameter on to Moment. Try echoing $resolvedArgs, it might give you some insight as to what's going wrong. Also, I think you want to return $m->format(), which is a string, not $m, which is an object.

If that doesn't work, I've found the simplest solution is to not use an anonymous function, but an actual function:

services.php

$di->setShared('view', function () {
  ...
  $compiler->addFunction('dateForHumans','viewDateForHumans');
}

function viewDateForHumans($timestamp){ // This can also be a static class function if you want
  $m = new \Moment\Moment($string, 'Europe/Berlin'); // default is "now" UTC
  return $m->format(); // e.g. 2012-10-03T10:00:00+0000
}

With all that said - if all you're doing is formatting a UNIX timestamp, you don't need to use Moment.php for that. This function would be adequate:

$Compiler->addFunction('dbDate',function($resolvedArgs,$exprArgs) use($Compiler){
    $format = $Compiler->expression($exprArgs[0]['expr']);
    $date   = $Compiler->expression($exprArgs[1]['expr']);

    return 'date('.$format.','.$date.')';
});
edited 16d ago

Yes, I wanted return formatted date, but your solution helped me a lot, thanks!