-
Notifications
You must be signed in to change notification settings - Fork 183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Date fields - timezone problem #149
Comments
@euronymous I had an issue with non UTC dates in MySQL and found the dateString option useful for a workaround. It may help in your case as well - see issue 120 and daddybh's dateString option: #120 |
Adding checks to allow true OR "DATE" as dateStrings values "DATE" should only effect DATE fields whereas true retains original behavior, effecting DATE, DATETIME and TIMESTAMP fields. See: mysqljs#605 , loopbackio/loopback-connector-mysql#120 , loopbackio/loopback-connector-mysql#149
I also just ran into this problem when writing some tests where I entered data into a mysql database directly, then fetched it with loopback. My understanding is that |
@DaGaMs , Since this is a JavaScript/node system, I think this is the correct behavior for DATETIME and TIMESTAMP columns since JavaScript serializes its Date object into ISO 8601 'which is always 24 characters long: YYYY-MM-DDTHH:mm:ss.sssZ. The timezone is always zero UTC offset, as denoted by the suffix "Z".' See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toJSON Are you saying that if you retrieve the same DATETIME field with mysqljs (which is what this loopback connector uses) and serialize that javascript Date object with .toJSON() you get a different result than you see when you retrieve it as JSON through Loopback? |
Good question. @DaGaMs Can you provide a sample repo so we can clone and verify the issue on our machines locally? |
@DaGaMs , The way I see it, you have a Date in JavaScript as in: If you want to send that to a REST API like Loopback you are going to turn it into JSON: If you retrieve it from Loopback and make a new JavaScript Date object it will be the same date you put in: |
@bbito @superkhau Indeed, I get different results depending whether I get the date with mysqljs or with loopback. Here's a code snippet to explain what I did (from a mocha/chai test): // This is our direct connection to the database
var db = mysql.createConnection({
host : 'localhost',
user : 'test_user',
password : 'test_password',
database : 'testdb',
dateStrings: false
});
// imagine a db.connect(...) somewhere here, and all the server bootstrapping
// make a request object
request = require('supertest')('http://localhost:3000');
thisDate = new Date();
db.query("INSERT INTO `File` (created, name) VALUES (?, ?, ?)", [thisDate, "testFile.txt"], (err, res) => {
db.query("SELECT * FROM `File`", function (err, rows, fields) {
// Get the file through the api
request.get('/api/Files')
.expect(200)
.expect('Content-Type', /json/).end((err, res) => {
var files = res.body;
var file = files[0];
var createdDate = new Date(file.created);
console.log("ThisDate", thisDate);
console.log("ThisDate toJSON", thisDate.toJSON());
console.log("MysqlJS toJSON", rows[0].created.toJSON());
console.log("MysqlJS", rows[0].created);
console.log("LoopBack", file.created);
console.log("CreatedDate toJSON", createdDate.toJSON());
done(err);
});
});
}); The result looked like this:
I'm in London, but due to summer time I'm in GMT+1. So, while mysqlJS correctly converts UTC back to local time, loopback ignores the local timezone. |
@DaGaMs - that sure is confusing! |
When I PUT your ThisDate toJSON value ("2016-07-26T12:49:04.039Z") through loopback API Explorer it persists unchanged in mysql (less ms): mysql> select mft_datetime, mft_date_time_notes from date_time_table where id=12;
+---------------------+-----------------------------------------+
| mft_datetime | mft_date_time_notes |
+---------------------+-----------------------------------------+
| 2016-07-26 12:49:04 | from DaGaMs as 2016-07-26T12:49:04.039Z |
+---------------------+-----------------------------------------+ When I retrieve it through loopback:
|
My {
"name": "File",
"base": "PersistedModel",
"strict": true,
"idInjection": true,
"options": {
"validateUpsert": true
},
"mysql": {
"schema": "testdb",
"table": "File"
},
"properties": {
"created": {
"type": "date",
"required": true,
"length": null,
"precision": null,
"scale": null,
"mysql": {
"columnName": "created",
"dataType": "datetime",
"dataLength": null,
"dataPrecision": null,
"dataScale": null,
"nullable": "N"
},
"_selectable": false
},
"path": {
"type": "string",
"required": true,
"length": 4096,
"precision": null,
"scale": null,
"mysql": {
"columnName": "path",
"dataType": "varchar",
"dataLength": 4096,
"dataPrecision": null,
"dataScale": null,
"nullable": "N"
},
"_selectable": false
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
} the {
"db": {
"name": "db",
"connector": "memory"
},
"mysql": {
"host": "localhost",
"port": 3306,
"database": "testdb",
"password": "test_password",
"name": "mysql",
"user": "test_user",
"connector": "mysql"
}
} |
The
I ran the script again, getting these dates:
Now, here's what's in the database:
So, what seems to be happening is that when I Looking at the output of the |
Very interesting!
https://github.com/strongloop/loopback-connector-mysql/blob/master/lib/mysql.js#L307
It seems that the mysqljs timezone setting is checked and passed to the connection, but dateToMysql is called to make a UTC Date regardless and I don't understand why toJSON isn't simply called if that is the intent anyway... |
I don't know what it breaks, but if I remove that call to dateToMysql(val), then the DATETIME is stored as local (as expected). When I send the same data as above it now gets stored as: mysql> select mft_datetime, mft_date_time_notes from date_time_table where id=13;
+---------------------+-----------------------------------------+
| mft_datetime | mft_date_time_notes |
+---------------------+-----------------------------------------+
| 2016-07-26 05:49:04 | from DaGaMs as 2016-07-26T12:49:04.039Z |
+---------------------+-----------------------------------------+ The return through loopback is still the same as well. |
I don't understand why Loopback-Connector isn't just handing off a date object to mysqljs... That seems to provide the correct behavior in the case of my forked branch... As @DaGaMs found, the current behavior disregards the timezone option and stores as UTC which will cause problems when storing or accessing DATETIME or TIMESTAMP values with other connectors that DO respect the 'local' option for timezone (which is default). |
@superkhau do you know why that conversion to UTC date string is being performed via dateToMysql(val)? Unfortunately a fix will throw off any server-side date comparisons since all existing commits through loopback will be UTC regardless of the mysql timezone setting including the default of 'local'. |
@DaGaMs - Could you try re-running your test with my patch? |
@bbito I do not know off the the top of my head. @bajtos or @raymondfeng Do you know? |
@superkhau what I have found is that the current code (v2.2.1) always commits UTC to mysql. The change I made as I understand it just allows the JS Date object to be passed to the driver. Without this change, neither the default timezone setting of 'local' nor any other setting for timezone effects the committed DATETIME field. With the change I am finding the the default 'local' setting is honored and that changing the setting to 'UTC' changes the commit to UTC. |
I'm only working with the Explorer right now, but in my exploration so far I'm always finding that |
I don't have a good enough understanding of this problem, but based on the discussion above, this looks like a bug to me. One more thing to consider: let's say the database is configured to use British Summer Time when storing DateTime values. And let's say a REST client sends a date in a different timezone, e.g. |
Just to make things complicated, there's another thing to consider: according to the MySQL docs:
So potentially I'm afraid it will be difficult to fix this issue due to the regressions it might cause - currently, all code that enters dates/times through the api will store data as UTC in the database, but code that writes to the database directly needs to ensure that values are UTC or otherwise loopback will mess up the timezone. For now, I've resorted to the making all |
@bajtos I think this is over-thinking the problem. I'm sure there are many challenges in working with time-based data in systems where clients are in multiple time zones, but I don't think it is the responsibility of Loopback to figure those out. The bug as I see it is that Loopback is neither honoring the |
Edit: 20170412: darknos has simple test that contradicts my read of TIMESTAMP vs DATETIME here: I have confirmed with a simple MySQL command line test producing similar results
mysql> select * from date_time_table where id=25;
+----+------------+---------------------+---------------------+----------+------------------------------------------------------------------------------------------------------+
| id | mft_date | mft_datetime | mft_timestamp | mft_time | mft_date_time_notes
|
+----+------------+---------------------+---------------------+----------+------------------------------------------------------------------------------------------------------+
| 25 | 2016-07-27 | 2016-07-27 01:00:00 | 2016-07-27 01:00:00 | 01:00:00 | 7/27/16 - 1:00am PDT toJSON = 2016-07-27T08:00:00.005Z, timezone: Removed, should default to 'local' |
+----+------------+---------------------+---------------------+----------+------------------------------------------------------------------------------------------------------+ Here's the table description for clarity and the Loopback behavior with my patch: mysql> describe date_time_table;
+---------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| mft_date | date | YES | | NULL | |
| mft_datetime | datetime | YES | | NULL | |
| mft_timestamp | timestamp | YES | | NULL | |
| mft_time | time | YES | | NULL | |
| mft_date_time_notes | varchar(255) | YES | | NULL | |
+---------------------+--------------+------+-----+---------+----------------+ Data sent through Loopback:
Data returned from Loopback * Note, I am using my
Finally, here's the model definition - date-time-table.json:
|
@bbito Ah, I see. It appears that I misinterpreted the mysql docs there. |
That would explain why this problem was not reported earlier, as it affects only a small set of LoopBack users.
This sounds like a very reasonable solution to me. I see one potential caveat: how to handle SQL query parameters, e.g. when converting a LoopBack request
I agree that the fix proposed here is a breaking change that must be handled carefully. I am proposing the following approach:
@raymondfeng @superkhau @0candy @loay thoughts? |
This commit contains all previous work after rebase went badly RE: Pull Request requested by @kjdelisle regarding loopbackio#149 following my proposed solution "A" at loopbackio#149 (comment). As mentioned in the post linked above, this change allows the underlying mysqljs/mysql module to handle Date object serialization and removes the forced conversion of Dates to UTC Strings. An opt-out fallback to forced coercion to UTC is included, which was modeled after loopbackio#265 from @darknos . Old, squashed commits: d0ea1d9 Legacy UTC date processing fallback credit: @darknos a59dad7 Remove orphaned string functions e8fdbdc Incorporate @darknos expanded check for zero dates abd4e0a Remove DATE manipulations in from/toColumnValue
no. DateType function works when you pass something in try to put some |
With regard to #271 everything looks good except for the DATE format, which I think we knew was going to be problematic. Inserting basic dates from end-to-end can fall victim to the timezone which can roll them forwards/backwards. (ex. inserting 2017-02-04 from GMT-5 will give you 2017-02-03) I've tested the |
Wow, I'm very surprised! I was sure that #271 would break all sorts of stuff I hadn't tested for. I've just been pounding on what happens when I send and retrieve Date types through REST API and checking MySQL rows. I thought changing the output of Concerning DATEs, I plan to continue to use |
@bbito Ah, sorry! To clarify: "looks good" means that I was manually verifying it to make sure that it's doing what I expect in combination with the other changes in my PR (I'm not going to merge one to test the other, so I linked them locally to play around with it). It does have a few failing tests, but they look unrelated. The loopbackio/loopback-datasource-juggler#1356 PR will allow you to pass values from your model to your database column as a raw string (but still check that they're valid "dates"), which is what I was manually verifying in conjunction with your PR. I've been held up quite a bit today (we're running some planning and such), so I'll be getting to this late in the day. You would still want to use |
@kjdelisle Addressing your earlier comment:
I find this very confusing. Currently I can do this at the DataSource level - not the field/model level as you propose, but the behavior seems fairly transparent to me from the settings below.
I don't know what to make of |
@bbito
But this means that you lose validation of the date at LoopBack level; nothing's stopping you from sending in a non-Date string! This way, if you wanted to insert |
I'm agree with @bbito but notice that "myDate": {
"type": "string",
"dataType": "date",
...
} |
I like the sound of this feature, but I'm suspicious of how SELECTs through mysqljs/mysql will pull the DATE String from MySQL. The only way I know to be sure I'll get the same DATE whether I'm in -12:00 or +14:00 is by passing |
What I'm saying is that the |
@kjdelisle Thanks for the clarification, that squashes my concern about the data! Great! Instead of the 2 attributes: |
If I did that, I'd have to create a new Type throughout the model-builder and dao classes. Doing that might be the "nicer" way, but I'm not really convinced that we gain anything by doing that (or lose anything by not doing that). |
@kjdelisle While I'll admit I was hoping you'd say "I've been itching to create a new Type!" I understand if it's not a good use of resources. I think the gain is that Loopback has cool tools like ARC > Discover Models where it would be nice to select |
Giving it some thought, I do want this to be an easier process for others, and having it as a separate type would be a heck of a lot easier. Let's see how that goes. |
This commit contains all previous work after rebase went badly RE: Pull Request requested by @kjdelisle regarding loopbackio#149 following my proposed solution "A" at loopbackio#149 (comment). As mentioned in the post linked above, this change allows the underlying mysqljs/mysql module to handle Date object serialization and removes the forced conversion of Dates to UTC Strings. An opt-out fallback to forced coercion to UTC is included, which was modeled after loopbackio#265 from @darknos . Old, squashed commits: d0ea1d9 Legacy UTC date processing fallback credit: @darknos a59dad7 Remove orphaned string functions e8fdbdc Incorporate @darknos expanded check for zero dates abd4e0a Remove DATE manipulations in from/toColumnValue
All the relevant PRs are merged, closing. |
Hi,
I've spend the last hour for reverse engineering of that case...
Lets look at the file:
/loopback-connector-mysql/lib/mysql.js:353
val = new Date(val.toString().replace(/GMT.*$/, 'GMT'));
Why you don't give a sh about timezone?! This is at least weird.
Here is an example. I have a date from MySQL:
Mon Jun 01 2015 02:01:01 GMT+0200 (CEST)
So lets try:
And the result is:
And at last the strongloop API response:
"2015-06-01T02:01:01.000Z"
Kind Regards,
Krzysiek
The text was updated successfully, but these errors were encountered: