date.js 10 KB

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