13

I'm having some trouble finding a date format that is common to Node.js, knex, and MySQL (via Bookshelf).

I set up a table with the Knex schema builder:

knex.schema.createTableIfNotExists("examples", function (table) {
    ...
    table.dateTime("some_datetime");
})

This creates a column with a DATETIME type in MySQL.

I have a Bookshelf model that represents this (leaving out all the boilerplate stuff here), and I attempt to use the built-in Date.now() as a value:

exampleModel.save({
    some_datetime: Date.now()
})

With debugging turned on in Knex I see the query is actually attempting to insert an epoch timestamp with milliseconds ("..." mine for brevity):

{ ...
  bindings: [ 1485644012453, ... ],
  sql: 'update `examples` set `some_datetime` = ? where `id` = ?' }

But this is incorrect, as MySQL expects you to use FROM_UNIXTIME in this case, and so the resulting date in the database is, of course, the good ol' 0000-00-00 00:00:00.

What should I be doing here to make this all consistent?

  • Is there a different type I should use in the schema builder when creating the table?
  • Or, is there something different I should use to obtain the date besides Date.now()?
  • Or, something else?

I'm having trouble finding the common ground here. My intuition says use dateTime in Knex, Date.now() in Node, DATETIME in MySQL, but this isn't correct.

Just to be clear: This question isn't necessarily focused on what's philosophically correct -- at the moment I'm actually not able to figure out how to store date/times in the database at all. I'm looking for a working combination, semantic correctness would only be a bonus.

Community
  • 1
  • 1
