date.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /*!
  2. * Module requirements.
  3. */
  4. 'use strict';
  5. const MongooseError = require('../error/index');
  6. const SchemaDateOptions = require('../options/SchemaDateOptions');
  7. const SchemaType = require('../schematype');
  8. const castDate = require('../cast/date');
  9. const utils = require('../utils');
  10. const CastError = SchemaType.CastError;
  11. /**
  12. * Date SchemaType constructor.
  13. *
  14. * @param {String} key
  15. * @param {Object} options
  16. * @inherits SchemaType
  17. * @api public
  18. */
  19. function SchemaDate(key, options) {
  20. SchemaType.call(this, key, options, 'Date');
  21. }
  22. /**
  23. * This schema type's name, to defend against minifiers that mangle
  24. * function names.
  25. *
  26. * @api public
  27. */
  28. SchemaDate.schemaName = 'Date';
  29. SchemaDate.defaultOptions = {};
  30. /*!
  31. * Inherits from SchemaType.
  32. */
  33. SchemaDate.prototype = Object.create(SchemaType.prototype);
  34. SchemaDate.prototype.constructor = SchemaDate;
  35. SchemaDate.prototype.OptionsConstructor = SchemaDateOptions;
  36. /*!
  37. * ignore
  38. */
  39. SchemaDate._cast = castDate;
  40. /**
  41. * Sets a default option for all Date instances.
  42. *
  43. * ####Example:
  44. *
  45. * // Make all dates have `required` of true by default.
  46. * mongoose.Schema.Date.set('required', true);
  47. *
  48. * const User = mongoose.model('User', new Schema({ test: Date }));
  49. * new User({ }).validateSync().errors.test.message; // Path `test` is required.
  50. *
  51. * @param {String} option - The option you'd like to set the value for
  52. * @param {*} value - value for option
  53. * @return {undefined}
  54. * @function set
  55. * @static
  56. * @api public
  57. */
  58. SchemaDate.set = SchemaType.set;
  59. /**
  60. * Get/set the function used to cast arbitrary values to dates.
  61. *
  62. * ####Example:
  63. *
  64. * // Mongoose converts empty string '' into `null` for date types. You
  65. * // can create a custom caster to disable it.
  66. * const original = mongoose.Schema.Types.Date.cast();
  67. * mongoose.Schema.Types.Date.cast(v => {
  68. * assert.ok(v !== '');
  69. * return original(v);
  70. * });
  71. *
  72. * // Or disable casting entirely
  73. * mongoose.Schema.Types.Date.cast(false);
  74. *
  75. * @param {Function} caster
  76. * @return {Function}
  77. * @function get
  78. * @static
  79. * @api public
  80. */
  81. SchemaDate.cast = function cast(caster) {
  82. if (arguments.length === 0) {
  83. return this._cast;
  84. }
  85. if (caster === false) {
  86. caster = v => {
  87. if (v != null && !(v instanceof Date)) {
  88. throw new Error();
  89. }
  90. return v;
  91. };
  92. }
  93. this._cast = caster;
  94. return this._cast;
  95. };
  96. /**
  97. * Declares a TTL index (rounded to the nearest second) for _Date_ types only.
  98. *
  99. * This sets the `expireAfterSeconds` index option available in MongoDB >= 2.1.2.
  100. * This index type is only compatible with Date types.
  101. *
  102. * ####Example:
  103. *
  104. * // expire in 24 hours
  105. * new Schema({ createdAt: { type: Date, expires: 60*60*24 }});
  106. *
  107. * `expires` utilizes the `ms` module from [guille](https://github.com/guille/) allowing us to use a friendlier syntax:
  108. *
  109. * ####Example:
  110. *
  111. * // expire in 24 hours
  112. * new Schema({ createdAt: { type: Date, expires: '24h' }});
  113. *
  114. * // expire in 1.5 hours
  115. * new Schema({ createdAt: { type: Date, expires: '1.5h' }});
  116. *
  117. * // expire in 7 days
  118. * var schema = new Schema({ createdAt: Date });
  119. * schema.path('createdAt').expires('7d');
  120. *
  121. * @param {Number|String} when
  122. * @added 3.0.0
  123. * @return {SchemaType} this
  124. * @api public
  125. */
  126. SchemaDate.prototype.expires = function(when) {
  127. if (!this._index || this._index.constructor.name !== 'Object') {
  128. this._index = {};
  129. }
  130. this._index.expires = when;
  131. utils.expires(this._index);
  132. return this;
  133. };
  134. /*!
  135. * ignore
  136. */
  137. SchemaDate._checkRequired = v => v instanceof Date;
  138. /**
  139. * Override the function the required validator uses to check whether a string
  140. * passes the `required` check.
  141. *
  142. * ####Example:
  143. *
  144. * // Allow empty strings to pass `required` check
  145. * mongoose.Schema.Types.String.checkRequired(v => v != null);
  146. *
  147. * const M = mongoose.model({ str: { type: String, required: true } });
  148. * new M({ str: '' }).validateSync(); // `null`, validation passes!
  149. *
  150. * @param {Function} fn
  151. * @return {Function}
  152. * @function checkRequired
  153. * @static
  154. * @api public
  155. */
  156. SchemaDate.checkRequired = SchemaType.checkRequired;
  157. /**
  158. * Check if the given value satisfies a required validator. To satisfy
  159. * a required validator, the given value must be an instance of `Date`.
  160. *
  161. * @param {Any} value
  162. * @param {Document} doc
  163. * @return {Boolean}
  164. * @api public
  165. */
  166. SchemaDate.prototype.checkRequired = function(value, doc) {
  167. if (SchemaType._isRef(this, value, doc, true)) {
  168. return !!value;
  169. }
  170. // `require('util').inherits()` does **not** copy static properties, and
  171. // plugins like mongoose-float use `inherits()` for pre-ES6.
  172. const _checkRequired = typeof this.constructor.checkRequired == 'function' ?
  173. this.constructor.checkRequired() :
  174. SchemaDate.checkRequired();
  175. return _checkRequired(value);
  176. };
  177. /**
  178. * Sets a minimum date validator.
  179. *
  180. * ####Example:
  181. *
  182. * var s = new Schema({ d: { type: Date, min: Date('1970-01-01') })
  183. * var M = db.model('M', s)
  184. * var m = new M({ d: Date('1969-12-31') })
  185. * m.save(function (err) {
  186. * console.error(err) // validator error
  187. * m.d = Date('2014-12-08');
  188. * m.save() // success
  189. * })
  190. *
  191. * // custom error messages
  192. * // We can also use the special {MIN} token which will be replaced with the invalid value
  193. * var min = [Date('1970-01-01'), 'The value of path `{PATH}` ({VALUE}) is beneath the limit ({MIN}).'];
  194. * var schema = new Schema({ d: { type: Date, min: min })
  195. * var M = mongoose.model('M', schema);
  196. * var s= new M({ d: Date('1969-12-31') });
  197. * s.validate(function (err) {
  198. * console.log(String(err)) // ValidationError: The value of path `d` (1969-12-31) is before the limit (1970-01-01).
  199. * })
  200. *
  201. * @param {Date} value minimum date
  202. * @param {String} [message] optional custom error message
  203. * @return {SchemaType} this
  204. * @see Customized Error Messages #error_messages_MongooseError-messages
  205. * @api public
  206. */
  207. SchemaDate.prototype.min = function(value, message) {
  208. if (this.minValidator) {
  209. this.validators = this.validators.filter(function(v) {
  210. return v.validator !== this.minValidator;
  211. }, this);
  212. }
  213. if (value) {
  214. let msg = message || MongooseError.messages.Date.min;
  215. if (typeof msg === 'string') {
  216. msg = msg.replace(/{MIN}/, (value === Date.now ? 'Date.now()' : value.toString()));
  217. }
  218. const _this = this;
  219. this.validators.push({
  220. validator: this.minValidator = function(val) {
  221. let _value = value;
  222. if (typeof value === 'function' && value !== Date.now) {
  223. _value = _value.call(this);
  224. }
  225. const min = (_value === Date.now ? _value() : _this.cast(_value));
  226. return val === null || val.valueOf() >= min.valueOf();
  227. },
  228. message: msg,
  229. type: 'min',
  230. min: value
  231. });
  232. }
  233. return this;
  234. };
  235. /**
  236. * Sets a maximum date validator.
  237. *
  238. * ####Example:
  239. *
  240. * var s = new Schema({ d: { type: Date, max: Date('2014-01-01') })
  241. * var M = db.model('M', s)
  242. * var m = new M({ d: Date('2014-12-08') })
  243. * m.save(function (err) {
  244. * console.error(err) // validator error
  245. * m.d = Date('2013-12-31');
  246. * m.save() // success
  247. * })
  248. *
  249. * // custom error messages
  250. * // We can also use the special {MAX} token which will be replaced with the invalid value
  251. * var max = [Date('2014-01-01'), 'The value of path `{PATH}` ({VALUE}) exceeds the limit ({MAX}).'];
  252. * var schema = new Schema({ d: { type: Date, max: max })
  253. * var M = mongoose.model('M', schema);
  254. * var s= new M({ d: Date('2014-12-08') });
  255. * s.validate(function (err) {
  256. * console.log(String(err)) // ValidationError: The value of path `d` (2014-12-08) exceeds the limit (2014-01-01).
  257. * })
  258. *
  259. * @param {Date} maximum date
  260. * @param {String} [message] optional custom error message
  261. * @return {SchemaType} this
  262. * @see Customized Error Messages #error_messages_MongooseError-messages
  263. * @api public
  264. */
  265. SchemaDate.prototype.max = function(value, message) {
  266. if (this.maxValidator) {
  267. this.validators = this.validators.filter(function(v) {
  268. return v.validator !== this.maxValidator;
  269. }, this);
  270. }
  271. if (value) {
  272. let msg = message || MongooseError.messages.Date.max;
  273. if (typeof msg === 'string') {
  274. msg = msg.replace(/{MAX}/, (value === Date.now ? 'Date.now()' : value.toString()));
  275. }
  276. const _this = this;
  277. this.validators.push({
  278. validator: this.maxValidator = function(val) {
  279. let _value = value;
  280. if (typeof _value === 'function' && _value !== Date.now) {
  281. _value = _value.call(this);
  282. }
  283. const max = (_value === Date.now ? _value() : _this.cast(_value));
  284. return val === null || val.valueOf() <= max.valueOf();
  285. },
  286. message: msg,
  287. type: 'max',
  288. max: value
  289. });
  290. }
  291. return this;
  292. };
  293. /**
  294. * Casts to date
  295. *
  296. * @param {Object} value to cast
  297. * @api private
  298. */
  299. SchemaDate.prototype.cast = function(value) {
  300. const castDate = typeof this.constructor.cast === 'function' ?
  301. this.constructor.cast() :
  302. SchemaDate.cast();
  303. try {
  304. return castDate(value);
  305. } catch (error) {
  306. throw new CastError('date', value, this.path, error, this);
  307. }
  308. };
  309. /*!
  310. * Date Query casting.
  311. *
  312. * @api private
  313. */
  314. function handleSingle(val) {
  315. return this.cast(val);
  316. }
  317. SchemaDate.prototype.$conditionalHandlers =
  318. utils.options(SchemaType.prototype.$conditionalHandlers, {
  319. $gt: handleSingle,
  320. $gte: handleSingle,
  321. $lt: handleSingle,
  322. $lte: handleSingle
  323. });
  324. /**
  325. * Casts contents for queries.
  326. *
  327. * @param {String} $conditional
  328. * @param {any} [value]
  329. * @api private
  330. */
  331. SchemaDate.prototype.castForQuery = function($conditional, val) {
  332. if (arguments.length !== 2) {
  333. return this._castForQuery($conditional);
  334. }
  335. const handler = this.$conditionalHandlers[$conditional];
  336. if (!handler) {
  337. throw new Error('Can\'t use ' + $conditional + ' with Date.');
  338. }
  339. return handler.call(this, val);
  340. };
  341. /*!
  342. * Module exports.
  343. */
  344. module.exports = SchemaDate;