Laravel - timezone saved in DB off by 5 hours - mysql

So, in Laravel's app.php I have the following timezone set:
'timezone' => 'America/Denver',
In MySQL settings I've got the same timezone. When I run select now() I get the current Denver time.
However, when I create a record in any table in the database, the created_at field (with default value set to CURRENT_TIMESTAMP) somehow ends up 5 hours ahead of Denver.
I believe it's somehow defaulting to UTC time, but I am not sure. All online resources I've found related to this issue claim that setting the timezone in Laravel should do the trick.
What else can I do to make sure I get the correct timezone saved in CURRENT_TIMESTAMP?
I don't think server-wide PHP settings should have precedent over what's set in MySQL or in Laravel in this matter, but I have still gone ahead and tried editing the timezone in php.ini to America/Denver and no luck. It was previously commented out (not set to UTC).

Use
SET SESSION time_zone = 'America/Denver';
In a raw query (DB::select(DB::raw("SET SESSION time_zone = 'America/Denver'")) before inserting and updating.
Test case
CREATE TABLE test (
id INT
, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO test (id) VALUES(1);
SET SESSION time_zone = 'America/Denver';
INSERT INTO test (id) VALUES(2);
Possible results
| id | created_at |
| --- | ------------------- |
| 1 | 2019-03-04 13:57:31 |
| 2 | 2019-03-04 06:57:31 |
see demo

Eloquent creates a new Carbon object when it sets the timestamp for created_at, it doesn’t use MySQL’s default. This should use date_default_timezone_set, which Laravel is setting.
Rather obvious answer is: have you tried clearing your config cache?
php artisan config:clear
As an aside it is generally advisable to always use UTC across everything, and only convert it to a local timezone at the last possible moment.
From Carbon:
// PS: we recommend you to work with UTC as default timezone and only use
// other timezones (such as the user timezone) on display

Related

Laravel allowing null dates on Model::create() but not Model->update()

I have setup my Laravel migration to allow nullable dates for my StartTime and EndTime entries as such:
$table->dateTime( 'StartTime' )->nullable();
$table->dateTime( 'EndTime' )->nullable();
When I create a new entry through eloquent, it allows me to insert null values into my database successfully:
try {
// Create the new Campaign record
$campaign = Campaign::create( $request->all() );
}
+----+-------+--------+-----------+---------+---------------------+
| Id | Name | Active | StartTime | EndTime | created_at |
+----+-------+--------+-----------+---------+---------------------+
| 1 | Test2 | 0 | NULL | NULL | 2020-07-02 22:01:22 |
+----+-------+--------+-----------+---------+---------------------+
1 row in set (0.00 sec)
However, when I later try and update my record using eloquent and still passing a null value for StartTime, it throws an error:
try {
// Get a reference to the campaign
$campaign = Campaign::find( $id );
// Update the campaign
$campaign->update( $request->all() );
}
(22007) SQLSTATE[22007]: Invalid datetime format: 1292 Incorrect datetime value: 'null' for column 'StartTime' at row 1
In the case of the create method, I am not passing in a StartTime value at all, but in the case of the update method, I am simply passing back the null value that Laravel returns as part of the model. So in other words, I haven't altered the value of StartTime at all, I've simply just passed $campaign back to Laravel for the update.
So it seems that Laravel is assigning the nullable() upon insert of a new entry into the database, but will not allow me to pass a null value back for the update.
Am I missing something here? I can't seem to find a solution to this anywhere.
UPDATE
Okay, so further investigation seems like my problem is stemming from the AngularJS $http POST request. For troubleshooting purposes, I added code to my Laravel controller to alter the StartTime to null:
if( $request->StartTime === 'null' ) {
$request['StartTime'] = null;
}
And that worked. So it looks like Angular is passing the null value back in the request as 'null'
Laravel 5.3 was updated to use MySQL "strict" mode by default, which includes the NO_ZERO_DATE mode.
The issue is that your existing data was allowed to have '0000-00-00 00:00:00' as a datetime value. But, now your connection is using a sql mode that does not allow that value (NO_ZERO_DATE).
However, the quick option is to just disable "strict" mode on your database connection. Open your config/database.php file, and make sure your database connection shows 'strict' => false.
Or, create migration like this :
$table->datetime('StartTime')->nullable($value = true);
Or,
$table->datetime('StartTime')->default(null);

MySQL 5.5 UNIX_TIMESTAMP function returns different values on prod and local machines

I have an old PHP system, using MySQL 5.5.47 as DB.
The guys who have created the system, have taken a strange decision.
In some cases, they saved a date value without day - for example '2018-01-00'. The field type is DATE.
A lot of queries use where clause like this: UNIX_TIMESTAMP(DATE(<DATE>)) BETWEEN 1514757600 AND 1546207210, where <DATE> is a column which contains records like '2018-01-00', '2018-02-00', etc.
The two timestamps represent dates 2018-01-01 and 2018-12-31.
On production, this type of queries run without issue.
On my local machine, they do not return any results.
What I found is if I run the command: SELECT UNIX_TIMESTAMP( DATE( '2018-01-00' ) ) on production the result is 1514757600, but on my local machine it returns 0.
I'm using a Docker compose to reproduce the production as close as possible. Initially, I have used MySQL 5.6 for local development when I hit this issue, I tried with MySQL 5.5.62, but the result is same.
Does anyone know how I can set up my local MySQL to work as the production one?
Query on production:
mysql> SELECT DATE('2018-01-00'), UNIX_TIMESTAMP(DATE('2018-01-00')), UNIX_TIMESTAMP('2018-01-00');
+--------------------+------------------------------------+------------------------------+
| DATE('2018-01-00') | UNIX_TIMESTAMP(DATE('2018-01-00')) | UNIX_TIMESTAMP('2018-01-00') |
+--------------------+------------------------------------+------------------------------+
| 2018-01-00 | 1514757600 | 0 |
+--------------------+------------------------------------+------------------------------+
Query on local:
mysql> SELECT DATE('2018-01-00'), UNIX_TIMESTAMP(DATE('2018-01-00')), UNIX_TIMESTAMP('2018-01-00');
+--------------------+------------------------------------+------------------------------+
| DATE('2018-01-00') | UNIX_TIMESTAMP(DATE('2018-01-00')) | UNIX_TIMESTAMP('2018-01-00') |
+--------------------+------------------------------------+------------------------------+
| 2018-01-00 | 0 | 0 |
+--------------------+------------------------------------+------------------------------+
It turns out to be a bug in Mysql prior 5.5.48. In the release notes of 5.5.48, there is a statement about fixing a bug related to the unix_timestamp function.
https://dev.mysql.com/doc/relnotes/mysql/5.5/en/news-5-5-48.html
When an invalid date was supplied to the UNIX_TIMESTAMP() function using the STR_TO_DATE() function, no check was performed before converting it to a timestamp value. (Bug #21564557)

Delete old rows MySQL Trigger - beginner

I am looking for a way to delete old rows from my MySQL table and I have not really understood the questions and answears I have found so I was hoping that someone could clarify. I have added a Timestamp column to my table where I store the date the row was created. If the row is older than four months, I wan't to delete it. I have looked into triggers but if I have understood correctly, an insert trigger only alows you to work with the row that you insert? Maybe I'm wrong but I'm very new to triggers and don't even understand when/how to run them? Is it enough to just run them once, will the action automatically be repeated every time someone insert something (if it is an insert trigger)?
I run my insertQuery very often, everytime someone requests an URL to their painting. I am using php and my table is structured like this:
DatabaseID || theKey || Timestamp
1 abcd 2016-01-02
2 a1bc 2016-01-03
3 a1sb 2016-01-03
4 a12b 2016-01-05
EDIT: Forgot to mention, I would like the deletion of old rows to be automatic and run at least once a week.
It would be ideal to have a mysql event scheduler http://dev.mysql.com/doc/refman/5.7/en/create-event.html
As a first step you need to activate the scheduler as
SET GLOBAL event_scheduler = ON;
Note that if the server is restarted then it will need to reset the
above so better to set event_scheduler=on somewhere under the [mysqld]
section in the default mysql config file, usually /etc/my.cnf, make
sure to restart the mysql server
Once the scheduler is set run following to see if its working and you should see something as
show variables like '%event_scheduler%';
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| event_scheduler | ON |
+-----------------+-------+
Finally on the terminal write the scheduler as
delimiter //
create event if not exists clean_old_records
on schedule every 1 week
do
begin
delete from your_table_name
where
Timestamp < date_sub(curdate(),interval 1 month) ;
end; //
delimiter ;
In the example above it will delete records older than one month and you can have your own value.

Can MySQL convert a stored UTC time to local timezone?

Can MySQL convert a stored UTC time to local time-zoned time directly in a normal select statement?
Let's say you have some data with a timestamp (UTC).
CREATE TABLE `SomeDateTable` (
`id` int(11) NOT NULL auto_increment,
`value` float NOT NULL default '0',
`date` datetime NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (`id`)
)
Then when I
"select value, date from SomeDateTable";
I of course get all the dates as in their stored UTC form.
But let's say that I would like to have them in another timezone (with DST),
can I then add some magic to the select query so that I get all the dates back in the selected timezone?
"select value, TIMEZONE(date, "Europe/Berlin") from SomeDateTable";
Or must I do this in some other layer on top, like in some php code?
(it seems to be how most people have solved this problem).
If your MySQL installation allows you to use CONVERT_TZ it is a very clean solution,
this example shows how to use it.
SELECT CONVERT_TZ( '2010-01-01 12:00', 'UTC', 'Europe/Stockholm' )
However I don't know if this is a good way since some MySQL installation is missing this function, use with care.
Yup, there's the convert_tz function.
For those unable to configure the mysql environment (e.g. due to lack of SUPER access) to use human-friendly timezone names like "America/Denver" or "GMT" you can also use the function with numeric offsets like this:
CONVERT_TZ(date,'+00:00','-07:00')
One can easily use
CONVERT_TZ(your_timestamp_column_name, 'UTC', 'your_desired_timezone_name')
For example:
CONVERT_TZ(timeperiod, 'UTC', 'Asia/Karachi')
Plus this can also be used in WHERE statement and to compare timestamp i would use the following in Where clause:
WHERE CONVERT_TZ(timeperiod, 'UTC', '{$this->timezone}') NOT BETWEEN {$timeperiods['today_start']} AND {$timeperiods['today_end']}
select convert_tz(now(),##session.time_zone,'+03:00')
For get the time only use:
time(convert_tz(now(),##session.time_zone,'+03:00'))
1. Correctly setup your server:
On server, su to root and do this:
# mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql mysql
(Note that the command at the end is of course mysql , and, you're sending it to a table which happens to have the same name: mysql.)
Next, you can now # ls /usr/share/zoneinfo .
Use that command to see all the time zone info on ubuntu or almost any unixish server.
(BTW that's the convenient way to find the exact official name of some time zone.)
2. It's then trivial in mysql:
For example
mysql> select ts, CONVERT_TZ(ts, 'UTC', 'Pacific/Tahiti') from example_table ;
+---------------------+-----------------------------------------+
| ts | CONVERT_TZ(ts, 'UTC', 'Pacific/Tahiti') |
+---------------------+-----------------------------------------+
| 2020-10-20 16:59:57 | 2020-10-20 06:59:57 |
| 2020-10-20 17:02:59 | 2020-10-20 07:02:59 |
| 2020-10-20 17:30:08 | 2020-10-20 07:30:08 |
| 2020-10-20 18:36:29 | 2020-10-20 08:36:29 |
| 2020-10-20 18:37:20 | 2020-10-20 08:37:20 |
| 2020-10-20 18:37:20 | 2020-10-20 08:37:20 |
| 2020-10-20 19:00:18 | 2020-10-20 09:00:18 |
+---------------------+-----------------------------------------+
I propose to use
SET time_zone = 'proper timezone';
being done once right after connect to database. and after this all timestamps will be converted automatically when selecting them.

How can I tell when a MySQL table was last updated?

In the footer of my page, I would like to add something like "last updated the xx/xx/200x" with this date being the last time a certain mySQL table has been updated.
What is the best way to do that? Is there a function to retrieve the last updated date? Should I access to the database every time I need this value?
In later versions of MySQL you can use the information_schema database to tell you when another table was updated:
SELECT UPDATE_TIME
FROM information_schema.tables
WHERE TABLE_SCHEMA = 'dbname'
AND TABLE_NAME = 'tabname'
This does of course mean opening a connection to the database.
An alternative option would be to "touch" a particular file whenever the MySQL table is updated:
On database updates:
Open your timestamp file in O_RDRW mode
close it again
or alternatively
use touch(), the PHP equivalent of the utimes() function, to change the file timestamp.
On page display:
use stat() to read back the file modification time.
I'm surprised no one has suggested tracking last update time per row:
mysql> CREATE TABLE foo (
id INT PRIMARY KEY
x INT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP,
KEY (updated_at)
);
mysql> INSERT INTO foo VALUES (1, NOW() - INTERVAL 3 DAY), (2, NOW());
mysql> SELECT * FROM foo;
+----+------+---------------------+
| id | x | updated_at |
+----+------+---------------------+
| 1 | NULL | 2013-08-18 03:26:28 |
| 2 | NULL | 2013-08-21 03:26:28 |
+----+------+---------------------+
mysql> UPDATE foo SET x = 1234 WHERE id = 1;
This updates the timestamp even though we didn't mention it in the UPDATE.
mysql> SELECT * FROM foo;
+----+------+---------------------+
| id | x | updated_at |
+----+------+---------------------+
| 1 | 1235 | 2013-08-21 03:30:20 | <-- this row has been updated
| 2 | NULL | 2013-08-21 03:26:28 |
+----+------+---------------------+
Now you can query for the MAX():
mysql> SELECT MAX(updated_at) FROM foo;
+---------------------+
| MAX(updated_at) |
+---------------------+
| 2013-08-21 03:30:20 |
+---------------------+
Admittedly, this requires more storage (4 bytes per row for TIMESTAMP).
But this works for InnoDB tables before 5.7.15 version of MySQL, which INFORMATION_SCHEMA.TABLES.UPDATE_TIME doesn't.
I don't have information_schema database, using mysql version 4.1.16, so in this case you can query this:
SHOW TABLE STATUS FROM your_database LIKE 'your_table';
It will return these columns:
| Name | Engine | Version | Row_format | Rows | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time | Update_time | Check_time | Collation | Checksum | Create_options | Comment |
As you can see there is a column called: "Update_time" that shows you the last update time for your_table.
The simplest thing would be to check the timestamp of the table files on the disk. For example, You can check under your data directory
cd /var/lib/mysql/<mydatabase>
ls -lhtr *.ibd
This should give you the list of all tables with the table when it was last modified the oldest time, first.
For a list of recent table changes use this:
SELECT UPDATE_TIME, TABLE_SCHEMA, TABLE_NAME
FROM information_schema.tables
ORDER BY UPDATE_TIME DESC, TABLE_SCHEMA, TABLE_NAME
I would create a trigger that catches all updates/inserts/deletes and write timestamp in custom table, something like
tablename | timestamp
Just because I don't like the idea to read internal system tables of db server directly
Although there is an accepted answer I don't feel that it is the right one. It is the simplest way to achieve what is needed, but even if already enabled in InnoDB (actually docs tell you that you still should get NULL ...), if you read MySQL docs, even in current version (8.0) using UPDATE_TIME is not the right option, because:
Timestamps are not persisted when the server is restarted or when the
table is evicted from the InnoDB data dictionary cache.
If I understand correctly (can't verify it on a server right now), timestamp gets reset after server restart.
As for real (and, well, costly) solutions, you have Bill Karwin's solution with CURRENT_TIMESTAMP and I'd like to propose a different one, that is based on triggers (I'm using that one).
You start by creating a separate table (or maybe you have some other table that can be used for this purpose) which will work like a storage for global variables (here timestamps). You need to store two fields - table name (or whatever value you'd like to keep here as table id) and timestamp. After you have it, you should initialize it with this table id + starting date (NOW() is a good choice :) ).
Now, you move to tables you want to observe and add triggers AFTER INSERT/UPDATE/DELETE with this or similar procedure:
CREATE PROCEDURE `timestamp_update` ()
BEGIN
UPDATE `SCHEMA_NAME`.`TIMESTAMPS_TABLE_NAME`
SET `timestamp_column`=DATE_FORMAT(NOW(), '%Y-%m-%d %T')
WHERE `table_name_column`='TABLE_NAME';
END
OS level analysis:
Find where the DB is stored on disk:
grep datadir /etc/my.cnf
datadir=/var/lib/mysql
Check for most recent modifications
cd /var/lib/mysql/{db_name}
ls -lrt
Should work on all database types.
a) It will show you all tables and there last update dates
SHOW TABLE STATUS FROM db_name;
then, you can further ask for specific table:
SHOW TABLE STATUS FROM db_name like 'table_name';
b) As in above examples you cannot use sorting on 'Update_time' but using SELECT you can:
SELECT * FROM information_schema.tables WHERE TABLE_SCHEMA='db_name' ORDER BY UPDATE_TIME DESC;
to further ask about particular table:
SELECT * FROM information_schema.tables WHERE TABLE_SCHEMA='db_name' AND table_name='table_name' ORDER BY UPDATE_TIME DESC';
I got this to work locally, but not on my shared host for my public website (rights issue I think).
SELECT last_update FROM mysql.innodb_table_stats WHERE table_name = 'yourTblName';
'2020-10-09 08:25:10'
MySQL 5.7.20-log on Win 8.1
Just grab the file date modified from file system. In my language that is:
tbl_updated = file.update_time(
"C:\ProgramData\MySQL\MySQL Server 5.5\data\mydb\person.frm")
Output:
1/25/2013 06:04:10 AM
If you are running Linux you can use inotify to look at the table or the database directory. inotify is available from PHP, node.js, perl and I suspect most other languages. Of course you must have installed inotify or had your ISP install it. A lot of ISP will not.
Not sure if this would be of any interest. Using mysqlproxy in between mysql and clients, and making use of a lua script to update a key value in memcached according to interesting table changes UPDATE,DELETE,INSERT was the solution which I did quite recently. If the wrapper supported hooks or triggers in php, this could have been eaiser. None of the wrappers as of now does this.
i made a column by name : update-at in phpMyAdmin and got the current time from Date() method in my code (nodejs) . with every change in table this column hold the time of changes.
Same as others, but with some conditions i've used, to save time:
SELECT
UPDATE_TIME,
TABLE_SCHEMA,
TABLE_NAME
FROM
information_schema.tables
WHERE
1 = 1
AND UPDATE_TIME > '2021-11-09 00:00:00'
AND TABLE_SCHEMA = 'db_name_here'
AND TABLE_NAME not in ('table_name_here',)
ORDER BY
UPDATE_TIME DESC,
TABLE_SCHEMA,
TABLE_NAME;
This is what I did, I hope it helps.
<?php
mysql_connect("localhost", "USER", "PASSWORD") or die(mysql_error());
mysql_select_db("information_schema") or die(mysql_error());
$query1 = "SELECT `UPDATE_TIME` FROM `TABLES` WHERE
`TABLE_SCHEMA` LIKE 'DataBaseName' AND `TABLE_NAME` LIKE 'TableName'";
$result1 = mysql_query($query1) or die(mysql_error());
while($row = mysql_fetch_array($result1)) {
echo "<strong>1r tr.: </strong>".$row['UPDATE_TIME'];
}
?>
Cache the query in a global variable when it is not available.
Create a webpage to force the cache to be reloaded when you update it.
Add a call to the reloading page into your deployment scripts.