This commit is contained in:
Sakib Hasan 2017-04-24 16:19:33 -04:00 committed by GitHub
parent 70f0acadd4
commit 345492e5b2
3 changed files with 22 additions and 355 deletions

View File

@ -103,7 +103,7 @@ Edit `datasources.json` to add any other additional properties that you require.
<td>Enable this option to deal with big numbers (BIGINT and DECIMAL columns) in the database. Default is false.</td>
</tr>
<tr>
<td>timezone</td>
<td>timeZone</td>
<td>String</td>
<td>The timezone used to store local dates. Default is local.</td>
</tr>
@ -280,45 +280,6 @@ Example:
}
```
### Date types
For TIMESTAMP and DATE types, use the `dateType` option to specify custom type. By default it is DATETIME.
Example:
```javascript
{ startTime :
{ type: Date,
dataType: 'timestamp'
}
}
```
**Note:** When quering a `DATE` type, please be aware that values sent to the server via REST API call will be converted to a Date object using the server timezone. Then, only `YYYY-MM-DD` part of the date will be used for the SQL query.
For example, if the client and the server is in GMT+2 and GMT -2 timezone respectively. Performing the following operation at `02:00 on 2016/11/22` from the client side:
```javascript
var products = Product.find({where:{expired:new Date(2016,11,22)}});
```
will result in the REST URL to look like: `/api/Products/?filter={"where":{"expired":"2016-12-21T22:00:00Z"}}` and the SQL will be like this:
```SQL
SELECT * FROM Product WHERE expired = '2016-12-21'
```
which is not correct.
**Solution:** The workaround to avoid such edge case boundaries with timezones is to use the `DATE` type field as a **_string_** type in the LoopBack model definition.
```javascript
{ birthday :
{ type: String,
dataType: 'date'
}
}
```
### Other types
Convert String / DataSource.Text / DataSource.JSON to the following MySQL types:
@ -351,6 +312,18 @@ Example: 
}
```
Convert JSON Date types to  datetime or timestamp
Example: 
```javascript
{ startTime :
{ type: Date,
dataType: 'timestamp'
}
}
```
### Enum
Enums are special. Create an Enum using Enum factory:

View File