Jason C
  • 38,729
  • 14
  • 126
  • 182
  • 2
    *"Is there a different type I should use in the schema builder when creating the table?"* - You could make it (alter) that the column type is a [large enough int](http://stackoverflow.com/a/4289801/1415724). As for the `Date.now()` stuff ;that I couldn't say since I don't know anything about the `X.js` methods. The other methods/scripts you're using need to be passed as `YYYY-MM-DD 00:00:00` for `DATETIME` – Funk Forty Niner Jan 28 '17 at 23:34
  • 1
    @Fred-ii- Hm, so you suggest ditching the Knex and MySQL date/time types entirely, just working with epoch times stored as integers internally, and converting to/from other formats as needed by e.g. UI stuff? That seems philosophically off, but it is also simple and effective and a reasonable idea. I'll probably do that. Thanks. – Jason C Jan 28 '17 at 23:46
  • 1
    That's up to you. Different storage methods (all) have their pros & cons. Personally, I like working with MySQL's built-in date(time) functions. You'd have to end up using `strtotime()` or MySQL's `DATE_FORMAT()` to later query. – Funk Forty Niner Jan 28 '17 at 23:58

2 Answers2

23

Javascript function Date.now() returns epoch in milliseconds. Mysql driver, which knexuses to send queries expects that you pass ISO8601 strings or Date() objects to DATETIME columns.

Date objects are converted to 'YYYY-mm-dd HH:ii:ss' strings

https://github.com/mysqljs/mysql

So instead of Date.now() use new Date() or new Date().toISOString()

EDIT:

Just checking that mysql really accepts .toISOString() output because I couldn't find mention about it from docs https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-overview.html

MariaDB [(none)]> select CAST('2017-01-30T16:49:19.278Z' AS DATETIME);
+----------------------------------------------+
| CAST('2017-01-30T16:49:19.278Z' AS DATETIME) |
+----------------------------------------------+
| 2017-01-30 16:49:19                          |
+----------------------------------------------+
1 row in set, 1 warning (0.00 sec)

Looks like it really does. Yay! All good.

EDIT2: Actually plainly using .toISOString() causes warning and error in some case so to insert UTC DATETIME columns .toISOString().replace('Z','').replace('T', ' ') should do... Or any other preferred way to make that UTC datetime object to be converted to format yyyy-MM-dd HH:mm:ss.

Also if you are using TIMESTAMP column type and you want to insert UTC times to the database it is important to set also database session to be in UTC. Otherwise the javascript timestamps are interpret as local database times and converted from it to UTC before storing.

CREATE TABLE test (descr TEXT, dt DATETIME, ts TIMESTAMP);

SET time_zone = '+8:00';
INSERT INTO test (descr, dt, ts) VALUES 
  ('session tz +08:00 insert local times', '2020-01-01T00:00:00', '2020-01-01T00:00:00');

SET time_zone = '+0:00';
INSERT INTO test (descr, dt, ts) VALUES 
  ('session tz +00:00 insert local times', '2020-01-01 00:00:00', '2020-01-01 00:00:00');


SET time_zone = '+02:00';
select 'server local time:', now();
select * from test;

SET time_zone = '+08:00';
select 'server local time:', now();
select * from test;
---

**Query #1**

    SET time_zone = '+02:00';

There are no results to be displayed.

---
**Query #2**

    select 'server local time:', now();

| server local time: | now()               |
| ------------------ | ------------------- |
| server local time: | 2020-05-10 16:38:26 |

---
**Query #3**

    select * from test;

| descr                                | dt                  | ts                  |
| ------------------------------------ | ------------------- | ------------------- |
| session tz +08:00 insert local times | 2020-01-01 00:00:00 | 2019-12-31 18:00:00 |
| session tz +00:00 insert local times | 2020-01-01 00:00:00 | 2020-01-01 02:00:00 |

---
**Query #4**

    SET time_zone = '+08:00';

There are no results to be displayed.

---
**Query #5**

    select 'server local time:', now();

| server local time: | now()               |
| ------------------ | ------------------- |
| server local time: | 2020-05-10 22:38:26 |

---
**Query #6**

    select * from test;

| descr                                | dt                  | ts                  |
| ------------------------------------ | ------------------- | ------------------- |
| session tz +08:00 insert local times | 2020-01-01 00:00:00 | 2020-01-01 00:00:00 |
| session tz +00:00 insert local times | 2020-01-01 00:00:00 | 2020-01-01 08:00:00 |

---

View on DB Fiddle

Shows how storing and reading TIMESTAMPs are actually always handled as local times and DATATIMEs are independent from time_zone setting of the DB session.

With rapid test I was not able to make yyyy-MM-dd HH:mm:ss+zz:zz type of timestamps / datetimes to work even on mysql 8.

Sebastian
  • 3,322
  • 22
  • 34
Mikael Lepistö
  • 18,909
  • 3
  • 68
  • 70
  • 1
    That "1 warning" thing is actually a problem, and writing ISO dates with sql_mode STRICT_TRANS_TABLES or STRICT_ALL_TABLES. MySQL does not support ISO dates; it only appears to work in your example because T is explicitly allowed as a delimiter instead of space. The timezone part ("Z" here) is not compatible. See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-literals.html – jelder May 06 '20 at 19:55
  • Thanks for the info! So to have correct time stored to the database should that `Date()` object first interpreted as a local time of the databases timezone and only then be then be stored as timestamp without timezone? Or is there a way to tell timezone offset in that timestamp string? – Mikael Lepistö May 07 '20 at 09:02
  • As far as I can tell, MySQL doesn't support storing time zones in any date column. It's converted to UTC for storage and then converted back to local time when retrieved. – jelder May 07 '20 at 17:26
  • True time zone information is not stored to the database, but the question was how mysql interpret the timestamp without time zone when it is given to database... I mean `2017-01-30T16:49:19.278Z` is clear and says that time is given in UTC time zone. Even if DB timezone has been set to somewhere else that timestamp is absolute and doesn't depend on local time zone of the DB. But when `2017-01-30T16:49:19.278` is given to myslq is the timestamp interpreted as in local time zone of the database or in UTC (maybe it is easier for me to just try it out). – Mikael Lepistö May 08 '20 at 09:02
  • Ok, looks like from mysql 8.0 +00:00 etc. type of absolute times are supported instead of having to insert local times which would be converted from local -> UTC for TIMESTAMP and be stored as is for DATETIME column types https://dev.mysql.com/doc/refman/8.0/en/datetime.html I'll update the answer shortly with better examples. – Mikael Lepistö May 10 '20 at 14:10
13

Instead of Date.now() you could use the built in now() function of Knex.js:

const knexfile = require('../../knexfile');
const knex = require('knex')(knexfile.development);
const date = knex.fn.now();

Alternative:

const knexfile = require('../../knexfile');
const knex = require('knex')(knexfile.development);
knex.raw('CURRENT_TIMESTAMP');
Benny Code
  • 51,456
  • 28
  • 233
  • 198
  • both the knex.fn.now() and knex.raw only return a raw object that I was unable to convert to a date for me – Ryker Feb 22 '22 at 17:16