date.js 8.9 KB

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