date.js 12 KB

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