date.js 10 KB

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