@ -135,9 +135,6 @@ function generateOptions(settings) {
charset: s.collation.toUpperCase(), // Correct by docs despite seeming odd.
supportBigNumbers: s.supportBigNumbers,
connectionLimit: s.connectionLimit,
//prevent mysqljs from converting DATE, DATETIME and TIMESTAMP types
//to javascript Date object
dateStrings: true,
};
// Don't configure the DB if the pool can be used for multiple DBs
@ -310,40 +307,17 @@ MySQL.prototype.updateOrCreate = function(model, data, options, cb) {
this._modifyOrCreate(model, data, options, fields, cb);
};
function dateToMysql(dt, tz) {
if (!tz || tz == 'local') {
return dt.getFullYear() + '-' +
fillZeros(dt.getMonth() + 1) + '-' +
fillZeros(dt.getDate()) + ' ' +
fillZeros(dt.getHours()) + ':' +
fillZeros(dt.getMinutes()) + ':' +
fillZeros(dt.getSeconds());
} else {
tz = convertTimezone(tz);
if (tz !== false && tz !== 0) dt.setTime(dt.getTime() + (tz * 60000));
return dt.getUTCFullYear() + '-' +
fillZeros(dt.getUTCMonth() + 1) + '-' +
fillZeros(dt.getUTCDate()) + ' ' +
fillZeros(dt.getUTCHours()) + ':' +
fillZeros(dt.getUTCMinutes()) + ':' +
fillZeros(dt.getUTCSeconds());
}
function dateToMysql(val) {
return val.getUTCFullYear() + '-' +
fillZeros(val.getUTCMonth() + 1) + '-' +
fillZeros(val.getUTCDate()) + ' ' +
fillZeros(val.getUTCHours()) + ':' +
fillZeros(val.getUTCMinutes()) + ':' +
fillZeros(val.getUTCSeconds());
function fillZeros(v) {
return v < 10 ? '0' + v : v;
}
function convertTimezone(tz) {
if (tz === 'Z') {
return 0;
}
var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/);
if (m) return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60;
return false;
}
}
MySQL.prototype.getInsertedId = function(model, info) {
@ -382,13 +356,6 @@ MySQL.prototype.toColumnValue = function(prop, val) {
if (!val.toUTCString) {
val = new Date(val);
}
if (prop.dataType == 'date') {
return dateToMysql(val).substring(0, 10);
} else if (prop.dataType == 'timestamp' || (prop.mysql && prop.mysql.dataType == 'timestamp')) {
var tz = this.client.config.connectionConfig.timezone;
return dateToMysql(val, tz);
}
return dateToMysql(val);
}
if (prop.type === Boolean) {
@ -448,19 +415,10 @@ MySQL.prototype.fromColumnValue = function(prop, val) {
// MySQL allows, unless NO_ZERO_DATE is set, dummy date/time entries
// new Date() will return Invalid Date for those, so we need to handle
// those separate.
if (!val || val == '0000-00-00 00:00:00' || val == '0000-00-00') {
if (val == '0000-00-00 00:00:00') {
val = null;
} else {
var dateString = val;
var tz = this.client.config.connectionConfig.timezone;
if (prop.dataType == 'date') {
dateString += ' 00:00:00';
}
//if datatype is timestamp and zimezone is not local - convert to proper timezone
if (tz !== 'local' && (prop.dataType == 'timestamp' || (prop.mysql && prop.mysql.dataType == 'timestamp'))) {
dateString += ' ' + tz;
}
return new Date(dateString);
val = new Date(val.toString().replace(/GMT.*$/, 'GMT'));
}
break;
case 'Boolean':

View File

@ -1,264 +0,0 @@
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
// Node module: loopback-connector-mysql
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
require('./init.js');
var assert = require('assert');
var db, DateModel;
describe('MySQL DATE, DATETTIME, TIMESTAMP types on server with local TZ', function() {
var date;
var dateOnly = new Date(2015, 10, 25); //2015-12-25
var timezone = getTimeZone();
before(function(done) {
prepareModel('local', done);
});
it('should set local timezone in mysql ' + timezone, function(done) {
query("SET @@session.time_zone = '" + timezone + "';", function(err) {
assert.ok(!err);
done();
});
});
it('should create a model instance with dates', function(done) {
date = new Date();
date.setMilliseconds(0);
DateModel.create({
datetimeField: date,
timestampField: date,
dateField: dateOnly,
}, function(err, obj) {
assert.ok(!err);
done();
});
});
it('should get model instance', function(done) {
DateModel.findOne({
where: {
id: 1,
},
}, function(err, found) {
assert.ok(!err);
assert.equal(found.datetimeField.toISOString(), date.toISOString());
assert.equal(found.timestampField.toISOString(), date.toISOString());
assert.equal(found.dateField.toISOString(), dateOnly.toISOString());
done();
});
});
it('timestampField shoud equal DEFAULT CURRENT_TIMESTAMP field', function(done) {
DateModel.findOne({
where: {
id: 1,
},
}, function(err, found) {
assert.ok(!err);
assert.equal(found.timestampField.toISOString(), found.timestampDefaultField.toISOString());
done();
});
});
it('should find model instance by datetime field', function(done) {
DateModel.findOne({
where: {
datetimeField: date,
},
}, function(err, found) {
assert.ok(!err);
assert.ok(found);
assert.equal(found.id, 1);
done();
});
});
it('should find model instance by timestamp field', function(done) {
DateModel.findOne({
where: {
timestampField: date,
},
}, function(err, found) {
assert.ok(!err);
assert.ok(found);
assert.equal(found.id, 1);
done();
});
});
it('should find model instance by date field', function(done) {
DateModel.findOne({
where: {
dateField: dateOnly,
},
}, function(err, found) {
assert.ok(!err);
assert.ok(found);
assert.equal(found.id, 1);
done();
});
});
it('should disconnect when done', function(done) {
db.disconnect();
done();
});
});
describe('MySQL DATE, DATETTIME, TIMESTAMP types on server with non local TZ (+05:30)', function() {
var timezone = '+05:30';
var date;
var dateOnly = new Date(2016, 11, 22); //2015-12-25
before(function(done) {
prepareModel(timezone, done);
});
it('should set session timezone to ' + timezone, function(done) {
query("SET @@session.time_zone = '" + timezone + "'", function(err) {
assert.ok(!err);
done();
});
});
it('should create a model instance with dates with TZ: +05:30', function(done) {
//set date to current timestamp to comapre with mysql CURRENT_TIMESTAMP from the server
date = new Date();
date.setMilliseconds(0);
DateModel.create({
datetimeField: date,
timestampField: date,
timestampDefaultField: null,
dateField: dateOnly,
}, function(err, found) {
assert.ok(!err);
done();
});
});
it('should get model instance', function(done) {
DateModel.findOne({
where: {
id: 1,
},
}, function(err, found) {
assert.ok(!err);
assert.equal(found.datetimeField.toISOString(), date.toISOString());
assert.equal(found.timestampField.toISOString(), date.toISOString());
assert.equal(found.dateField.toISOString(), dateOnly.toISOString());
done();
});
});
it('timestampField shoud equal DEFAULT CURRENT_TIMESTAMP field', function(done) {
DateModel.findOne({
where: {
id: 1,
},
}, function(err, found) {
assert.ok(!err);
assert.equal(found.timestampField.toISOString(), found.timestampDefaultField.toISOString());
done();
});
});
it('should find model instance by datetime field', function(done) {
DateModel.findOne({
where: {
datetimeField: date,
},
}, function(err, found) {
assert.ok(!err);
assert.ok(found);
assert.equal(found.id, 1);
done();
});
});
it('should find model instance by timestamp field', function(done) {
DateModel.findOne({
where: {
timestampField: date,
},
}, function(err, found) {
assert.ok(!err);
assert.ok(found);
assert.equal(found.id, 1);
done();
});
});
it('should find model instance by date field', function(done) {
DateModel.findOne({
where: {
dateField: dateOnly,
},
}, function(err, found) {
assert.ok(!err);
assert.ok(found);
assert.equal(found.id, 1);
done();
});
});
it('set timezone to UTC +00:00', function(done) {
query("SET @@session.time_zone = '+00:00'", function(err) {
assert.ok(!err);
done();
});
});
it('now datetime and timestamp field should be different in 330 minutes 5:30 - 0:00', function(done) {
DateModel.findOne({
where: {
id: 1,
},
}, function(err, found) {
assert.ok(!err);
var diff = (found.datetimeField.getTime() - found.timestampField.getTime()) / 60000;
assert.equal(diff, 330);
done();
});
});
it('should disconnect when done', function(done) {
db.disconnect();
done();
});
});
var prepareModel = function(tz, done) {
db = getSchema({timezone: tz});
DateModel = db.define('DateModel', {
id: {type: Number, id: 1, generated: true},
datetimeField: {type: Date, dataType: 'datetime', null: false},
timestampField: {type: Date, dataType: 'timestamp', null: false},
timestampDefaultField: {type: Date, dataType: 'timestamp', null: false},
dateField: {type: Date, dataType: 'date', null: false},
});
// set sql_mode to empty to zero's on date for CURRENT_TIMESTAMP
query("SET sql_mode = ''", function(err) {
if (err) done(err);
db.automigrate(function() {
//SET DEFAULT CURRENT_TIMESTAMP for timestampDefaultField
query('ALTER TABLE `DateModel` CHANGE COLUMN `timestampDefaultField` ' +
'`timestampDefaultField` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP;', function(err, result) {
if (err) done(err);
done();
});
});
});
};
var query = function(sql, cb) {
db.adapter.execute(sql, cb);
};
function getTimeZone() {
var offset = new Date().getTimezoneOffset(), o = Math.abs(offset);
return (offset < 0 ? '+' : '-') + ('00' + Math.floor(o / 60)).slice(-2) + ':' + ('00' + (o % 60)).slice(-2);
}