├── .prettierrc ├── screenshot.png ├── .travis.yml ├── .gitignore ├── lib ├── rules │ ├── no-dynamic-import-moment.js │ ├── no-require-moment.js │ ├── no-moment-constructor.js │ ├── no-import-moment.js │ ├── no-moment-methods.js │ └── methods.json ├── index.js └── __tests__ │ └── index.js ├── LICENSE ├── package.json ├── __tests__ ├── performance.js └── index.js └── README.md /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5" 4 | } 5 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeDebugTest/You-Dont-Need-Momentjs/master/screenshot.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | - '8.0' 5 | - 10 6 | - 11 7 | after_success: 8 | - npm run coveralls 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | .vscode 9 | 10 | coverage 11 | yarn.lock 12 | package-lock.json 13 | 14 | .history -------------------------------------------------------------------------------- /lib/rules/no-dynamic-import-moment.js: -------------------------------------------------------------------------------- 1 | var message = 'Use date-fns or Native Date methods instead of moment.js'; 2 | 3 | module.exports = function(context) { 4 | return { 5 | CallExpression: function(node) { 6 | if (node.callee.type !== 'Import') { 7 | return; 8 | } 9 | 10 | var arg = node.arguments[0]; 11 | 12 | if (arg.type === 'Literal' && arg.value === 'moment') { 13 | context.report(node, message); 14 | } 15 | }, 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /lib/rules/no-require-moment.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context) { 2 | return { 3 | CallExpression: function(node) { 4 | if (node.callee.type === 'Identifier' && node.callee.name === 'require') { 5 | var arg = node.arguments[0]; 6 | 7 | if (arg && arg.type === 'Literal' && arg.value === 'moment') { 8 | context.report( 9 | node, 10 | 'Use date-fns or Native Date methods instead of moment.js' 11 | ); 12 | } 13 | } 14 | }, 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /lib/rules/no-moment-constructor.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context) { 2 | return { 3 | CallExpression(node) { 4 | if (node.callee.name === 'moment') { 5 | if (node.arguments.length === 0 || node.arguments.length === 1) { 6 | context.report({ 7 | node, 8 | message: `Consider using Native new Date().`, 9 | }); 10 | } else { 11 | context.report({ 12 | node, 13 | message: `Consider using date-fns, e.g. parse("2010-10-20 4:30", "yyyy-MM-dd H:mm", new Date()).`, 14 | }); 15 | } 16 | } 17 | }, 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /lib/rules/no-import-moment.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context) { 2 | return { 3 | ImportDeclaration: function(node) { 4 | node.specifiers.forEach(function(specifier) { 5 | if ( 6 | (specifier.type === 'ImportDefaultSpecifier' || 7 | specifier.type === 'ImportNamespaceSpecifier') && 8 | specifier.local.type === 'Identifier' && 9 | specifier.local.name === 'moment' 10 | ) { 11 | context.report( 12 | node, 13 | 'Use date-fns or Native Date methods instead of moment.js' 14 | ); 15 | } 16 | }); 17 | }, 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const rules = Object.assign( 2 | {}, 3 | { 4 | 'no-dynamic-import-moment': require('./rules/no-dynamic-import-moment'), 5 | 'no-import-moment': require('./rules/no-import-moment'), 6 | 'no-moment-constructor': require('./rules/no-moment-constructor'), 7 | 'no-require-moment': require('./rules/no-require-moment'), 8 | }, 9 | require('./rules/no-moment-methods') 10 | ); 11 | 12 | module.exports.rules = rules; 13 | 14 | const configure = (list, level) => { 15 | const r = {}; 16 | Object.keys(list).map(rule => (r['you-dont-need-momentjs/' + rule] = level)); 17 | return r; 18 | }; 19 | 20 | module.exports.configs = { 21 | recommended: { 22 | plugins: ['you-dont-need-momentjs'], 23 | rules: configure(rules, 2), 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /lib/rules/no-moment-methods.js: -------------------------------------------------------------------------------- 1 | const kebabCase = require('kebab-case'); 2 | const rules = require('./methods'); 3 | 4 | for (const rule in rules) { 5 | const alternative = rules[rule].alternative; 6 | const ruleName = rules[rule].ruleName || kebabCase(rule); 7 | module.exports[ruleName] = { 8 | create(context) { 9 | return { 10 | CallExpression(node) { 11 | const callee = node.callee; 12 | const objectName = 13 | callee.name || 14 | (callee.object && callee.object.name) || 15 | (callee.object && 16 | callee.object.callee && 17 | callee.object.callee.name); 18 | if ( 19 | objectName === 'moment' && 20 | callee.property && 21 | (callee.property.name === rule || callee.property.value === rule) 22 | ) { 23 | context.report({ 24 | node, 25 | message: `Consider using ${alternative}`, 26 | }); 27 | } 28 | }, 29 | }; 30 | }, 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 You-Dont-Need 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-you-dont-need-momentjs", 3 | "description": "Check better alternatives you can use without momentjs", 4 | "version": "1.4.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/you-dont-need/You-Dont-Need-Momentjs.git" 8 | }, 9 | "keywords": [ 10 | "moment-js", 11 | "date-fns", 12 | "eslint", 13 | "eslintplugin", 14 | "eslint-plugin", 15 | "tree-shaking", 16 | "you-dont-need" 17 | ], 18 | "author": "Andrew Yang ", 19 | "contributors": [ 20 | "Robert Chang ", 21 | "Jago MF (https://github.com/jagomf)", 22 | "Steve Mao (https://github.com/stevemao)" 23 | ], 24 | "main": "lib/index.js", 25 | "scripts": { 26 | "test:watch": "jest --watch", 27 | "test": "jest --coverage", 28 | "test:perf": "node ./__tests__/performance.js", 29 | "coveralls": "cat ./coverage/lcov.info | coveralls", 30 | "precommit": "pretty-quick --staged" 31 | }, 32 | "devDependencies": { 33 | "babel-eslint": "^9.0.0", 34 | "coveralls": "^3.0.2", 35 | "date-fns": "^2.0.0-alpha.23", 36 | "dayjs": "^1.7.5", 37 | "eslint": "^5.5.0", 38 | "husky": "^0.14.3", 39 | "jest": "^23.5.0", 40 | "luxon": "^1.4.4", 41 | "moment": "^2.22.2", 42 | "prettier": "^1.14.2", 43 | "pretty-quick": "^1.6.0" 44 | }, 45 | "dependencies": { 46 | "kebab-case": "^1.0.0" 47 | }, 48 | "peerDependencies": { 49 | "eslint": "^5.5.0" 50 | }, 51 | "jest": { 52 | "collectCoverageFrom": [ 53 | "lib/**/*.js" 54 | ], 55 | "testPathIgnorePatterns": [ 56 | "/node_modules/", 57 | "./__tests__/performance.js" 58 | ] 59 | }, 60 | "license": "MIT", 61 | "bugs": { 62 | "url": "https://github.com/you-dont-need/You-Dont-Need-Momentjs/issues" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/rules/methods.json: -------------------------------------------------------------------------------- 1 | { 2 | "seconds": { 3 | "alternative": "new Date().getSeconds() or new Date().setSeconds()" 4 | }, 5 | "hours": { 6 | "alternative": "new Date().getHours() or new Date().setHours()" 7 | }, 8 | "date": { 9 | "alternative": "new Date().getDate() or new Date().setDate()" 10 | }, 11 | "day": { 12 | "alternative": "new Date().getDay() or new Date().setDate()" 13 | }, 14 | "dayOfYear": { 15 | "alternative": "date-fns getDayOfYear(date) or setDayOfYear(date, dayOfYear)" 16 | }, 17 | "week": { 18 | "alternative": "date-fns getWeek(date) or setWeek(date, week)" 19 | }, 20 | "isoWeeksInYear": { 21 | "alternative": "date-fns getISOWeeksInYear(date)" 22 | }, 23 | "max": { 24 | "alternative": "date-fns max(date)" 25 | }, 26 | "min": { 27 | "alternative": "date-fns min(date)" 28 | }, 29 | "add": { 30 | "alternative": "date-fns addDays(date, amount) or dayjs().add(number, unit)" 31 | }, 32 | "subtract": { 33 | "alternative": "date-fns subDays(date, amount) or dayjs().subtract(number, unit)" 34 | }, 35 | "startOf": { 36 | "alternative": "date-fns startOfMonth(date) or dayjs().startOf(unit)" 37 | }, 38 | "endOf": { 39 | "alternative": "date-fns endOfDay(date) or dayjs().endOf(unit)" 40 | }, 41 | "format": { 42 | "alternative": "date-fns format(date, format) or dayjs().format()" 43 | }, 44 | "fromNow": { 45 | "alternative": "date-fns formatDistance(date, baseDate)" 46 | }, 47 | "to": { 48 | "alternative": "date-fns formatDistance(date, baseDate)" 49 | }, 50 | "diff": { 51 | "alternative": "date-fns differenceInMilliseconds(dateLeft, dateRight) or dayjs().diff()" 52 | }, 53 | "daysInMonth": { 54 | "alternative": "date-fns getDaysInMonth(date) or dayjs().daysInMonth();" 55 | }, 56 | "isBefore": { 57 | "alternative": "date-fns isBefore(date, dateToCompare) or dayjs().isBefore()" 58 | }, 59 | "isSame": { 60 | "alternative": "date-fns isSameMonth(dateLeft, dateRight) or dayjs().isSame()" 61 | }, 62 | "isAfter": { 63 | "alternative": "date-fns isAfter(date, dateToCompare) or dayjs().isAfter()" 64 | }, 65 | "isBetween": { 66 | "alternative": "date-fns isWithinInterval(date, interval)" 67 | }, 68 | "isLeapYear": { 69 | "alternative": "date-fns isLeapYear(date)" 70 | }, 71 | "isDate": { 72 | "alternative": "date-fns isDate(date)" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/__tests__/index.js: -------------------------------------------------------------------------------- 1 | const rule = require('../').rules; 2 | const RuleTester = require('eslint').RuleTester; 3 | 4 | RuleTester.setDefaultConfig({ 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | ecmaVersion: 6, 8 | sourceType: 'module', 9 | }, 10 | }); 11 | 12 | var ruleTester = new RuleTester(); 13 | 14 | describe('ESLint plugin', () => { 15 | ruleTester.run('no-dynamic-import-moment', rule['no-dynamic-import-moment'], { 16 | valid: ["import('date-fns').then()"], 17 | invalid: [ 18 | { 19 | code: "import('moment').then(()=>{})", 20 | errors: [ 21 | { 22 | message: 'Use date-fns or Native Date methods instead of moment.js', 23 | type: 'CallExpression', 24 | }, 25 | ], 26 | }, 27 | { 28 | code: "(async () => { const m = await import('moment'); })()", 29 | errors: [ 30 | { 31 | message: 'Use date-fns or Native Date methods instead of moment.js', 32 | type: 'CallExpression', 33 | }, 34 | ], 35 | }, 36 | ], 37 | }); 38 | 39 | ruleTester.run('no-import-moment', rule['no-import-moment'], { 40 | valid: [ 41 | "import { format, formatDistance, formatRelative, subDays } from 'date-fns'", 42 | ], 43 | invalid: [ 44 | { 45 | code: "import moment from 'moment'", 46 | errors: [ 47 | { 48 | message: 'Use date-fns or Native Date methods instead of moment.js', 49 | type: 'ImportDeclaration', 50 | }, 51 | ], 52 | }, 53 | { 54 | code: "import * as moment from 'moment'", 55 | errors: [ 56 | { 57 | message: 'Use date-fns or Native Date methods instead of moment.js', 58 | type: 'ImportDeclaration', 59 | }, 60 | ], 61 | }, 62 | ], 63 | }); 64 | 65 | ruleTester.run('no-moment-constructor', rule['no-moment-constructor'], { 66 | valid: ['new Date()', 'Date()'], 67 | invalid: [ 68 | { 69 | code: 'moment()', 70 | errors: [ 71 | { 72 | message: 'Consider using Native new Date().', 73 | type: 'CallExpression', 74 | }, 75 | ], 76 | }, 77 | { 78 | code: "moment('12-25-1995')", 79 | errors: [ 80 | { 81 | message: 'Consider using Native new Date().', 82 | type: 'CallExpression', 83 | }, 84 | ], 85 | }, 86 | { 87 | code: "moment('12-25-1995', 'MM-DD-YYYY')", 88 | errors: [ 89 | { 90 | message: 91 | 'Consider using date-fns, e.g. parse("2010-10-20 4:30", "yyyy-MM-dd H:mm", new Date()).', 92 | type: 'CallExpression', 93 | }, 94 | ], 95 | }, 96 | ], 97 | }); 98 | 99 | ruleTester.run('no-moment-methods/seconds', rule['seconds'], { 100 | valid: [], 101 | invalid: [ 102 | { 103 | code: 'moment().seconds()', 104 | errors: [ 105 | { 106 | message: 107 | 'Consider using new Date().getSeconds() or new Date().setSeconds()', 108 | type: 'CallExpression', 109 | }, 110 | ], 111 | }, 112 | ], 113 | }); 114 | 115 | ruleTester.run('no-moment-methods/seconds', rule['seconds'], { 116 | valid: [], 117 | invalid: [ 118 | { 119 | code: "moment()['seconds']();", 120 | errors: [ 121 | { 122 | message: 123 | 'Consider using new Date().getSeconds() or new Date().setSeconds()', 124 | type: 'CallExpression', 125 | }, 126 | ], 127 | }, 128 | ], 129 | }); 130 | 131 | ruleTester.run('no-moment-methods/is-after', rule['is-after'], { 132 | valid: [], 133 | invalid: [ 134 | { 135 | code: "const m = moment('2010-10-20').isAfter('2010-10-19')", 136 | errors: [ 137 | { 138 | message: 139 | 'Consider using date-fns isAfter(date, dateToCompare) or dayjs().isAfter()', 140 | type: 'CallExpression', 141 | }, 142 | ], 143 | }, 144 | ], 145 | }); 146 | 147 | ruleTester.run('no-require-moment', rule['no-require-moment'], { 148 | valid: [ 149 | "var { format, formatDistance, formatRelative, subDays } = require('date-fns')", 150 | 'var empty = require()', 151 | 'var other = r()', 152 | ], 153 | invalid: [ 154 | { 155 | code: "var moment = require('moment')", 156 | errors: [ 157 | { 158 | message: 'Use date-fns or Native Date methods instead of moment.js', 159 | type: 'CallExpression', 160 | }, 161 | ], 162 | }, 163 | { 164 | code: "var format = require('moment').format", 165 | errors: [ 166 | { 167 | message: 'Use date-fns or Native Date methods instead of moment.js', 168 | type: 'CallExpression', 169 | }, 170 | ], 171 | }, 172 | ], 173 | }); 174 | }); 175 | -------------------------------------------------------------------------------- /__tests__/performance.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | const { 3 | getSeconds, 4 | getHours, 5 | setHours, 6 | endOfDay, 7 | startOfMonth, 8 | setSeconds, 9 | max, 10 | min, 11 | getDate, 12 | addDays, 13 | subDays, 14 | setDate, 15 | getDay, 16 | setDay, 17 | getISOWeeksInYear, 18 | getDayOfYear, 19 | setDayOfYear, 20 | setWeek, 21 | getWeek, 22 | getDaysInMonth, 23 | } = require('date-fns'); 24 | const dayjs = require('dayjs'); 25 | const { DateTime } = require('luxon'); 26 | const iterations = 1000000; 27 | const array = [ 28 | new Date(2017, 4, 13), 29 | new Date(2018, 2, 12), 30 | new Date(2016, 0, 10), 31 | new Date(2016, 0, 9), 32 | ]; 33 | 34 | const performanceTest = (type, testFunction) => { 35 | console.time(type); 36 | for (let i = 0; i < iterations; i++) { 37 | testFunction(); 38 | } 39 | console.timeEnd(type); 40 | }; 41 | 42 | const runTests = object => { 43 | for (const key in object) { 44 | if (typeof object[key] === 'function') { 45 | object[key](); 46 | } 47 | } 48 | }; 49 | 50 | const Get = { 51 | moment: () => { 52 | performanceTest('Moment', () => { 53 | moment().seconds(); 54 | moment().hours(); 55 | }); 56 | }, 57 | native: () => { 58 | performanceTest('Native', () => { 59 | new Date().getSeconds(); 60 | new Date().getHours(); 61 | }); 62 | }, 63 | dateFns: () => { 64 | performanceTest('DateFns', () => { 65 | getSeconds(new Date()); 66 | getHours(new Date()); 67 | }); 68 | }, 69 | dayJs: () => { 70 | performanceTest('DayJs', () => { 71 | dayjs().second(); 72 | dayjs().hour(); 73 | }); 74 | }, 75 | luxon: () => { 76 | performanceTest('Luxon', () => { 77 | DateTime.local().second; 78 | DateTime.local().hour; 79 | }); 80 | }, 81 | }; 82 | 83 | // runTests(Get); 84 | 85 | const Set = { 86 | moment: () => { 87 | performanceTest('Moment', () => { 88 | moment().seconds(30); 89 | moment().hours(13); 90 | }); 91 | }, 92 | native: () => { 93 | performanceTest('Native', () => { 94 | new Date(new Date().setSeconds(30)); 95 | new Date(new Date().setHours(13)); 96 | }); 97 | }, 98 | dateFns: () => { 99 | performanceTest('DateFns', () => { 100 | setSeconds(new Date(), 30); 101 | setHours(new Date(), 13); 102 | }); 103 | }, 104 | dayJs: () => { 105 | performanceTest('DayJs', () => { 106 | dayjs().set('second', 30); 107 | dayjs().set('hour', 13); 108 | }); 109 | }, 110 | luxon: () => { 111 | performanceTest('Luxon', () => { 112 | DateTime.utc().set({ second: 30 }); 113 | DateTime.utc().set({ hour: 13 }); 114 | }); 115 | }, 116 | }; 117 | 118 | // runTests(Set); 119 | 120 | const DateOfMonth = { 121 | moment: () => { 122 | performanceTest('Moment', () => { 123 | moment().date(); 124 | moment().date(4); 125 | }); 126 | }, 127 | native: () => { 128 | performanceTest('Native', () => { 129 | new Date().getDate(); 130 | new Date().setDate(4); 131 | }); 132 | }, 133 | dateFns: () => { 134 | performanceTest('DateFns', () => { 135 | getDate(new Date()); 136 | setDate(new Date(), 4); 137 | }); 138 | }, 139 | dayJs: () => { 140 | performanceTest('DayJs', () => { 141 | dayjs().date(); 142 | dayjs().set('date', 4); 143 | }); 144 | }, 145 | luxon: () => { 146 | performanceTest('Luxon', () => { 147 | DateTime.local().day; 148 | DateTime.local().set({ day: 4 }); 149 | }); 150 | }, 151 | }; 152 | 153 | // runTests(DateOfMonth); 154 | 155 | const DayOfWeek = { 156 | moment: () => { 157 | performanceTest('Moment', () => { 158 | moment().day(); 159 | moment().day(-14); 160 | }); 161 | }, 162 | native: () => { 163 | performanceTest('Native', () => { 164 | new Date().getDay(); 165 | new Date().setDate(new Date().getDate() - 14); 166 | }); 167 | }, 168 | dateFns: () => { 169 | performanceTest('DateFns', () => { 170 | getDay(new Date()); 171 | setDay(new Date(), -14); 172 | }); 173 | }, 174 | dayJs: () => { 175 | performanceTest('DayJs', () => { 176 | dayjs().day(); 177 | dayjs().set('day', -14); 178 | }); 179 | }, 180 | luxon: () => { 181 | performanceTest('Luxon', () => { 182 | DateTime.local().weekday; 183 | DateTime.local().set({ weekday: -14 }); 184 | }); 185 | }, 186 | }; 187 | 188 | // runTests(DayOfWeek); 189 | 190 | const DayOfYear = { 191 | moment: () => { 192 | performanceTest('Moment', () => { 193 | moment().dayOfYear(); 194 | moment().dayOfYear(256); 195 | }); 196 | }, 197 | native: () => { 198 | performanceTest('Native', () => { 199 | Math.floor( 200 | (new Date() - new Date(new Date().getFullYear(), 0, 0)) / 201 | 1000 / 202 | 60 / 203 | 60 / 204 | 24 205 | ); 206 | }); 207 | }, 208 | dateFns: () => { 209 | performanceTest('DateFns', () => { 210 | getDayOfYear(new Date()); 211 | setDayOfYear(new Date(), 256); 212 | }); 213 | }, 214 | luxon: () => { 215 | performanceTest('Luxon', () => { 216 | DateTime.local().ordinal; 217 | DateTime.local().set({ ordinal: 256 }); 218 | }); 219 | }, 220 | }; 221 | 222 | // runTests(DayOfYear); 223 | 224 | const WeekOfYear = { 225 | moment: () => { 226 | performanceTest('Moment', () => { 227 | moment().week(); 228 | moment().week(24); 229 | }); 230 | }, 231 | native: () => { 232 | performanceTest('Native', () => { 233 | const MILLISECONDS_IN_WEEK = 604800000; 234 | const firstDayOfWeek = 1; 235 | 236 | const t = new Date(); 237 | const s = new Date(t.getFullYear(), 0, 1); 238 | s.setDate(s.getDate() + ((firstDayOfWeek - s.getDay()) % 7)); 239 | Math.round((t - s) / MILLISECONDS_IN_WEEK) + 1; 240 | 241 | const d = new Date(); 242 | const f = new Date(d.getFullYear(), 0, 1); 243 | f.setDate(f.getDate() + ((firstDayOfWeek - f.getDay()) % 7)); 244 | d.setDate( 245 | d.getDate() - (Math.round((d - f) / MILLISECONDS_IN_WEEK) + 1 - 24) * 7 246 | ); 247 | }); 248 | }, 249 | dateFns: () => { 250 | performanceTest('DateFns', () => { 251 | getWeek(new Date()); 252 | setWeek(new Date(), 24); 253 | }); 254 | }, 255 | luxon: () => { 256 | performanceTest('Luxon', () => { 257 | DateTime.local().weekYear; 258 | DateTime.local().set({ weekYear: 24 }); 259 | }); 260 | }, 261 | }; 262 | 263 | // runTests(WeekOfYear); 264 | 265 | const DaysInMonth = { 266 | moment: () => { 267 | performanceTest('Moment', () => { 268 | moment('2012-02', 'YYYY-MM').daysInMonth(); 269 | }); 270 | }, 271 | native: () => { 272 | performanceTest('Native', () => { 273 | new Date(2012, 2, 0).getDate(); 274 | }); 275 | }, 276 | dateFns: () => { 277 | performanceTest('DateFns', () => { 278 | getDaysInMonth(new Date(2012, 2)); 279 | }); 280 | }, 281 | dayJs: () => { 282 | performanceTest('DayJs', () => { 283 | dayjs('2012-02').daysInMonth(); 284 | }); 285 | }, 286 | luxon: () => { 287 | performanceTest('Luxon', () => { 288 | DateTime.local(2012, 2).day; 289 | }); 290 | }, 291 | }; 292 | 293 | // runTests(DaysInMonth); 294 | 295 | const WeeksInYear = { 296 | moment: () => { 297 | performanceTest('Moment', () => { 298 | moment().isoWeeksInYear(); 299 | }); 300 | }, 301 | dateFns: () => { 302 | performanceTest('DateFns', () => { 303 | getISOWeeksInYear(new Date()); 304 | }); 305 | }, 306 | luxon: () => { 307 | performanceTest('Luxon', () => { 308 | DateTime.local().weeksInWeekYear; 309 | }); 310 | }, 311 | }; 312 | 313 | // runTests(WeeksInYear); 314 | 315 | const MaximumOfGivenDates = { 316 | moment: () => { 317 | performanceTest('Moment', () => { 318 | moment.max(array.map(a => moment(a))); 319 | }); 320 | }, 321 | native: () => { 322 | performanceTest('Native', () => { 323 | new Date(Math.max.apply(null, array)).toISOString(); 324 | }); 325 | }, 326 | dateFns: () => { 327 | performanceTest('DateFns', () => { 328 | max(array); 329 | }); 330 | }, 331 | luxon: () => { 332 | performanceTest('Luxon', () => { 333 | const dates = array.map(a => DateTime.fromJSDate(a)); 334 | DateTime.max(...dates); 335 | }); 336 | }, 337 | }; 338 | 339 | // runTests(MaximumOfGivenDates); 340 | 341 | const MinimumOfGivenDates = { 342 | moment: () => { 343 | performanceTest('Moment', () => { 344 | moment.min(array.map(a => moment(a))); 345 | }); 346 | }, 347 | native: () => { 348 | performanceTest('Native', () => { 349 | new Date(Math.min.apply(null, array)).toISOString(); 350 | }); 351 | }, 352 | dateFns: () => { 353 | performanceTest('DateFns', () => { 354 | min(array); 355 | }); 356 | }, 357 | luxon: () => { 358 | performanceTest('Luxon', () => { 359 | const dates = array.map(a => DateTime.fromJSDate(a)); 360 | DateTime.min(...dates); 361 | }); 362 | }, 363 | }; 364 | 365 | // runTests(MinimumOfGivenDates); 366 | 367 | const Add = { 368 | moment: () => { 369 | performanceTest('Moment', () => { 370 | moment().add(7, 'days'); 371 | }); 372 | }, 373 | native: () => { 374 | performanceTest('Native', () => { 375 | const now = new Date(); 376 | now.setDate(now.getDate() + 7); 377 | }); 378 | }, 379 | dateFns: () => { 380 | performanceTest('DateFns', () => { 381 | addDays(new Date(), 7); 382 | }); 383 | }, 384 | dayJs: () => { 385 | performanceTest('DayJs', () => { 386 | dayjs().add(7, 'day'); 387 | }); 388 | }, 389 | luxon: () => { 390 | performanceTest('Luxon', () => { 391 | DateTime.local().plus({ day: 7 }); 392 | }); 393 | }, 394 | }; 395 | 396 | // runTests(Add); 397 | 398 | const Subtract = { 399 | moment: () => { 400 | performanceTest('Moment', () => { 401 | moment().subtract(7, 'days'); 402 | }); 403 | }, 404 | native: () => { 405 | performanceTest('Native', () => { 406 | new Date(new Date().getTime() - 1000 * 60 * 60 * 24 * 7); 407 | }); 408 | }, 409 | dateFns: () => { 410 | performanceTest('DateFns', () => { 411 | subDays(new Date(), 7); 412 | }); 413 | }, 414 | dayJs: () => { 415 | performanceTest('DayJs', () => { 416 | dayjs().subtract(7, 'day'); 417 | }); 418 | }, 419 | luxon: () => { 420 | performanceTest('Luxon', () => { 421 | DateTime.local().minus({ day: 7 }); 422 | }); 423 | }, 424 | }; 425 | 426 | // runTests(Subtract); 427 | 428 | const StartOfTime = { 429 | moment: () => { 430 | performanceTest('Moment', () => { 431 | moment().startOf('month'); 432 | }); 433 | }, 434 | dateFns: () => { 435 | performanceTest('DateFns', () => { 436 | startOfMonth(new Date()); 437 | }); 438 | }, 439 | dayJs: () => { 440 | performanceTest('DayJs', () => { 441 | dayjs().startOf('month'); 442 | }); 443 | }, 444 | luxon: () => { 445 | performanceTest('Luxon', () => { 446 | DateTime.local().startOf('month'); 447 | }); 448 | }, 449 | }; 450 | 451 | // runTests(StartOfTime); 452 | 453 | const EndOfTime = { 454 | moment: () => { 455 | performanceTest('Moment', () => { 456 | moment().endOf('day'); 457 | }); 458 | }, 459 | native: () => { 460 | performanceTest('Native', () => { 461 | new Date().setHours(23, 59, 59, 999); 462 | }); 463 | }, 464 | dateFns: () => { 465 | performanceTest('DateFns', () => { 466 | endOfDay(new Date()); 467 | }); 468 | }, 469 | dayJs: () => { 470 | performanceTest('DayJs', () => { 471 | dayjs().endOf('day'); 472 | }); 473 | }, 474 | luxon: () => { 475 | performanceTest('Luxon', () => { 476 | DateTime.local().endOf('day'); 477 | }); 478 | }, 479 | }; 480 | 481 | // runTests(EndOfTime); 482 | -------------------------------------------------------------------------------- /__tests__/index.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | const { DateTime, Interval } = require('luxon'); 3 | const date = require('date-fns'); 4 | const fr = require('date-fns/locale/fr'); 5 | const dayjs = require('dayjs'); 6 | const relativeTime = require('dayjs/plugin/relativeTime'); // load on demand 7 | const weekOfYear = require('dayjs/plugin/weekOfYear'); // load on demand 8 | const isBetween = require('dayjs/plugin/isBetween'); // load on demand 9 | const isLeapYear = require('dayjs/plugin/isLeapYear'); // load on demand 10 | dayjs.extend(relativeTime); 11 | dayjs.extend(weekOfYear); 12 | dayjs.extend(isBetween); 13 | dayjs.extend(isLeapYear); 14 | 15 | const time = 1536484369695; 16 | 17 | describe('Parse', () => { 18 | it('String + Date Format', () => { 19 | const m = moment('12-25-1995', 'MM-DD-YYYY'); 20 | 21 | const [, mm, dd, yyyy] = /^(\d{2})-(\d{2})-(\d{4})$/.exec('12-25-1995'); 22 | const n = new Date(`${mm}, ${dd} ${yyyy}`); 23 | expect(m.valueOf()).toBe(n.getTime()); 24 | 25 | const d = date.parse('12-25-1995', 'MM-dd-yyyy', new Date()); 26 | expect(m.valueOf()).toBe(d.getTime()); 27 | 28 | const day = dayjs('12-25-1995'); 29 | expect(m.valueOf()).toBe(day.valueOf()); 30 | 31 | const luxon = DateTime.fromFormat('12-25-1995', 'MM-dd-yyyy'); 32 | expect(m.valueOf()).toBe(luxon.ts); 33 | }); 34 | it('String + Time Format', () => { 35 | const m = moment('2010-10-20 4:30', 'YYYY-MM-DD HH:mm'); 36 | 37 | const [ 38 | , 39 | yyyy, 40 | mm, 41 | dd, 42 | hh, 43 | mi, 44 | ] = /^(\d{4})-(\d{2})-(\d{2})\s(\d{1,2}):(\d{2})$/.exec('2010-10-20 4:30'); 45 | const n = new Date(`${yyyy}-${mm}-${dd}T${('0' + hh).slice(-2)}:${mi}:00`); 46 | expect(m.valueOf()).toBe(n.getTime()); 47 | 48 | const d = date.parse('2010-10-20 4:30', 'yyyy-MM-dd H:mm', new Date()); 49 | expect(m.valueOf()).toBe(d.getTime()); 50 | 51 | const luxon = DateTime.fromFormat('2010-10-20 4:30', 'yyyy-MM-dd H:mm'); 52 | expect(m.valueOf()).toBe(luxon.ts); 53 | }); 54 | it('String + Format + locale', () => { 55 | const m = moment('2012 mars', 'YYYY MMM', 'fr'); 56 | 57 | const d = date.parse('2012 mars', 'yyyy MMMM', new Date(), { locale: fr }); 58 | expect(m.valueOf()).toBe(d.getTime()); 59 | }); 60 | }); 61 | 62 | describe('Get + Set', () => { 63 | it('get Second', () => { 64 | const m = moment(time).seconds(); 65 | 66 | const n = new Date(time).getSeconds(); 67 | expect(m).toBe(n); 68 | 69 | const d = date.getSeconds(new Date(time)); 70 | expect(m).toBe(d); 71 | 72 | const day = dayjs(time).second(); 73 | expect(m).toBe(day); 74 | 75 | const luxon = DateTime.fromMillis(time).second; 76 | expect(m).toBe(luxon); 77 | }); 78 | it('set Second', () => { 79 | const m = moment(time) 80 | .seconds(30) 81 | .valueOf(); 82 | const n = new Date(time).setSeconds(30); 83 | expect(m).toBe(n); 84 | 85 | const d = date.setSeconds(new Date(time), 30).getTime(); 86 | expect(m).toBe(d); 87 | 88 | const day = dayjs(time) 89 | .set('second', 30) 90 | .valueOf(); 91 | expect(m).toBe(day); 92 | 93 | const luxon = DateTime.fromMillis(time).set({ second: 30 }); 94 | expect(m).toBe(luxon.ts); 95 | }); 96 | 97 | it('get Hour', () => { 98 | const m = moment(time).hours(); 99 | const n = new Date(time).getHours(); 100 | expect(m).toBe(n); 101 | 102 | const d = date.getHours(new Date(time)); 103 | expect(m).toBe(d); 104 | 105 | const day = dayjs(time).hour(); 106 | expect(m).toBe(day); 107 | 108 | const luxon = DateTime.fromMillis(time).hour; 109 | expect(m).toBe(luxon); 110 | }); 111 | it('set Hour', () => { 112 | const m = moment(time) 113 | .hour(13) 114 | .valueOf(); 115 | const n = new Date(time).setHours(13); 116 | expect(m).toBe(n); 117 | 118 | const d = date.setHours(new Date(time), 13).getTime(); 119 | expect(m).toBe(d); 120 | 121 | const day = dayjs(time) 122 | .set('hour', 13) 123 | .valueOf(); 124 | expect(m).toBe(day); 125 | 126 | const luxon = DateTime.fromMillis(time).set({ hour: 13 }); 127 | expect(m).toBe(luxon.ts); 128 | }); 129 | 130 | it('get Date of Month', () => { 131 | const m = moment(time).date(); 132 | const n = new Date(time).getDate(); 133 | expect(m).toBe(n); 134 | 135 | const d = date.getDate(new Date(time)); 136 | expect(m).toBe(d); 137 | 138 | const day = dayjs(time).date(); 139 | expect(m).toBe(day); 140 | 141 | const luxon = DateTime.fromMillis(time).day; 142 | expect(m).toBe(luxon); 143 | }); 144 | 145 | it('set Date of Month', () => { 146 | const m = moment(time) 147 | .date(4) 148 | .valueOf(); 149 | const n = new Date(time).setDate(4); 150 | expect(m).toBe(n); 151 | 152 | const d = date.setDate(new Date(time), 4).getTime(); 153 | expect(m).toBe(d); 154 | 155 | const day = dayjs(time) 156 | .set('date', 4) 157 | .valueOf(); 158 | expect(m).toBe(day); 159 | 160 | const luxon = DateTime.fromMillis(time).set({ day: 4 }); 161 | expect(m).toBe(luxon.ts); 162 | }); 163 | 164 | it('get Day of Week', () => { 165 | const m = moment(time).day(); 166 | const n = new Date(time).getDay(); 167 | expect(m).toBe(n); 168 | 169 | const d = date.getDay(new Date(time)); 170 | expect(m).toBe(d); 171 | 172 | const day = dayjs(time).day(); 173 | expect(m).toBe(day); 174 | 175 | const luxon = DateTime.fromMillis(time).weekday; 176 | expect(m).toBe(luxon % 7); 177 | }); 178 | 179 | it('set Day of Week', () => { 180 | const m = moment(time) 181 | .day(-14) 182 | .valueOf(); 183 | const n = new Date(time).setDate(new Date(time).getDate() - 14); 184 | expect(m).toBe(n); 185 | 186 | const d = date.setDay(new Date(time), -14).getTime(); 187 | expect(m).toBe(d); 188 | 189 | const day = dayjs(time) 190 | .set('day', -14) 191 | .valueOf(); 192 | expect(m).toBe(day); 193 | 194 | const luxon = DateTime.fromMillis(time).minus({ day: 14 }); 195 | expect(m).toBe(luxon.ts); 196 | }); 197 | 198 | it('get Day of Year', () => { 199 | const m = moment(time).dayOfYear(); 200 | const n = Math.floor( 201 | (new Date(time) - new Date(new Date(time).getFullYear(), 0, 0)) / 202 | 1000 / 203 | 60 / 204 | 60 / 205 | 24 206 | ); 207 | expect(m).toBe(n); 208 | 209 | const d = date.getDayOfYear(new Date(time)); 210 | expect(m).toBe(d); 211 | 212 | const luxon = DateTime.fromMillis(time).ordinal; 213 | expect(m).toBe(luxon); 214 | }); 215 | 216 | it('set Day of Year', () => { 217 | const m = moment(time) 218 | .dayOfYear(256) 219 | .valueOf(); 220 | const d = date.setDayOfYear(new Date(time), 256).getTime(); 221 | expect(m).toBe(d); 222 | 223 | const luxon = DateTime.fromMillis(time).set({ ordinal: 256 }).ts; 224 | expect(m).toBe(luxon); 225 | }); 226 | 227 | it('get Week of Year', () => { 228 | const m = moment(time).week(); 229 | 230 | const MILLISECONDS_IN_WEEK = 604800000; 231 | const firstDayOfWeek = 1; // monday as the first day (0 = sunday) 232 | const t = new Date(time); 233 | const s = new Date(t.getFullYear(), 0, 1); 234 | s.setDate(s.getDate() + ((firstDayOfWeek - s.getDay()) % 7)); 235 | const n = Math.round((t - s) / MILLISECONDS_IN_WEEK) + 1; 236 | expect(m).toBe(n); 237 | 238 | const d = date.getWeek(new Date(time)); 239 | expect(m).toBe(d); 240 | 241 | const day = dayjs(time).week(); // plugin 242 | expect(m).toBe(day); 243 | 244 | const luxon = DateTime.fromMillis(time).weekNumber + 1; 245 | expect(m).toBe(luxon); 246 | }); 247 | 248 | it('set Week of Year', () => { 249 | const MILLISECONDS_IN_WEEK = 604800000; 250 | const firstDayOfWeek = 1; // monday as the first day (0 = sunday) 251 | 252 | const m = moment(time) 253 | .week(24) 254 | .valueOf(); 255 | const n = new Date(time); 256 | const s = new Date(n.getFullYear(), 0, 1); 257 | s.setDate(s.getDate() + ((firstDayOfWeek - s.getDay()) % 7)); 258 | const w = Math.round((n - s) / MILLISECONDS_IN_WEEK) + 1; 259 | n.setDate(n.getDate() - (w - 24) * 7); 260 | const d = date.setWeek(new Date(time), 24).getTime(); 261 | expect(m).toBe(d); 262 | expect(m).toBe(n.getTime()); 263 | expect(n.getTime()).toBe(d); 264 | 265 | const luxon = DateTime.fromMillis(time).set({ weekNumber: 23 }); 266 | expect(m).toBe(luxon.ts); 267 | }); 268 | 269 | it('Days in Month', () => { 270 | const m = moment('2012-02', 'YYYY-MM').daysInMonth(); 271 | const d = date.getDaysInMonth(new Date(2012, 1)); 272 | expect(m).toBe(d); 273 | 274 | const day = dayjs('2012-02').daysInMonth(); 275 | expect(m).toBe(day); 276 | 277 | const n = new Date(2012, 2, 0).getDate(); 278 | expect(m).toBe(n); 279 | 280 | const luxon = DateTime.local(2012, 2).daysInMonth; 281 | expect(m).toBe(luxon); 282 | }); 283 | 284 | it('get Weeks In Year', () => { 285 | const m = moment(time).isoWeeksInYear(); 286 | const d = date.getISOWeeksInYear(new Date(time)); 287 | expect(m).toBe(d); 288 | 289 | const luxon = DateTime.fromMillis(time).weeksInWeekYear; 290 | expect(m).toBe(luxon); 291 | }); 292 | 293 | it('Maximum of the given dates', () => { 294 | const array = [ 295 | new Date(2017, 4, 13), 296 | new Date(2018, 2, 12), 297 | new Date(2016, 0, 10), 298 | new Date(2016, 0, 9), 299 | ]; 300 | const m = moment.max(array.map(a => moment(a))); 301 | const d = date.max(array); 302 | expect(m.valueOf()).toBe(d.getTime()); 303 | expect(d).toEqual(new Date(2018, 2, 12)); 304 | 305 | const n = new Date(Math.max.apply(null, array)); 306 | expect(n).toEqual(new Date(2018, 2, 12)); 307 | 308 | const luxon = DateTime.max( 309 | ...array.map(a => DateTime.fromJSDate(a)) 310 | ).toJSDate(); 311 | expect(luxon).toEqual(new Date(2018, 2, 12)); 312 | }); 313 | 314 | it('Minimum of the given dates', () => { 315 | const array = [ 316 | new Date(2017, 4, 13), 317 | new Date(2018, 2, 12), 318 | new Date(2016, 0, 10), 319 | new Date(2016, 0, 9), 320 | ]; 321 | const n = new Date(Math.min.apply(null, array)); 322 | expect(n).toEqual(new Date(2016, 0, 9)); 323 | 324 | const m = moment.min(array.map(a => moment(a))); 325 | const d = date.min(array); 326 | expect(m.valueOf()).toBe(d.getTime()); 327 | expect(d).toEqual(new Date(2016, 0, 9)); 328 | 329 | const luxon = DateTime.min( 330 | ...array.map(a => DateTime.fromJSDate(a)) 331 | ).toJSDate(); 332 | expect(luxon).toEqual(new Date(2016, 0, 9)); 333 | }); 334 | }); 335 | 336 | describe('Manipulate', () => { 337 | it('Add', () => { 338 | const m = moment(time).add(7, 'days'); 339 | const d = date.addDays(new Date(time), 7); 340 | expect(m.valueOf()).toBe(d.getTime()); 341 | 342 | const n = new Date(time); 343 | n.setDate(n.getDate() + 7); 344 | expect(n.valueOf()).toBe(m.valueOf()); 345 | 346 | const day = dayjs(time).add(7, 'day'); 347 | expect(m.valueOf()).toBe(day.valueOf()); 348 | 349 | const luxon = DateTime.fromMillis(time).plus({ day: 7 }); 350 | expect(m.valueOf()).toBe(luxon.ts); 351 | }); 352 | 353 | it('Subtract', () => { 354 | const m = moment(time).subtract(7, 'days'); 355 | const n = new Date(new Date(time).getTime() - 1000 * 60 * 60 * 24 * 7); 356 | expect(n.valueOf()).toBe(m.valueOf()); 357 | 358 | const d = date.subDays(new Date(time), 7); 359 | expect(m.valueOf()).toBe(d.getTime()); 360 | 361 | const day = dayjs(time).subtract(7, 'day'); 362 | expect(m.valueOf()).toBe(day.valueOf()); 363 | 364 | const luxon = DateTime.fromMillis(time).minus({ day: 7 }); 365 | expect(m.valueOf()).toBe(luxon.ts); 366 | }); 367 | 368 | it('Start of Time', () => { 369 | const m = moment(time).startOf('month'); 370 | const d = date.startOfMonth(new Date(time)); 371 | expect(m.valueOf()).toBe(d.getTime()); 372 | 373 | const day = dayjs(time).startOf('month'); 374 | expect(m.valueOf()).toBe(day.valueOf()); 375 | 376 | const luxon = DateTime.fromMillis(time).startOf('month'); 377 | expect(m.valueOf()).toBe(luxon.ts); 378 | }); 379 | 380 | it('End of Time', () => { 381 | const m = moment(time).endOf('day'); 382 | const n = new Date(time).setHours(23, 59, 59, 999); 383 | expect(m.valueOf()).toBe(n); 384 | 385 | const d = date.endOfDay(new Date(time)); 386 | expect(m.valueOf()).toBe(d.getTime()); 387 | 388 | const day = dayjs(time).endOf('day'); 389 | expect(m.valueOf()).toBe(day.valueOf()); 390 | 391 | const luxon = DateTime.fromMillis(time).endOf('day'); 392 | expect(m.valueOf()).toBe(luxon.ts); 393 | }); 394 | }); 395 | 396 | describe('Display', () => { 397 | it('Format', () => { 398 | const m = moment(time).format('dddd, MMMM D YYYY, h:mm:ss A'); 399 | const d = date.format(new Date(time), 'eeee, MMMM d YYYY, h:mm:ss aa', { 400 | awareOfUnicodeTokens: true, 401 | }); 402 | const day = dayjs(time).format('dddd, MMMM D YYYY, h:mm:ss A'); 403 | const l = DateTime.fromMillis(time).toFormat( 404 | 'EEEE, MMMM d yyyy, h:mm:ss a' 405 | ); 406 | expect(m).toBe(d); 407 | expect(m).toBe(day); 408 | expect(m).toBe(l); 409 | 410 | const m2 = moment(time).format('ddd, hA'); 411 | const d2 = date.format(new Date(time), 'eee, ha'); 412 | const day2 = dayjs(time).format('ddd, hA'); 413 | const l2 = DateTime.fromMillis(time).toFormat('EEE, ha'); 414 | expect(m2).toBe(d2); 415 | expect(m2).toBe(day2); 416 | expect(m2).toBe(l2); 417 | }); 418 | 419 | it('Time from now', () => { 420 | const month3 = 1000 * 3600 * 24 * 30 * 3; // ms * hour * day * month * 3 421 | const timeDistance = new Date().getTime() - month3; 422 | 423 | moment.relativeTimeThreshold( 424 | 'd', 425 | new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate() 426 | ); 427 | const m = moment(timeDistance).fromNow(); 428 | const d = date.formatDistanceStrict(new Date(timeDistance), new Date(), { 429 | addSuffix: true, 430 | }); 431 | const day = dayjs(timeDistance).fromNow(); // plugin 432 | expect(m).toBe(d); 433 | expect(m).toBe(day); 434 | }); 435 | 436 | it('Time from X', () => { 437 | const m = moment([2007, 0, 27]).to(moment([2007, 0, 29])); 438 | const d = date.formatDistance(new Date(2007, 0, 27), new Date(2007, 0, 29)); 439 | const day = dayjs('2007-01-27').to(dayjs('2007-01-29')); 440 | expect(m).toContain(d); 441 | expect(m).toBe(day); 442 | }); 443 | 444 | it('Difference', () => { 445 | const m = moment([2007, 0, 27]).diff(moment([2007, 0, 29])); 446 | const n = new Date(2007, 0, 27) - new Date(2007, 0, 29); 447 | const d = date.differenceInMilliseconds( 448 | new Date(2007, 0, 27), 449 | new Date(2007, 0, 29) 450 | ); 451 | const day = dayjs('2007-01-27').diff(dayjs('2007-01-29'), 'milliseconds'); 452 | const luxon = DateTime.local(2007, 1, 27).diff(DateTime.local(2007, 1, 29)) 453 | .milliseconds; 454 | expect(m).toBe(d); 455 | expect(m).toBe(day); 456 | expect(n).toBe(d); 457 | expect(n).toBe(m); 458 | expect(n).toBe(day); 459 | expect(n).toBe(luxon); 460 | 461 | const m2 = moment([2007, 0, 27]).diff(moment([2007, 0, 29]), 'days'); 462 | const n2 = Math.ceil( 463 | (new Date(2007, 0, 27) - new Date(2007, 0, 29)) / 1000 / 60 / 60 / 24 464 | ); 465 | const d2 = date.differenceInDays( 466 | new Date(2007, 0, 27), 467 | new Date(2007, 0, 29) 468 | ); 469 | const day2 = dayjs('2007-01-27').diff(dayjs('2007-01-29'), 'days'); 470 | const luxon2 = DateTime.local(2007, 1, 27).diff( 471 | DateTime.local(2007, 1, 29), 472 | 'days' 473 | ).days; 474 | 475 | expect(m2).toBe(d2); 476 | expect(m2).toBe(day2); 477 | expect(n2).toBe(m2); 478 | expect(n2).toBe(d2); 479 | expect(n2).toBe(day2); 480 | expect(n2).toBe(luxon2); 481 | }); 482 | }); 483 | 484 | describe('Query', () => { 485 | it('Is Before', () => { 486 | const m = moment('2010-10-20').isBefore('2010-10-21'); 487 | const n = new Date(2010, 10, 20) < new Date(2010, 10, 21); 488 | const d = date.isBefore(new Date(2010, 9, 20), new Date(2010, 9, 21)); 489 | const day = dayjs('2010-10-20').isBefore('2010-10-21'); //plugin 490 | const luxon = 491 | DateTime.fromISO('2010-10-20') < DateTime.fromISO('2010-10-21'); 492 | expect(m).toBeTruthy(); 493 | expect(d).toBeTruthy(); 494 | expect(day).toBeTruthy(); 495 | expect(n).toBeTruthy(); 496 | expect(luxon).toBeTruthy(); 497 | }); 498 | 499 | it('Is Same', () => { 500 | expect(moment('2010-10-20').isSame('2010-10-21')).toBeFalsy(); 501 | expect(new Date(2010, 9, 20) === new Date(2010, 9, 21)).toBeFalsy(); 502 | expect( 503 | date.isSameDay(new Date(2010, 9, 20), new Date(2010, 9, 21)) 504 | ).toBeFalsy(); 505 | expect(dayjs('2010-10-20').isSame('2010-10-21')).toBeFalsy(); 506 | expect( 507 | +DateTime.fromISO('2010-10-20') === +DateTime.fromISO('2010-10-21') 508 | ).toBeFalsy(); 509 | 510 | expect(moment('2010-10-20').isSame('2010-10-21', 'month')).toBeTruthy(); 511 | expect( 512 | new Date(2010, 9, 20).valueOf() === new Date(2010, 9, 20).valueOf() 513 | ).toBeTruthy(); 514 | expect( 515 | new Date(2010, 9, 20).getTime() === new Date(2010, 9, 20).getTime() 516 | ).toBeTruthy(); 517 | expect( 518 | new Date(2010, 9, 20).valueOf() === new Date(2010, 9, 20).getTime() 519 | ).toBeTruthy(); 520 | expect( 521 | new Date(2010, 9, 20).toDateString().substring(4, 7) === 522 | new Date(2010, 9, 21).toDateString().substring(4, 7) 523 | ).toBeTruthy(); 524 | expect( 525 | date.isSameMonth(new Date(2010, 9, 20), new Date(2010, 9, 21)) 526 | ).toBeTruthy(); 527 | expect( 528 | DateTime.fromISO('2010-10-20').hasSame( 529 | DateTime.fromISO('2010-10-21'), 530 | 'month' 531 | ) 532 | ).toBeTruthy(); 533 | }); 534 | 535 | it('Is After', () => { 536 | const m = moment('2010-10-20').isAfter('2010-10-19'); 537 | const n = new Date(2010, 10, 20) > new Date(2010, 10, 19); 538 | const d = date.isAfter(new Date(2010, 9, 20), new Date(2010, 9, 19)); 539 | const day = dayjs('2010-10-20').isAfter('2010-10-19'); 540 | const luxon = 541 | DateTime.fromISO('2010-10-20') > DateTime.fromISO('2010-10-19'); 542 | expect(m).toBeTruthy(); 543 | expect(n).toBeTruthy(); 544 | expect(d).toBeTruthy(); 545 | expect(day).toBeTruthy(); 546 | expect(luxon).toBeTruthy(); 547 | }); 548 | 549 | it('Is Between', () => { 550 | const m = moment('2010-10-20').isBetween('2010-10-19', '2010-10-25'); 551 | const d = date.isWithinInterval(new Date(2010, 9, 20), { 552 | start: new Date(2010, 9, 19), 553 | end: new Date(2010, 9, 25), 554 | }); 555 | const day = dayjs('2010-10-20').isBetween('2010-10-19', '2010-10-25'); //plugin 556 | const luxon = Interval.fromDateTimes( 557 | DateTime.fromISO('2010-10-19'), 558 | DateTime.fromISO('2010-10-25') 559 | ).contains(DateTime.fromISO('2010-10-20')); 560 | 561 | expect(m).toBeTruthy(); 562 | expect(d).toBeTruthy(); 563 | expect(day).toBeTruthy(); 564 | expect(luxon).toBeTruthy(); 565 | }); 566 | 567 | it('Is Leap Year', () => { 568 | expect(moment([2000]).isLeapYear()).toBeTruthy(); 569 | expect(moment([2001]).isLeapYear()).toBeFalsy(); 570 | expect(new Date(2000, 1, 29).getDate() === 29).toBeTruthy(); 571 | expect(date.isLeapYear(new Date(2000, 0, 1))).toBeTruthy(); 572 | expect(date.isLeapYear(new Date(2001, 0, 1))).toBeFalsy(); 573 | expect(dayjs('2000-01-01').isLeapYear()).toBeTruthy(); 574 | expect(dayjs('2001-01-01').isLeapYear()).toBeFalsy(); 575 | expect(DateTime.local(2000).isInLeapYear).toBeTruthy(); 576 | expect(DateTime.local(2001).isInLeapYear).toBeFalsy(); 577 | }); 578 | 579 | it('Is a Date', () => { 580 | expect(moment.isDate(new Date())).toBeTruthy(); 581 | expect(new Date() instanceof Date).toBeTruthy(); 582 | expect(date.isDate(new Date())).toBeTruthy(); 583 | expect(dayjs.isDayjs(dayjs())).toBeTruthy(); 584 | expect(DateTime.local().isValid).toBeTruthy(); 585 | }); 586 | }); 587 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # You don't (may not) need Moment.js 2 | 3 | [Moment.js](https://momentjs.com/) is a fantastic time & date library with lots of great features and utilities. However, if you are working on a performance sensitive web application, it might cause a huge performance overhead because of its complex APIs and large bundle size. 4 | 5 | ![Large bundle size](./screenshot.png) 6 | 7 | Problems with Moment.js: 8 | 9 | - It is highly based on OOP APIs, which makes it fail to work with tree-shaking, thus leading to a huge bundle size and performance issues. 10 | - It is mutable due to OOP APIs and non-pure functions, which cause bugs: 11 | https://github.com/moment/moment/blob/develop/src/test/moment/add_subtract.js#L244-L286 12 | 13 | If you are not using timezone but only a few simple functions from moment.js, this might bloat your app, and therefore is considered overkill. [dayjs](https://github.com/iamkun/dayjs) has a smaller core and has very similar APIs so it makes it very easy to migrate. [date-fns](https://github.com/date-fns/date-fns) enables [tree-shaking and other benefits](https://github.com/date-fns/date-fns/issues/275#issuecomment-264934189) so that it works great with React, Sinon.js, and webpack, etc. See https://github.com/moment/moment/issues/2373 for more ideas on why and how people switch from moment.js to other solutions. 14 | 15 | ## Brief Comparison 16 | 17 | | Name | Size(gzip) | Tree-shaking | Popularity(stars) | Methods richness | Pattern | Timezone Support | Locale | 18 | | ---------------------------------------- | --------------------------------- | ------------ | ----------------- | ---------------- | ---------- | --------------------- | ------ | 19 | | [Moment.js](https://momentjs.com/) | 329K(69.6K) | No | 39k | High | OO | Good(moment-timezone) | 123 | 20 | | [Luxon](https://moment.github.io/luxon/) | 59.9K(17.2K) | No | 7k | High | OO | Good(Intl) | - | 21 | | [date-fns](https://date-fns.org) | 78.4k(13.4k) without tree-shaking | Yes | 15k | High | Functional | Not yet | 50 | 22 | | [dayjs](https://github.com/iamkun/dayjs) | 6.5k(2.6k) without plugins | No | 17k | Medium | OO | Not yet | 39 | 23 | 24 | ## Voice of Developers 25 | 26 | > [Removed moment.js to replace with date-fns - build output reduced by 40%](https://github.com/oysterprotocol/webnode/pull/116) 27 | 28 | > —Jared Farago from [webnode](https://github.com/oysterprotocol/webnode/pull/116) project. 29 | 30 | > [Make use of native JavaScript object and array utilities before going big. Good library if you’re looking to replace Moment.js for one reason or another. Immutable too.](https://twitter.com/dan_abramov/status/805030922785525760) 31 | 32 | > —Dan Abramov, Author of [Redux](https://github.com/reduxjs/redux) and co-author of [Create React App](https://github.com/facebook/create-react-app). Building tools for humans. 33 | 34 | > [I strongly recommend using date-fns over Moment.js, it's has a nicer API and you can include only parts you need!](https://twitter.com/silvenon/status/804946772690923520) 35 | 36 | > —Matija Marohnić, a design-savvy frontend developer from Croatia. 37 | 38 | > [Just yesterday changed momentjs to this lib in out project. Cut the size of our js bundle almost in half 😱](https://twitter.com/gribnoysup/status/805061630752997377) 39 | 40 | > —Sergey Petushkov, a javaScript developer from Moscow, Russia • Currently in Berlin, Germany. 41 | 42 | ## ESLint Plugin 43 | 44 |

45 | 46 | NPM Version 48 | 49 | 50 | Downloads 52 | 53 | 54 | Build Status 56 | 57 | 58 | Coverage Status 60 | 61 | 62 | Dependency Status 64 | 65 |

66 | 67 | If you're using [ESLint](http://eslint.org/), you can install a 68 | [plugin](http://eslint.org/docs/user-guide/configuring#using-the-configuration-from-a-plugin) that 69 | will help you identify places in your codebase where you don't (may not) need Moment.js. 70 | 71 | Install the plugin... 72 | 73 | ```sh 74 | npm install --save-dev eslint-plugin-you-dont-need-momentjs 75 | ``` 76 | 77 | ...then update your config 78 | 79 | ```js 80 | "extends" : ["plugin:you-dont-need-momentjs/recommended"], 81 | ``` 82 | 83 | ## Quick Links 84 | 85 | **[Parse](#parse)** 86 | 87 | 1. [String + Date Format](#string--date-format) 88 | 1. [String + Time Format](#string--time-format) 89 | 1. [String + Format + locale](#string--format--locale) 90 | 91 | **[Get + Set](#get-set)** 92 | 93 | 1. [Millisecond/Second/Minute/Hour](#millisecond--second--minute--hour) 94 | 1. [Date of Month](#date-of-month) 95 | 1. [Day of Week](#day-of-week) 96 | 1. [Day of Year](#day-of-year) 97 | 1. [Week of Year](#week-of-year) 98 | 1. [Days in Month](#days-in-month) 99 | 1. [Weeks in Year](#weeks-in-year) 100 | 1. [Maximum of the given dates](#maximum-of-the-given-dates) 101 | 1. [Minimum of the given dates](#minimum-of-the-given-dates) 102 | 103 | **[Manipulate](#manipulate)** 104 | 105 | 1. [Add](#add) 106 | 1. [Subtract](#subtract) 107 | 1. [Start of Time](#start-of-time) 108 | 1. [End of Time](#end-of-time) 109 | 110 | **[Display](#display)** 111 | 112 | 1. [Format](#format) 113 | 1. [Time from now](#time-from-now) 114 | 1. [Time from X](#time-from-x) 115 | 1. [Difference](#difference) 116 | 117 | **[Query](#query)** 118 | 119 | 1. [Is Before](#is-before) 120 | 1. [Is Same](#is-same) 121 | 1. [Is After](#is-after) 122 | 1. [Is Between](#is-between) 123 | 1. [Is Leap Year](#is-leap-year) 124 | 1. [Is a Date](#is-a-date) 125 | 126 | ⚠️ _Note that the provided examples of date-fns are for [v2](https://date-fns.org/v2.0.0-alpha.16/docs/Getting-Started) which is in pre-release right now. [See v1 docs](https://date-fns.org/docs/Getting-Started) for the current release._ 127 | 128 | ## Parse 129 | 130 | ### String + Date Format 131 | 132 | Return the date parsed from date string using the given format string. 133 | 134 | ```js 135 | // Moment.js 136 | moment('12-25-1995', 'MM-DD-YYYY'); 137 | // => "1995-12-24T13:00:00.000Z" 138 | 139 | // Native 140 | const datePattern = /^(\d{2})-(\d{2})-(\d{4})$/; 141 | const [, month, day, year] = datePattern.exec('12-25-1995'); 142 | new Date(`${month}, ${day} ${year}`); 143 | // => "1995-12-24T13:00:00.000Z" 144 | 145 | // date-fns 146 | import parse from 'date-fns/parse'; 147 | parse('12-25-1995', 'MM-dd-yyyy', new Date()); 148 | // => "1995-12-24T13:00:00.000Z" 149 | 150 | // dayjs 151 | dayjs('12-25-1995'); 152 | // => "1995-12-24T13:00:00.000Z" 153 | 154 | // luxon 155 | DateTime.fromFormat('12-25-1995', 'MM-dd-yyyy').toJSDate(); 156 | // => "1995-12-24T13:00:00.000Z" 157 | ``` 158 | 159 | **[⬆ back to top](#quick-links)** 160 | 161 | ### String + Time Format 162 | 163 | Return the date parsed from time string using the given format string. 164 | 165 | ```js 166 | // Moment.js 167 | moment('2010-10-20 4:30', 'YYYY-MM-DD HH:mm'); 168 | // => "2010-10-19T17:30:00.000Z" 169 | 170 | // Native 171 | const datePattern = /^(\d{4})-(\d{2})-(\d{2})\s(\d{1,2}):(\d{2})$/; 172 | const [, year, month, day, rawHour, min] = datePattern.exec('2010-10-20 4:30'); 173 | new Date(`${year}-${month}-${day}T${('0' + rawHour).slice(-2)}:${min}:00`); 174 | // => "2010-10-19T17:30:00.000Z" 175 | 176 | // date-fns 177 | import parse from 'date-fns/parse'; 178 | parse('2010-10-20 4:30', 'yyyy-MM-dd H:mm', new Date()); 179 | // => "2010-10-19T17:30:00.000Z" 180 | 181 | // dayjs ⚠️ requires customParseFormat plugin 182 | import customParseFormat from 'dayjs/plugin/customParseFormat' 183 | dayjs.extend(customParseFormat) 184 | dayjs('2010-10-20 4:30', 'YYYY-MM-DD HH:mm'); 185 | // => "2010-10-19T17:30:00.000Z" 186 | 187 | // luxon 188 | DateTime.fromFormat('2010-10-20 4:30', 'yyyy-MM-dd H:mm').toJSDate(); 189 | // => "2010-10-19T17:30:00.000Z" 190 | ``` 191 | 192 | **[⬆ back to top](#quick-links)** 193 | 194 | ### String + Format + locale 195 | 196 | Return the date parsed from string using the given format string and locale. 197 | 198 | ```js 199 | // Moment.js 200 | moment('2012 mars', 'YYYY MMM', 'fr'); 201 | // => "2012-02-29T13:00:00.000Z" 202 | 203 | // date-fns 204 | import parse from 'date-fns/parse'; 205 | import fr from 'date-fns/locale/fr'; 206 | parse('2012 mars', 'yyyy MMMM', new Date(), { locale: fr }); 207 | // => "2012-02-29T13:00:00.000Z" 208 | 209 | // dayjs ❌ does not support custom format parse 210 | 211 | // Luxon ❌ does not support Locale for node unless https://moment.github.io/luxon/docs/manual/install.html#node 212 | DateTime.fromFormat('2012 mars', 'yyyy MMMM', { locale: 'fr' }); 213 | // => "2012-02-29T13:00:00.000Z" 214 | ``` 215 | 216 | **[⬆ back to top](#quick-links)** 217 | 218 | ## Get + Set 219 | 220 | ### Millisecond / Second / Minute / Hour 221 | 222 | Get the `Millisecond/Second/Minute/Hour` of the given date. 223 | 224 | ```js 225 | // Moment.js 226 | moment().seconds(); 227 | // => 49 228 | moment().hours(); 229 | // => 19 230 | 231 | // Native 232 | new Date().getSeconds(); 233 | // => 49 234 | new Date().getHours(); 235 | // => 19 236 | 237 | // date-fns 238 | import getSeconds from 'date-fns/getSeconds'; 239 | import getHours from 'date-fns/getHours'; 240 | getSeconds(new Date()); 241 | // => 49 242 | getHours(new Date()); 243 | // => 19 244 | 245 | // dayjs 246 | dayjs().second(); 247 | // => 49 248 | dayjs().hour(); 249 | // => 19 250 | 251 | // Luxon 252 | DateTime.local().second; 253 | // => 49 254 | DateTime.local().hour; 255 | // => 19 256 | ``` 257 | 258 | ### Performance tests 259 | 260 | | Library | Time | 261 | | ------- | ---------- | 262 | | Moment | 1432.647ms | 263 | | Native | 367.769ms | 264 | | DateFns | 608.878ms | 265 | | DayJs | 502.302ms | 266 | | Luxon | 1296.507ms | 267 | 268 | Set the `Millisecond/Second/Minute/Hour` of the given date. 269 | 270 | ```js 271 | // Moment.js 272 | moment().seconds(30); 273 | // => "2018-09-09T09:12:30.695Z" 274 | moment().hours(13); 275 | // => "2018-09-09T03:12:49.695Z" 276 | 277 | // Native 278 | new Date(new Date().setSeconds(30)); 279 | // => "2018-09-09T09:12:30.695Z" 280 | new Date(new Date().setHours(13)); 281 | // => "2018-09-09T03:12:49.695Z" 282 | 283 | // date-fns 284 | import setSeconds from 'date-fns/setSeconds'; 285 | import setHours from 'date-fns/setHours'; 286 | setSeconds(new Date(), 30); 287 | // => "2018-09-09T09:12:30.695Z" 288 | setHours(new Date(), 13); 289 | // => "2018-09-09T03:12:49.695Z" 290 | 291 | // dayjs 292 | dayjs().set('second', 30); 293 | // => "2018-09-09T09:12:30.695Z" 294 | dayjs().set('hour', 13); 295 | // => "2018-09-09T03:12:49.695Z" 296 | 297 | // luxon 298 | DateTime.utc() 299 | .set({ second: 30 }) 300 | .toJSDate(); 301 | // => "2018-09-09T09:12:30.695Z" 302 | DateTime.utc() 303 | .set({ hour: 13 }) 304 | .toJSDate(); 305 | // => "2018-09-09T03:12:49.695Z" 306 | ``` 307 | 308 | ### Performance tests 309 | 310 | | Library | Time | 311 | | ------- | ---------- | 312 | | Moment | 1690.007ms | 313 | | Native | 636.242 | 314 | | DateFns | 696.768ms | 315 | | DayJs | 1891.058ms | 316 | | Luxon | 8630.314ms | 317 | 318 | **[⬆ back to top](#quick-links)** 319 | 320 | ### Date of Month 321 | 322 | Gets or sets the day of the month. 323 | 324 | ```js 325 | // Moment.js 326 | moment().date(); 327 | // => 9 328 | moment().date(4); 329 | // => "2018-09-04T09:12:49.695Z" 330 | 331 | // Native 332 | new Date().getDate(); 333 | // => 9 334 | new Date().setDate(4); 335 | // => "2018-09-04T09:12:49.695Z" 336 | 337 | // date-fns 338 | import getDate from 'date-fns/getDate'; 339 | import setDate from 'date-fns/setDate'; 340 | getDate(new Date()); 341 | // => 9 342 | setDate(new Date(), 4); 343 | // => "2018-09-04T09:12:49.695Z" 344 | 345 | // dayjs 346 | dayjs().date(); 347 | // => 9 348 | dayjs().set('date', 4); 349 | // => "2018-09-04T09:12:49.695Z" 350 | 351 | // luxon 352 | DateTime.utc().day; 353 | // => 9 354 | DateTime.utc() 355 | .set({ day: 4 }) 356 | .toString(); 357 | // => "2018-09-04T09:12:49.695Z" 358 | ``` 359 | 360 | ### Performance tests 361 | 362 | | Library | Time | 363 | | ------- | ---------- | 364 | | Moment | 1349.161ms | 365 | | Native | 388.734ms | 366 | | DateFns | 602.132ms | 367 | | DayJs | 1088.253ms | 368 | | Luxon | 4728.460ms | 369 | 370 | **[⬆ back to top](#quick-links)** 371 | 372 | ### Day of Week 373 | 374 | Gets or sets the day of the week. 375 | 376 | ```js 377 | // Moment.js 378 | moment().day(); 379 | // => 0 (Sunday) 380 | moment().day(-14); 381 | // => "2018-08-26T09:12:49.695Z" 382 | 383 | // Native 384 | new Date().getDay(); 385 | // => 0 (Sunday) 386 | new Date().setDate(new Date().getDate() - 14); 387 | // => "2018-08-26T09:12:49.695Z" 388 | 389 | // date-fns 390 | import getDay from 'date-fns/getDay'; 391 | import setDay from 'date-fns/setDay'; 392 | getDay(new Date()); 393 | // => 0 (Sunday) 394 | setDay(new Date(), -14); 395 | // => "2018-08-26T09:12:49.695Z" 396 | 397 | // dayjs 398 | dayjs().day(); 399 | // => 0 (Sunday) 400 | dayjs().set('day', -14); 401 | // => "2018-08-26T09:12:49.695Z" 402 | 403 | // Luxon 404 | DateTime.local().weekday; 405 | // => 7 (Sunday) 406 | DateTime.local() 407 | .minus({ day: 14 }) 408 | .toJSDate(); 409 | // => "2018-08-26T09:12:49.695Z" 410 | ``` 411 | 412 | | Library | Time | 413 | | ------- | ---------- | 414 | | Moment | 1869.609ms | 415 | | Native | 565.715ms | 416 | | DateFns | 815.355ms | 417 | | DayJs | 1087.407ms | 418 | | Luxon | 7093.800ms | 419 | 420 | **[⬆ back to top](#quick-links)** 421 | 422 | ### Day of Year 423 | 424 | Gets or sets the day of the year. 425 | 426 | ```js 427 | // Moment.js 428 | moment().dayOfYear(); 429 | // => 252 430 | moment().dayOfYear(256); 431 | // => "2018-09-13T09:12:49.695Z" 432 | 433 | // Native 434 | Math.floor( 435 | (new Date() - new Date(new Date().getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24 436 | ); 437 | // => 252 438 | 439 | // date-fns 440 | import getDayOfYear from 'date-fns/getDayOfYear'; 441 | import setDayOfYear from 'date-fns/setDayOfYear'; 442 | getDayOfYear(new Date()); 443 | // => 252 444 | setDayOfYear(new Date(), 256); 445 | // => "2018-09-13T09:12:49.695Z" 446 | 447 | // dayjs ❌ does not support day of year 448 | 449 | // Luxon 450 | DateTime.local().ordinal; 451 | // => 252 452 | DateTime.local() 453 | .set({ ordinal: 256 }) 454 | .toString(); 455 | // => "2018-09-13T09:12:49.695Z" 456 | ``` 457 | 458 | | Library | Time | 459 | | ------- | ---------- | 460 | | Moment | 9851.425ms | 461 | | Native | 575.954ms | 462 | | DateFns | 1976.969ms | 463 | | DayJs | - | 464 | | Luxon | 5242.336ms | 465 | 466 | **[⬆ back to top](#quick-links)** 467 | 468 | ### Week of Year 469 | 470 | Gets or sets the week of the year. 471 | 472 | ```js 473 | // Moment.js 474 | moment().week(); 475 | // => 37 476 | moment().week(24); 477 | // => "2018-06-10T09:12:49.695Z" 478 | 479 | // date-fns 480 | import getWeek from 'date-fns/getWeek'; 481 | import setWeek from 'date-fns/setWeek'; 482 | getWeek(new Date()); 483 | // => 37 484 | setWeek(new Date(), 24); 485 | // => "2018-06-10T09:12:49.695Z" 486 | 487 | // native getWeek 488 | const day = new Date(); 489 | const MILLISECONDS_IN_WEEK = 604800000; 490 | const firstDayOfWeek = 1; // monday as the first day (0 = sunday) 491 | const startOfYear = new Date(day.getFullYear(), 0, 1); 492 | startOfYear.setDate( 493 | startOfYear.getDate() + (firstDayOfWeek - (startOfYear.getDay() % 7)) 494 | ); 495 | const dayWeek = Math.round((day - startOfYear) / MILLISECONDS_IN_WEEK) + 1; 496 | // => 37 497 | 498 | // native setWeek 499 | const day = new Date(); 500 | const week = 24; 501 | const MILLISECONDS_IN_WEEK = 604800000; 502 | const firstDayOfWeek = 1; // monday as the first day (0 = sunday) 503 | const startOfYear = new Date(day.getFullYear(), 0, 1); 504 | startOfYear.setDate( 505 | startOfYear.getDate() + (firstDayOfWeek - (startOfYear.getDay() % 7)) 506 | ); 507 | const dayWeek = Math.round((day - startOfYear) / MILLISECONDS_IN_WEEK) + 1; 508 | day.setDate(day.getDate() - (dayWeek - week) * 7); 509 | day.toISOString(); 510 | // => "2018-06-10T09:12:49.794Z 511 | 512 | // dayjs ⚠️ requires weekOfYear plugin 513 | import weekOfYear from 'dayjs/plugin/weekOfYear'; 514 | dayjs.extend(weekOfYear); 515 | dayjs().week(); 516 | // => 37 517 | // dayjs ❌ does not support set week of year 518 | 519 | // Luxon 520 | DateTime.local().weekNumber; 521 | // => 37 522 | DateTime.local() 523 | .set({ weekNumber: 23 }) 524 | .toString(); 525 | // => "2018-06-10T09:12:49.794Z 526 | ``` 527 | 528 | | Library | Time | 529 | | ------- | ----------- | 530 | | Moment | 11997.187ms | 531 | | Native | 1467.277ms | 532 | | DateFns | 6070.445ms | 533 | | DayJs | - | 534 | | Luxon | 7944.387ms | 535 | 536 | **[⬆ back to top](#quick-links)** 537 | 538 | ### Days in Month 539 | 540 | Get the number of days in the current month. 541 | 542 | ```js 543 | // Moment.js 544 | moment('2012-02', 'YYYY-MM').daysInMonth(); 545 | // => 29 546 | 547 | // Native 548 | new Date(2012, 02, 0).getDate(); 549 | // => 29 550 | 551 | // date-fns 552 | import getDaysInMonth from 'date-fns/getDaysInMonth'; 553 | getDaysInMonth(new Date(2012, 1)); 554 | // => 29 555 | 556 | // dayjs 557 | dayjs('2012-02').daysInMonth(); 558 | // => 29 559 | 560 | // Luxon 561 | DateTime.local(2012, 2).daysInMonth; 562 | // => 29 563 | ``` 564 | 565 | | Library | Time | 566 | | ------- | ---------- | 567 | | Moment | 4836.243ms | 568 | | Native | 504.241ms | 569 | | DateFns | 1584.524ms | 570 | | DayJs | 2965.774ms | 571 | | Luxon | 1292.006ms | 572 | 573 | **[⬆ back to top](#quick-links)** 574 | 575 | ### Weeks in Year 576 | 577 | Gets the number of weeks in the current year, according to ISO weeks. 578 | 579 | ```js 580 | // Moment.js 581 | moment().isoWeeksInYear(); 582 | // => 52 583 | 584 | // date-fns 585 | import getISOWeeksInYear from 'date-fns/getISOWeeksInYear'; 586 | getISOWeeksInYear(new Date()); 587 | // => 52 588 | 589 | // dayjs ❌ does not support weeks in the year 590 | 591 | // Moment.js 592 | DateTime.local().weeksInWeekYear; 593 | // => 52 594 | ``` 595 | 596 | | Library | Time | 597 | | ------- | ---------- | 598 | | Moment | 983.551ms | 599 | | Native | - | 600 | | DateFns | 4751.236ms | 601 | | DayJs | - | 602 | | Luxon | 1743.877ms | 603 | 604 | **[⬆ back to top](#quick-links)** 605 | 606 | ### Maximum of the given dates 607 | 608 | Returns the maximum (most distant future) of the given date. 609 | 610 | ```js 611 | const array = [ 612 | new Date(2017, 4, 13), 613 | new Date(2018, 2, 12), 614 | new Date(2016, 0, 10), 615 | new Date(2016, 0, 9), 616 | ]; 617 | // Moment.js 618 | moment.max(array.map(a => moment(a))); 619 | // => "2018-03-11T13:00:00.000Z" 620 | 621 | // Native 622 | new Date(Math.max.apply(null, array)).toISOString(); 623 | // => "2018-03-11T13:00:00.000Z" 624 | 625 | // date-fns 626 | import max from 'date-fns/max'; 627 | max(array); 628 | // => "2018-03-11T13:00:00.000Z" 629 | 630 | // dayjs ❌ does not support the maximum of the given dates 631 | 632 | // Luxon 633 | DateTime.max(...array.map(a => DateTime.fromJSDate(a))).toJSDate(); 634 | // => "2018-03-11T13:00:00.000Z" 635 | ``` 636 | 637 | | Library | Time | 638 | | ------- | ---------- | 639 | | Moment | 1736.095ms | 640 | | Native | 1104.626ms | 641 | | DateFns | 966.238ms | 642 | | DayJs | - | 643 | | Luxon | 2703.421ms | 644 | 645 | **[⬆ back to top](#quick-links)** 646 | 647 | ### Minimum of the given dates 648 | 649 | Returns the minimum (most distant future) of the given date. 650 | 651 | ```js 652 | const array = [ 653 | new Date(2017, 4, 13), 654 | new Date(2018, 2, 12), 655 | new Date(2016, 0, 10), 656 | new Date(2016, 0, 9), 657 | ]; 658 | // Moment.js 659 | moment.min(array.map(a => moment(a))); 660 | // => "2016-01-08T13:00:00.000Z" 661 | 662 | // Native 663 | new Date(Math.min.apply(null, array)).toISOString(); 664 | // => "2016-01-08T13:00:00.000Z" 665 | 666 | // date-fns 667 | import min from 'date-fns/min'; 668 | min(array); 669 | // => "2016-01-08T13:00:00.000Z" 670 | 671 | // dayjs ❌ does not support the minimum of the given dates 672 | 673 | // Luxon 674 | DateTime.min(...array.map(a => DateTime.fromJSDate(a))).toJSDate(); 675 | // => "2016-01-08T13:00:00.000Z" 676 | ``` 677 | 678 | | Library | Time | 679 | | ------- | ---------- | 680 | | Moment | 1937.293ms | 681 | | Native | 1164.853ms | 682 | | DateFns | 908.990ms | 683 | | DayJs | - | 684 | | Luxon | 3085.444ms | 685 | 686 | **[⬆ back to top](#quick-links)** 687 | 688 | ## Manipulate 689 | 690 | ### Add 691 | 692 | Add the specified number of days to the given date. 693 | 694 | ```js 695 | // Moment.js 696 | moment().add(7, 'days'); 697 | // => "2018-09-16T09:12:49.695Z" 698 | 699 | // Native 700 | const now = new Date(); 701 | now.setDate(now.getDate() + 7); 702 | // => "Sun Sep 16 2018 09:12:49" 703 | 704 | // date-fns 705 | import addDays from 'date-fns/addDays'; 706 | addDays(new Date(), 7); 707 | // => "2018-09-16T09:12:49.695Z" 708 | 709 | // dayjs 710 | dayjs().add(7, 'day'); 711 | // => "2018-09-16T09:12:49.695Z" 712 | 713 | // Luxon 714 | DateTime.local() 715 | .plus({ day: 7 }) 716 | .toJSDate(); 717 | // => "2018-09-16T09:12:49.695Z" 718 | ``` 719 | 720 | | Library | Time | 721 | | ------- | ---------- | 722 | | Moment | 1468.151ms | 723 | | Native | 208.735ms | 724 | | DateFns | 337.129ms | 725 | | DayJs | 631.982ms | 726 | | Luxon | 7248.459ms | 727 | 728 | **[⬆ back to top](#quick-links)** 729 | 730 | ### Subtract 731 | 732 | Subtract the specified number of days from the given date. 733 | 734 | ```js 735 | // Moment.js 736 | moment().subtract(7, 'days'); 737 | // => "2018-09-02T09:12:49.695Z" 738 | 739 | // Native 740 | new Date(new Date().getTime() - 1000 * 60 * 60 * 24 * 7); 741 | // => Sun Sep 09 2018 09:12:49 742 | 743 | // date-fns 744 | import subDays from 'date-fns/subDays'; 745 | subDays(new Date(), 7); 746 | // => "2018-09-02T09:12:49.695Z" 747 | 748 | // dayjs 749 | dayjs().subtract(7, 'day'); 750 | // => "2018-09-02T09:12:49.695Z" 751 | 752 | // Luxon 753 | DateTime.local() 754 | .minus({ day: 7 }) 755 | .toJSDate(); 756 | // => "2018-09-02T09:12:49.695Z" 757 | ``` 758 | 759 | | Library | Time | 760 | | ------- | ---------- | 761 | | Moment | 1638.627ms | 762 | | Native | 246.940ms | 763 | | DateFns | 759.963ms | 764 | | DayJs | 954.443ms | 765 | | Luxon | 7701.059ms | 766 | 767 | **[⬆ back to top](#quick-links)** 768 | 769 | ### Start of Time 770 | 771 | Return the start of a unit of time for the given date. 772 | 773 | ```js 774 | // Moment.js 775 | moment().startOf('month'); 776 | // => "2018-08-31T14:00:00.000Z" 777 | 778 | // date-fns 779 | import startOfMonth from 'date-fns/startOfMonth'; 780 | startOfMonth(new Date()); 781 | // => "2018-08-31T14:00:00.000Z" 782 | 783 | // dayjs 784 | dayjs().startOf('month'); 785 | // => "2018-08-31T14:00:00.000Z" 786 | 787 | // Luxon 788 | DateTime.local().startOf('month'); 789 | // => "2018-09-02T09:12:49.695Z" 790 | ``` 791 | 792 | | Library | Time | 793 | | ------- | ---------- | 794 | | Moment | 1869.290ms | 795 | | Native | - | 796 | | DateFns | 455.759ms | 797 | | DayJs | 735.666ms | 798 | | Luxon | 5116.801ms | 799 | 800 | **[⬆ back to top](#quick-links)** 801 | 802 | ### End of Time 803 | 804 | Return the end of a unit of time for the given date. 805 | 806 | ```js 807 | // Moment.js 808 | moment().endOf('day'); 809 | // => "2018-09-09T13:59:59.999Z" 810 | 811 | // Native 812 | const end = new Date(); 813 | end.setHours(23, 59, 59, 999); 814 | end.toISOString(); 815 | // => "2018-09-09T16:59:59.999Z" 816 | 817 | // date-fns 818 | import endOfDay from 'date-fns/endOfDay'; 819 | endOfDay(new Date()); 820 | // => "2018-09-09T13:59:59.999Z" 821 | 822 | // dayjs 823 | dayjs().endOf('day'); 824 | // => "2018-09-09T13:59:59.999Z" 825 | 826 | // Luxon 827 | DateTime.local().endOf('day'); 828 | // => "2018-09-02T09:12:49.695Z" 829 | ``` 830 | 831 | | Library | Time | 832 | | ------- | ----------- | 833 | | Moment | 4583.067ms | 834 | | Native | 284.882ms | 835 | | DateFns | 386.746ms | 836 | | DayJs | 1138.415ms | 837 | | Luxon | 19305.183ms | 838 | 839 | **[⬆ back to top](#quick-links)** 840 | 841 | ## Display 842 | 843 | ### Format 844 | 845 | Return the formatted date string in the given format. 846 | 847 | ```js 848 | // Moment.js 849 | moment().format('dddd, MMMM Do YYYY, h:mm:ss A'); 850 | // => "Sunday, September 9th 2018, 7:12:49 PM" 851 | moment().format('ddd, hA'); 852 | // => "Sun, 7PM" 853 | 854 | // date-fns 855 | import format from 'date-fns/format'; 856 | format(new Date(), 'eeee, MMMM do YYYY, h:mm:ss aa'); 857 | // => "Sunday, September 9th 2018, 7:12:49 PM" 858 | format(new Date(), 'eee, ha'); 859 | // => "Sun, 7PM" 860 | 861 | // dayjs 862 | dayjs().format('dddd, MMMM D YYYY, h:mm:ss A'); 863 | // => "Sunday, September 9 2018, 7:12:49 PM" 864 | dayjs().format('ddd, hA'); 865 | // => "Sun, 7PM" 866 | // dayjs ⚠️ requires advancedFormat plugin to support more format tokens 867 | import advancedFormat from 'dayjs/plugin/customParseFormat' 868 | dayjs.extend(advancedFormat) 869 | dayjs().format('dddd, MMMM Do YYYY, h:mm:ss A'); 870 | // => "Sunday, September 9th 2018, 7:12:49 PM" 871 | 872 | // Luxon 873 | DateTime.fromMillis(time).toFormat('EEEE, MMMM dd yyyy, h:mm:ss a'); 874 | // => "Sunday, September 9 2018, 7:12:49 PM" ⚠️ not support 9th 875 | DateTime.fromMillis(time).toFormat('EEE, ha'); 876 | // => "Sun, 7PM" 877 | ``` 878 | 879 | **[⬆ back to top](#quick-links)** 880 | 881 | ### Time from now 882 | 883 | Return time from now. 884 | 885 | ```js 886 | // Moment.js 887 | moment(1536484369695).fromNow(); 888 | // => "4 days ago" 889 | 890 | // date-fns 891 | import formatDistance from 'date-fns/formatDistance'; 892 | formatDistance(new Date(1536484369695), new Date(), { addSuffix: true }); 893 | // => "4 days ago" 894 | 895 | // dayjs ⚠️ requires relativeTime plugin 896 | import relativeTime from 'dayjs/plugin/relativeTime'; 897 | dayjs.extend(relativeTime); 898 | 899 | dayjs(1536484369695).fromNow(); 900 | // => "5 days ago" ⚠️ the rounding method of this plugin is different from moment.js and date-fns, use with care. 901 | 902 | // luxon ❌ does not support relative time 903 | ``` 904 | 905 | **[⬆ back to top](#quick-links)** 906 | 907 | ### Time from x 908 | 909 | Return time from x. 910 | 911 | ```js 912 | // Moment.js 913 | moment([2007, 0, 27]).to(moment([2007, 0, 29])); 914 | // => "in 2 days" 915 | 916 | // date-fns 917 | import formatDistance from 'date-fns/formatDistance'; 918 | formatDistance(new Date(2007, 0, 27), new Date(2007, 0, 29)); 919 | // => "2 days" 920 | 921 | // dayjs ⚠️ requires relativeTime plugin 922 | import relativeTime from 'dayjs/plugin/relativeTime'; 923 | dayjs.extend(relativeTime); 924 | dayjs('2007-01-27').to(dayjs('2007-01-29')); 925 | // => "in 2 days" 926 | 927 | // luxon ❌ does not support relative time 928 | ``` 929 | 930 | **[⬆ back to top](#quick-links)** 931 | 932 | ### Difference 933 | 934 | Get the unit of time between the given dates. 935 | 936 | ```js 937 | // Moment.js 938 | moment([2007, 0, 27]).diff(moment([2007, 0, 29])); 939 | // => -172800000 940 | moment([2007, 0, 27]).diff(moment([2007, 0, 29]), 'days'); 941 | // => -2 942 | 943 | // Native 944 | new Date(2007, 0, 27) - new Date(2007, 0, 29); 945 | // => -172800000 946 | Math.ceil( 947 | (new Date(2007, 0, 27) - new Date(2007, 0, 29)) / 1000 / 60 / 60 / 24 948 | ); 949 | // => -2 950 | 951 | // date-fns 952 | import differenceInMilliseconds from 'date-fns/differenceInMilliseconds'; 953 | differenceInMilliseconds(new Date(2007, 0, 27), new Date(2007, 0, 29)); 954 | // => -172800000 955 | import differenceInDays from 'date-fns/differenceInDays'; 956 | differenceInDays(new Date(2007, 0, 27), new Date(2007, 0, 29)); 957 | // => -2 958 | 959 | // dayjs 960 | dayjs('2007-01-27').diff(dayjs('2007-01-29'), 'milliseconds'); 961 | // => -172800000 962 | dayjs('2007-01-27').diff(dayjs('2007-01-29'), 'days'); 963 | // => -2 964 | 965 | // luxon 966 | DateTime.local(2007, 1, 27).diff(DateTime.local(2007, 1, 29)).milliseconds; 967 | // => -172800000 968 | DateTime.local(2007, 1, 27).diff(DateTime.local(2007, 1, 29), 'days').days; 969 | // => -2 970 | ``` 971 | 972 | **[⬆ back to top](#quick-links)** 973 | 974 | ## Query 975 | 976 | ### Is Before 977 | 978 | Check if a date is before another date. 979 | 980 | ```js 981 | // Moment.js 982 | moment('2010-10-20').isBefore('2010-10-21'); 983 | // => true 984 | 985 | // Native 986 | new Date(2010, 10, 20) < new Date(2010, 10, 21); 987 | // => true 988 | 989 | // date-fns 990 | import isBefore from 'date-fns/isBefore'; 991 | isBefore(new Date(2010, 9, 20), new Date(2010, 9, 21)); 992 | // => true 993 | 994 | // dayjs 995 | dayjs('2010-10-20').isBefore('2010-10-21'); 996 | // => true 997 | 998 | // luxon 999 | DateTime.fromISO('2010-10-20') < DateTime.fromISO('2010-10-21'); 1000 | // => true 1001 | ``` 1002 | 1003 | **[⬆ back to top](#quick-links)** 1004 | 1005 | ### Is Same 1006 | 1007 | Check if a date is the same as another date. 1008 | 1009 | ```js 1010 | // Moment.js 1011 | moment('2010-10-20').isSame('2010-10-21'); 1012 | // => false 1013 | moment('2010-10-20').isSame('2010-10-20'); 1014 | // => true 1015 | moment('2010-10-20').isSame('2010-10-21', 'month'); 1016 | // => true 1017 | 1018 | // Native 1019 | new Date(2010, 9, 20).valueOf() === new Date(2010, 9, 21).valueOf(); 1020 | // => false 1021 | new Date(2010, 9, 20).valueOf() === new Date(2010, 9, 20).valueOf(); 1022 | // => true 1023 | new Date(2010, 9, 20).getTime() === new Date(2010, 9, 20).getTime(); 1024 | // => true 1025 | new Date(2010, 9, 20).valueOf() === new Date(2010, 9, 20).getTime(); 1026 | // => true 1027 | new Date(2010, 9, 20).toDateString().substring(4, 7) === 1028 | new Date(2010, 9, 21).toDateString().substring(4, 7); 1029 | // => true 1030 | 1031 | // date-fns 1032 | import isSameDay from 'date-fns/isSameDay'; 1033 | import isSameMonth from 'date-fns/isSameMonth'; 1034 | isSameDay(new Date(2010, 9, 20), new Date(2010, 9, 21)); 1035 | // => false 1036 | isSameDay(new Date(2010, 9, 20), new Date(2010, 9, 20)); 1037 | // => true 1038 | isSameMonth(new Date(2010, 9, 20), new Date(2010, 9, 21)); 1039 | // => true 1040 | 1041 | // dayjs 1042 | dayjs('2010-10-20').isSame('2010-10-21'); 1043 | // => false 1044 | dayjs('2010-10-20').isSame('2010-10-20'); 1045 | // => true 1046 | dayjs('2010-10-20').isSame('2010-10-21', 'month'); 1047 | // => true 1048 | 1049 | // luxon 1050 | (+DateTime.fromISO('2010-10-20') === 1051 | +DateTime.fromISO('2010-10-21') + 1052 | // => false 1053 | DateTime.fromISO('2010-10-20')) === 1054 | +DateTime.fromISO('2010-10-20'); 1055 | // => true 1056 | DateTime.fromISO('2010-10-20').hasSame(DateTime.fromISO('2010-10-21'), 'month'); 1057 | // => true 1058 | ``` 1059 | 1060 | **[⬆ back to top](#quick-links)** 1061 | 1062 | ### Is After 1063 | 1064 | Check if a date is after another date. 1065 | 1066 | ```js 1067 | // Moment.js 1068 | moment('2010-10-20').isAfter('2010-10-19'); 1069 | // => true 1070 | 1071 | // Native 1072 | new Date(2010, 9, 20) > new Date(2010, 9, 19); 1073 | // => true 1074 | 1075 | // date-fns 1076 | import isAfter from 'date-fns/isAfter'; 1077 | isAfter(new Date(2010, 9, 20), new Date(2010, 9, 19)); 1078 | // => true 1079 | 1080 | // dayjs 1081 | dayjs('2010-10-20').isAfter('2010-10-19'); 1082 | // => true 1083 | 1084 | // luxon 1085 | DateTime.fromISO('2010-10-20') > DateTime.fromISO('2010-10-19'); 1086 | // => true 1087 | ``` 1088 | 1089 | **[⬆ back to top](#quick-links)** 1090 | 1091 | ### Is Between 1092 | 1093 | Check if a date is between two other dates. 1094 | 1095 | ```js 1096 | // Moment.js 1097 | moment('2010-10-20').isBetween('2010-10-19', '2010-10-25'); 1098 | // => true 1099 | 1100 | // date-fns 1101 | import isWithinInterval from 'date-fns/isWithinInterval'; 1102 | isWithinInterval(new Date(2010, 9, 20), { 1103 | start: new Date(2010, 9, 19), 1104 | end: new Date(2010, 9, 25), 1105 | }); 1106 | // => true 1107 | 1108 | // dayjs ⚠️ requires isBetween plugin 1109 | import isBetween from 'dayjs/plugin/isBetween'; 1110 | dayjs.extend(isBetween); 1111 | dayjs('2010-10-20').isBetween('2010-10-19', '2010-10-25'); 1112 | // => true 1113 | 1114 | // luxon 1115 | Interval.fromDateTimes( 1116 | DateTime.fromISO('2010-10-19'), 1117 | DateTime.fromISO('2010-10-25') 1118 | ).contains(DateTime.fromISO('2010-10-20')); 1119 | // => true 1120 | ``` 1121 | 1122 | **[⬆ back to top](#quick-links)** 1123 | 1124 | ### Is Leap Year 1125 | 1126 | Check if a year is a leap year. 1127 | 1128 | ```js 1129 | // Moment.js 1130 | moment([2000]).isLeapYear(); 1131 | // => true 1132 | 1133 | // Native 1134 | new Date(2000, 1, 29).getDate() === 29; 1135 | // => true 1136 | 1137 | // date-fns 1138 | import isLeapYear from 'date-fns/isLeapYear'; 1139 | isLeapYear(new Date(2000, 0, 1)); 1140 | // => true 1141 | 1142 | // dayjs ⚠️ requires isLeapYear plugin 1143 | import isLeapYear from 'dayjs/plugin/isLeapYear'; 1144 | dayjs.extend(isLeapYear); 1145 | dayjs('2000-01-01').isLeapYear(); 1146 | // => true 1147 | 1148 | // luxon 1149 | expect(DateTime.local(2000).isInLeapYear).toBeTruthy(); 1150 | // => true 1151 | ``` 1152 | 1153 | **[⬆ back to top](#quick-links)** 1154 | 1155 | ### Is a Date 1156 | 1157 | Check if a variable is a native js Date object. 1158 | 1159 | ```js 1160 | // Moment.js 1161 | moment.isDate(new Date()); 1162 | // => true 1163 | 1164 | // Native 1165 | new Date() instanceof Date; 1166 | // => true 1167 | 1168 | // date-fns 1169 | import isDate from 'date-fns/isDate'; 1170 | isDate(new Date()); 1171 | // => true 1172 | 1173 | // dayjs 1174 | dayjs(new Date()).isValid() 1175 | 1176 | // luxon 1177 | DateTime.local().isValid; 1178 | // => true 1179 | ``` 1180 | 1181 | **[⬆ back to top](#quick-links)** 1182 | 1183 | # License 1184 | 1185 | MIT 1186 | --------------------------------------------------------------------------------