Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

joshteam's avatar

How to handle time zones in complicated queries?

I have the following code... that will essentially tell me how many many behaviors happened for a given day, but will also return 0s for days where no behaviors are stored. My question is two-fold:

(1) This was some of my earlier code and so I am thinking about cleaning this up, refactoring ~ are there any design patterns that I should use here? Or is this fine?

(2) All dates are stored in UTC, so I need to change the query to account for time zone. Otherwise the charts will show behaviors on the UTC dates, and not the local dates. So I was thinking I could just modify the following part: b.created_at < d.generated_date + INTERVAL 1 DAY AND to account for the diff between their local time and utc? Is there a cleaner better way to think about this?

Thanks in advance.


    protected function getBehaviors(Child $child, int $daysBack, $operator = '>=', $typeID = null)
    {
        $typeWhere = ($typeID) ? 'b.behavior_type_id = ' . $typeID  . ' AND': '';

        return DB::select($this->getRecursiveDatesQuery() . "
            LEFT JOIN behaviors b
                 ON b.child_id = :child_id AND
                    b.created_at >= d.generated_date AND
                    " . $typeWhere . "
                    b.created_at < d.generated_date + INTERVAL 1 DAY AND
                    b.value " . $operator . " 0
            GROUP BY
                d.generated_date
            ORDER BY
                d.generated_date ASC
        ", ['child_id' => $child->id, 'days' => ($daysBack - 1)]);
    }

    private function getRecursiveDatesQuery()
    {
        return "WITH RECURSIVE dates as (
                    SELECT
                      CURDATE() as generated_date,
                      1 as days
                    UNION ALL
                    SELECT
                      generated_date - interval 1 day,
                      days + 1
                    FROM
                      dates
                    WHERE
                      days <= :days
                )
                SELECT
                    DATE_FORMAT(d.generated_date, '%m/%d') as created_day,
                    COALESCE(COUNT(b.id), 0) as total
                FROM
                    dates d";
    }
0 likes
3 replies
joshteam's avatar

@jlrdw So, if I am following. Grab the dataset, then do some sort of recursive function that will figure out the local time zone equivalent? I am needing to pass this data into a graphing library that will show the dates as the "X-Axis" for the user. And MySQL is grouping on this as it's how it's calculating the counts.

jlrdw's avatar

@joshteam you can parameterize dates, here is short example of a query I have:

        // top part not shown
        $sql .= "where (`transactions`.`TransactionDate` Between :bdate and  :edate) ";
        $sql .= "group by `account_types`.`AccountType`,`accounts`.`AccountNumber`,`accounts`.`AccountName` ";
        $sql .= $pagingQuery;

        $sth = DB::connection('mysqlcb')->getPdo()->prepare($sql);
        $params = ['bdate' => $bdate, 'edate' => $edate];
        $sth->execute($params);
        $quy = $sth->fetchAll(\PDO::FETCH_OBJ);
        $report = new LengthAwarePaginator($quy, $numrows, $perpage);
       // rest of code

But take note that dates were first put into variables to be used as parameters. Do conversions before you query.

But just example showing to deal with the dates first, a getter setter (accessor mutator) or whatever.

Please or to participate in this conversation.