diff --git a/lib/date-string.js b/lib/date-string.js new file mode 100644 index 00000000..80ff0e2e --- /dev/null +++ b/lib/date-string.js @@ -0,0 +1,99 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +'use strict'; + +var inspect = require('util').inspect; + +module.exports = DateString; + +/** + * A String whose value is a valid representation of a Date. + * Use this type if you need to preserve the format of the value and still + * check if it's valid. + * Example: + * ```js + * var loopback = require('loopback'); + * var dt = new loopback.DateString('2001-01-01'); + * + * dt.toString(); + * // '2001-01-01' + * dt._date.toISOString(); + * // '2001-01-01T00:00:00.000Z' + * ``` + * + * You can use this definition on your models as well: + * ```json + * { + * "name": "Person", + * "base": "PersistedModel", + * "properties": { + * "name": { + * "type": "string" + * }, + * "dob": { + * "type": "DateString", + * "required": true + * }, + * }, + * "validations": [], + * "relations": {}, + * "acls": [], + * "methods": {} + * } + * ``` + * @class DateString + * @param {String} value + * @constructor + */ +function DateString(value) { + if (!(this instanceof DateString)) { + return new DateString(value); + } + + if (typeof(value) !== 'string') { + throw new Error('Input must be a string'); + } + + Object.defineProperty(this, 'when', { + get: () => { return this._when; }, + set: (val) => { + var d = new Date(val); + if (isNaN(d.getTime())) { + throw new Error('Invalid date'); + } else { + this._when = val; + this._date = d; + } + }, + }); + + this.when = value; +}; + +/** + * Returns the value of DateString in its original form. + * @returns {String} The Date as a String. + */ +DateString.prototype.toString = function() { + return this.when; +}; + +/** + * Returns the JSON representation of the DateString object. + * @returns {String} A JSON string. + */ +DateString.prototype.toJSON = function() { + return JSON.stringify({ + when: this.when, + }); +}; + +DateString.prototype.inspect = function(depth, options) { + return 'DateString ' + inspect({ + when: this.when, + _date: this._date, + }); +}; diff --git a/lib/types.js b/lib/types.js index 12140386..2121969f 100644 --- a/lib/types.js +++ b/lib/types.js @@ -40,6 +40,7 @@ Types.Any.prototype.toObject = Types.Any.prototype.toJSON = function() { }; module.exports = function(modelTypes) { + var DateString = require('./date-string'); var GeoPoint = require('./geo').GeoPoint; for (var t in Types) { @@ -63,6 +64,7 @@ module.exports = function(modelTypes) { modelTypes.registerType(Number); modelTypes.registerType(Boolean); modelTypes.registerType(Date); + modelTypes.registerType(DateString); modelTypes.registerType(Buffer, ['Binary']); modelTypes.registerType(Array); modelTypes.registerType(GeoPoint); diff --git a/test/date-string.test.js b/test/date-string.test.js new file mode 100644 index 00000000..c1ba8b2e --- /dev/null +++ b/test/date-string.test.js @@ -0,0 +1,81 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +/* global describe,it */ +/* jshint expr:true */ + +'use strict'; + +require('should'); + +var DateString = require('../lib/date-string'); +var fmt = require('util').format; +var inspect = require('util').inspect; +var os = require('os'); + +describe('DateString', function() { + describe('constructor', function() { + it('should support a valid date string', function() { + var theDate = '2015-01-01'; + var date = new DateString(theDate); + date.should.not.eql(null); + date.when.should.eql(theDate); + date.toString().should.eql(theDate); + }); + + testValidInput('should allow date with time', '2015-01-01 02:00:00'); + testValidInput('should allow full UTC datetime', '2015-06-30T20:00:00.000Z'); + testValidInput('should allow date with UTC offset', '2015-01-01 20:00:00 GMT-5'); + + testInvalidInput('should throw on non-date string', 'notadate', 'Invalid date'); + testInvalidInput('should throw on incorrect date-like value', + '2015-01-01 25:00:00', 'Invalid date'); + testInvalidInput('should throw on non-string input', 20150101, + 'Input must be a string'); + testInvalidInput('should throw on null input', null, 'Input must be a string'); + + it('should update internal date on set', function() { + var date = new DateString('2015-01-01'); + date.when = '2016-01-01'; + date.when.should.eql('2016-01-01'); + var d = new Date('2016-01-01'); + // The internal date representation should also be updated! + date._date.toString().should.eql(d.toString()); + }); + it('should return custom inspect output', function() { + var date = new DateString('2015-01-01'); + var result = inspect(date); + result.should.not.eql(null); + result.should.eql(fmt('DateString ' + inspect({ + when: date.when, + _date: date._date, + }))); + }); + + it('should return JSON output', function() { + var date = new DateString('2015-01-01'); + var result = date.toJSON(); + result.should.eql(JSON.stringify({when: date.when})); + }); + + function testValidInput(msg, val) { + it(msg, function() { + var theDate = new DateString(val); + theDate.when.should.eql(val); + var d = new Date(val); + theDate._date.toString().should.eql(d.toString()); + }); + } + + function testInvalidInput(msg, val, err) { + it(msg, () => { + var fn = () => { + var theDate = new DateString(val); + }; + fn.should.throw(err); + }); + } + }); +});