├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── README.md ├── index.d.ts ├── index.js ├── package.json ├── test └── index.js └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | refs: 4 | - &only_master 5 | filters: 6 | branches: 7 | only: master 8 | 9 | - ¬_master 10 | filters: 11 | branches: 12 | ignore: master 13 | 14 | workflows: 15 | test: 16 | jobs: 17 | - unit-tests: 18 | <<: *not_master 19 | name: node-10 20 | version: '10' 21 | - unit-tests: 22 | <<: *not_master 23 | name: node-12 24 | version: '12' 25 | 26 | release: 27 | jobs: 28 | - unit-tests: 29 | <<: *only_master 30 | name: node-10 31 | version: '10' 32 | - unit-tests: 33 | <<: *only_master 34 | name: node-12 35 | version: '12' 36 | 37 | - publish-dry-run: 38 | <<: *only_master 39 | context: common-env 40 | 41 | - publish-approval: 42 | type: approval 43 | context: common-env 44 | requires: 45 | - publish-dry-run 46 | 47 | - publish: 48 | <<: *only_master 49 | context: common-env 50 | requires: 51 | - node-10 52 | - node-12 53 | - publish-approval 54 | 55 | jobs: 56 | unit-tests: 57 | parameters: 58 | version: 59 | type: string 60 | docker: 61 | - image: circleci/node:<< parameters.version >> 62 | steps: 63 | - setup 64 | - test 65 | 66 | publish-dry-run: 67 | docker: 68 | - image: circleci/node:12 69 | steps: 70 | - setup 71 | - publish-dry-run 72 | 73 | publish: 74 | docker: 75 | - image: circleci/node:12 76 | steps: 77 | - setup 78 | - publish 79 | 80 | commands: 81 | setup: 82 | description: 'Checkout and install dependencies' 83 | steps: 84 | - checkout 85 | - run: 86 | name: Versions 87 | command: node -v && npm -v && yarn -v 88 | - run: 89 | name: Install Dependencies 90 | command: yarn install --pure-lockfile 91 | 92 | test: 93 | steps: 94 | - run: 95 | name: Test 96 | command: yarn test 97 | 98 | publish-dry-run: 99 | steps: 100 | - run: 101 | name: NPM Auth 102 | command: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc 103 | - run: 104 | name: Release (Dry Run) 105 | command: npx rollingversions publish --dry-run 106 | 107 | publish: 108 | steps: 109 | - run: 110 | name: NPM Auth 111 | command: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc 112 | - run: 113 | name: Release 114 | command: npx rollingversions publish -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | components 2 | build 3 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Forbes Lindesay 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dehumanize-date 2 | 3 | Parse dates in all the formats humans like to use: 4 | 5 | - today/tomorrow/yesterday 6 | - next/this/last Wednesday 7 | - 12th January 8 | - 12th January 1950 9 | - 09-08-2008 10 | - 2008-08-09 11 | 12 | Return dates in just the format that computers should use: 13 | 14 | - 2008-08-09 15 | 16 | [![Build Status](https://img.shields.io/travis/ForbesLindesay/dehumanize-date/master.svg)](https://travis-ci.org/ForbesLindesay/dehumanize-date) 17 | [![Dependency Status](https://img.shields.io/david/ForbesLindesay/dehumanize-date.svg)](https://david-dm.org/ForbesLindesay/dehumanize-date) 18 | [![NPM version](https://img.shields.io/npm/v/dehumanize-date.svg)](https://www.npmjs.com/package/dehumanize-date) 19 | 20 | [![testling badge](https://ci.testling.com/ForbesLindesay/dehumanize-date.png)](https://ci.testling.com/ForbesLindesay/dehumanize-date) 21 | 22 | ## Installation 23 | 24 | $ npm install dehumanize-date 25 | 26 | ## Usage 27 | 28 | A simple function which takes a string as an argument and returns a string in the form `yyyy-mm-dd`. 29 | 30 | ```javascript 31 | var date = dehumanizeDate(userInput); 32 | ``` 33 | 34 | The second parameter contains options. You can set whether to use US-style short dates, you can change 35 | what 'now' is for input like 'yesterday', and you can change the cutoff for 2-digit years that determines 36 | whether a given two-digit year is in the 21st or 20th century. 37 | 38 | ```javascript 39 | var date = dehumanizeDate(userInput, {usa: true, now: new Date(), cutoff: 80}); 40 | ``` 41 | 42 | If you just want to use US formats for numerical dates you can also pass `true` as the second parameter: 43 | 44 | ```javascript 45 | var date = dehumanizeDate(userInput, true); 46 | ``` 47 | 48 | ## License 49 | 50 | MIT 51 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export interface Options { 2 | /** 3 | * defaults to false 4 | */ 5 | usa?: boolean; 6 | /** 7 | * defaults to the current time 8 | */ 9 | now?: Date; 10 | /** 11 | * defaults to 80 12 | */ 13 | cutoff?: number; 14 | } 15 | 16 | declare function parse(str: string, options?: Options): string | null; 17 | export default parse; 18 | 19 | export function parseNearbyDays(str: string, now: Date): string | null; 20 | export function parseLastThisNext(string: string, now: Date): string | null; 21 | export function parseAgoFrom(string: string, now: Date): string | null; 22 | export function parseNumberDate(string: string, usa: boolean): string | null; 23 | export function parseNumberDateShortYear(string: string, usa: boolean, cutoff: number): string | null; 24 | export function parseNumberDateNoYear(string: string, usa: boolean, now: Date): string | null; 25 | export function parseWordyDate(string: string, now: Date, cutoff: number): string | null; 26 | export function monthFromName(month: string): number | null; 27 | export function date(year: number, month: number, day: number): string | null; 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var isLeapYear = require('is-leap-year'); 4 | 5 | var MONTH_NAMES = ["january", "february", "march", 6 | "april", "may", "june", 7 | "july", "august", "september", 8 | "october", "november", "december"]; 9 | 10 | var DAY_NAMES = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"]; 11 | 12 | var DAYS_IN_MONTH = [ 13 | 31, 14 | null, // 29 in leap years, otherwise 28 15 | 31, 16 | 30, 17 | 31, 18 | 30, 19 | 31, 20 | 31, 21 | 30, 22 | 31, 23 | 30, 24 | 31 25 | ]; 26 | 27 | exports = module.exports = function parse(str, options) { 28 | if (typeof options !== 'object') { 29 | options = {usa: options}; 30 | } 31 | options = options || {}; 32 | options.usa = options.usa !== undefined ? options.usa : false; 33 | options.now = options.now !== undefined ? options.now : new Date(); 34 | options.cutoff = options.cutoff !== undefined ? options.cutoff : 80; 35 | 36 | str = str.trim().toLowerCase(); 37 | 38 | return parseNearbyDays(str, options.now) || 39 | parseLastThisNext(str, options.now) || 40 | parseAgoFrom(str, options.now) || 41 | parseNumberDate(str, options.usa) || 42 | parseNumberDateShortYear(str, options.usa, options.cutoff) || 43 | parseNumberDateNoYear(str, options.usa, options.now) || 44 | parseWordyDate(str, options.now, options.cutoff) || 45 | parseIso8601Date(str); 46 | }; 47 | exports.default = module.exports; 48 | 49 | var NUMBER = /^[0-9]+$/; 50 | var NUMBER_WITH_ORDINAL = /^([0-9]+)(st|nd|rd|th)?$/; 51 | var NUMBER_DATE = /^(3[0-1]|[1-2][0-9]|0?[1-9])[,\|\\\/\-\. ]+(1[0-2]|0?[1-9])[,\|\\\/\-\. ]+([0-9]{4})$/; 52 | var NUMBER_DATE_USA = /^(1[0-2]|0?[1-9])[,\|\\\/\-\. ]+(3[0-1]|[1-2][0-9]|0?[1-9])[,\|\\\/\-\. ]+([0-9]{4})$/; 53 | var NUMBER_DATE_SHORT_YEAR = /^(3[0-1]|[1-2][0-9]|0?[1-9])[,\|\\\/\-\. ]+(1[0-2]|0?[1-9])[,\|\\\/\-\. ]+([0-9]{2})$/; 54 | var NUMBER_DATE_SHORT_YEAR_USA = /^(1[0-2]|0?[1-9])[,\|\\\/\-\. ]+(3[0-1]|[1-2][0-9]|0?[1-9])[,\|\\\/\-\. ]+([0-9]{2})$/; 55 | var NUMBER_DATE_NO_YEAR = /^(3[0-1]|[1-2][0-9]|0?[1-9])[,\|\\\/\-\. ]+(1[0-2]|0?[1-9])$/; 56 | var NUMBER_DATE_NO_YEAR_USA = /^(1[0-2]|0?[1-9])[,\|\\\/\-\. ]+(3[0-1]|[1-2][0-9]|0?[1-9])$/; 57 | 58 | 59 | var ISO_8601_DATE = /^([0-9]{4})[-/\\]?(1[0-2]|0?[1-9])[-/\\]?(3[0-1]|[1-2][0-9]|0?[1-9])$/; 60 | 61 | function addDays(now, numberOfDays) { 62 | var result = new Date(now * 1 + numberOfDays * 60 * 60 * 24 * 1000); 63 | return date(result.getFullYear(), result.getMonth(), result.getDate()); 64 | } 65 | 66 | exports.parseNearbyDays = parseNearbyDays; 67 | function parseNearbyDays(string, now) { 68 | if (string == 'today') { 69 | return date(now.getFullYear(), now.getMonth(), now.getDate());; 70 | } else if (string == 'yesterday') { 71 | return addDays(now, -1); 72 | } else if (string == 'tomorrow') { 73 | return addDays(now, +1); 74 | } else { 75 | return null; 76 | } 77 | } 78 | 79 | exports.parseLastThisNext = parseLastThisNext; 80 | function parseLastThisNext(string, now) { 81 | var tokens = string.split(/[,\s]+/); 82 | if (['last', 'this', 'next'].indexOf(tokens[0]) >= 0 && 83 | tokens.length === 2 ) { 84 | var dayAbbreviations = DAY_NAMES.map(function (name) { return name.substr(0, tokens[1].length); }); 85 | var dayIndex = dayAbbreviations.indexOf(tokens[1]); 86 | if (dayIndex !== -1 && 87 | dayAbbreviations.indexOf(tokens[1], dayIndex + 1) === -1) { 88 | var dayDiff = dayIndex - now.getDay(); 89 | if (dayDiff < 0) dayDiff += 7; 90 | if (tokens[0] === 'last') return addDays(now, dayDiff - 7); 91 | if (tokens[0] === 'this') return addDays(now, dayDiff); 92 | if (tokens[0] === 'next') return addDays(now, dayDiff + 7); 93 | } 94 | return null; 95 | } else { 96 | return null; 97 | } 98 | } 99 | 100 | exports.parseAgoFrom = parseAgoFrom; 101 | function parseAgoFrom(string, now) { 102 | var tokens = string.split(/[,\s]+/); 103 | if (['day', 'days', 'week', 'weeks'].indexOf(tokens[1]) >= 0 && 104 | tokens[0].match(NUMBER) && 105 | ['ago', 'from'].indexOf(tokens[2]) >= 0 106 | ) { 107 | if (tokens[2] === 'ago') { 108 | if (['day', 'days'].indexOf(tokens[1]) >= 0) return addDays(now, tokens[0] * -1); 109 | if (['week', 'weeks'].indexOf(tokens[1]) >= 0) return addDays(now, (tokens[0] * 7) * -1); 110 | } 111 | if (tokens[2] === 'from') { 112 | if (['day', 'days'].indexOf(tokens[1]) >= 0) return addDays(now, tokens[0]); 113 | if (['week', 'weeks'].indexOf(tokens[1]) >= 0) return addDays(now, (tokens[0] * 7)); 114 | } 115 | return null; 116 | } else { 117 | return null; 118 | } 119 | } 120 | 121 | exports.parseNumberDate = parseNumberDate; 122 | function parseNumberDate(str, usa) { 123 | var match = usa ? NUMBER_DATE_USA.exec(str) : NUMBER_DATE.exec(str); 124 | if (match) { 125 | return usa ? date(+match[3], match[1] - 1, +match[2]) : date(+match[3], match[2] - 1, +match[1]); 126 | } else { 127 | return null; 128 | } 129 | } 130 | 131 | exports.parseNumberDateShortYear = parseNumberDateShortYear; 132 | function parseNumberDateShortYear(str, usa, cutoff) { 133 | var match = usa ? NUMBER_DATE_SHORT_YEAR_USA.exec(str) : NUMBER_DATE_SHORT_YEAR.exec(str); 134 | if (match) { 135 | var year = (+match[3]); 136 | if (year > cutoff) year += 1900; 137 | else year += 2000; 138 | return usa ? date(year, match[1] - 1, +match[2]) : date(year, match[2] - 1, +match[1]); 139 | } else { 140 | return null; 141 | } 142 | } 143 | 144 | exports.parseNumberDateNoYear = parseNumberDateNoYear; 145 | function parseNumberDateNoYear(str, usa, today) { 146 | var match = usa ? NUMBER_DATE_NO_YEAR_USA.exec(str) : NUMBER_DATE_NO_YEAR.exec(str); 147 | if (match) { 148 | var year = today.getFullYear(); 149 | return usa ? date(year, match[1] - 1, +match[2]) : date(year, match[2] - 1, +match[1]); 150 | } else { 151 | return null; 152 | } 153 | } 154 | 155 | exports.parseWordyDate = parseWordyDate; 156 | function parseWordyDate(string, today, cutoff) { 157 | var tokens = string.split(/[,\s]+/); 158 | if (tokens.length >= 2) { 159 | var match; 160 | if (match = tokens[0].match(NUMBER_WITH_ORDINAL)) { 161 | return parseWordyDateParts(match[1], tokens[1], tokens[2], today, cutoff); 162 | } else if (match = tokens[1].match(NUMBER_WITH_ORDINAL)) { 163 | return parseWordyDateParts(match[1], tokens[0], tokens[2], today, cutoff); 164 | } else { 165 | return null; 166 | } 167 | } 168 | } 169 | 170 | function parseWordyDateParts(rawDay, rawMonth, rawYear, today, cutoff) { 171 | var day = +rawDay; 172 | var month = monthFromName(rawMonth); 173 | var year; 174 | 175 | if (rawYear) 176 | year = rawYear.match(NUMBER) ? rawYear * 1 : null; 177 | else 178 | year = today.getFullYear(); 179 | 180 | if (year < 100 && year >= cutoff) year += 1900; 181 | if (year < 100) year += 2000; 182 | 183 | if (!(day && month !== null && year)) 184 | return null; 185 | 186 | return date(year, month, day); 187 | } 188 | 189 | function parseIso8601Date(string) { 190 | var match; 191 | if (match = ISO_8601_DATE.exec(string)) { 192 | return date(+match[1], match[2] - 1, +match[3]); 193 | } else { 194 | return null; 195 | } 196 | } 197 | 198 | exports.monthFromName = monthFromName; 199 | function monthFromName(month) { 200 | var monthAbbreviations = MONTH_NAMES.map(function (name) { return name.substr(0, month.length); }); 201 | var monthIndex = monthAbbreviations.indexOf(month); 202 | if (monthIndex !== -1 && 203 | monthAbbreviations.indexOf(month, monthIndex + 1) === -1) { 204 | return monthIndex; 205 | } 206 | return null; 207 | } 208 | 209 | exports.date = date; 210 | function date(year, month, day) { 211 | month++; 212 | if (month > 12 || month < 1) return null; 213 | if (day < 1) return null; 214 | if (month === 2) { 215 | if (day > (isLeapYear(year) ? 29 : 28)) return null; 216 | } else if (day > DAYS_IN_MONTH[month - 1]) { 217 | return null; 218 | } 219 | if (month < 10) month = '0' + month; 220 | if (day < 10) day = '0' + day; 221 | 222 | return year + '-' + month + '-' + day; 223 | } 224 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dehumanize-date", 3 | "description": "Parse dates in all the formats humans like to use.", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "node test" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/ForbesLindesay/dehumanize-date.git" 11 | }, 12 | "keywords": [ 13 | "date", 14 | "parse", 15 | "dates", 16 | "humanize", 17 | "humanise", 18 | "human" 19 | ], 20 | "author": "ForbesLindesay", 21 | "license": "MIT", 22 | "dependencies": { 23 | "is-leap-year": "^0.1.0" 24 | }, 25 | "devDependencies": { 26 | "tape": "~4.10.0" 27 | }, 28 | "testling": { 29 | "files": "test/*.js", 30 | "browsers": [ 31 | "ie/8..latest", 32 | "firefox/17..latest", 33 | "firefox/nightly", 34 | "chrome/22..latest", 35 | "chrome/canary", 36 | "opera/12..latest", 37 | "opera/next", 38 | "safari/5.1..latest", 39 | "ipad/6.0..latest", 40 | "iphone/6.0..latest", 41 | "android-browser/4.2..latest" 42 | ] 43 | } 44 | } -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var test = require('tape'); 4 | var dehumanize = require('../'); 5 | 6 | function describe(name, parseDate, fn) { 7 | test(name, function (t) { 8 | var queue = []; 9 | fn(function (input, expected) { 10 | var initialExpected = expected; 11 | var actual = parseDate(input); 12 | t.equal(actual, expected, input + ' -> ' + initialExpected); 13 | }); 14 | t.end(); 15 | }); 16 | } 17 | 18 | describe('parseNearbyDays', function (input) { 19 | return dehumanize.parseNearbyDays(input, new Date(2000, 0, 5)); 20 | }, function (equal) { 21 | equal('today', '2000-01-05'); 22 | equal('yesterday', '2000-01-04'); 23 | equal('tomorrow', '2000-01-06'); 24 | equal('foo bar', null); 25 | }); 26 | 27 | describe('parseLastThisNext', function (input) { 28 | return dehumanize.parseLastThisNext(input, new Date(2000, 0, 5)); 29 | }, function (equal) { 30 | equal('next monday', '2000-01-17'); 31 | equal('next m', '2000-01-17'); 32 | equal('last tuesday', '2000-01-04'); 33 | equal('last tu', '2000-01-04'); 34 | equal('this thursday', '2000-01-06'); 35 | equal('this th', '2000-01-06'); 36 | equal('last t', null); 37 | equal('next s', null); 38 | equal('foo bar', null); 39 | }); 40 | 41 | describe('parseAgoFrom', function (input) { 42 | return dehumanize.parseAgoFrom(input, new Date(2000, 0, 5)); 43 | }, function (equal) { 44 | equal('2 days ago', '2000-01-03'); 45 | equal('2 days from now', '2000-01-07'); 46 | equal('1 day ago', '2000-01-04'); 47 | equal('1 day from now', '2000-01-06'); 48 | equal('1 week from now', '2000-01-12'); 49 | equal('2 weeks from now', '2000-01-19'); 50 | equal('1 week ago', '1999-12-29'); 51 | equal('2 weeks ago', '1999-12-22'); 52 | equal('foo days ago', null); 53 | }); 54 | 55 | describe('parseNumberDate', function (input) { 56 | return dehumanize.parseNumberDate(input, false); 57 | }, function (equal) { 58 | equal('2-2-2012', '2012-02-02'); 59 | equal('2/8/2012', '2012-08-02'); 60 | equal('2\\8\\2012', '2012-08-02'); 61 | equal('2,4,2012', '2012-04-02'); 62 | equal('2 4 2012', '2012-04-02'); 63 | equal('02-02-2012', '2012-02-02'); 64 | equal('80-80-2012', null); 65 | }); 66 | 67 | describe('parseNumberDateShortYear', function (input) { 68 | return dehumanize.parseNumberDateShortYear(input, false, 80); 69 | }, function (equal) { 70 | equal('2-2-12', '2012-02-02'); 71 | equal('2/8/12', '2012-08-02'); 72 | equal('2\\8\\12', '2012-08-02'); 73 | equal('2,4,12', '2012-04-02'); 74 | equal('2 4 12', '2012-04-02'); 75 | equal('02-02-12', '2012-02-02'); 76 | equal('30/01/60', '2060-01-30'); 77 | equal('02-02-85', '1985-02-02'); 78 | equal('80-80-12', null); 79 | }); 80 | 81 | describe('parseNumberDateNoYear', function (input) { 82 | return dehumanize.parseNumberDateNoYear(input, false, new Date(2012, 1, 1)); 83 | }, function (equal) { 84 | equal('2-2', '2012-02-02'); 85 | equal('2/8', '2012-08-02'); 86 | equal('2\\8', '2012-08-02'); 87 | equal('2,4', '2012-04-02'); 88 | equal('2 4', '2012-04-02'); 89 | equal('02-02', '2012-02-02'); 90 | equal('30/01', '2012-01-30'); 91 | equal('80-80', null); 92 | }); 93 | 94 | describe('monthFromName', function (input) { 95 | return dehumanize.monthFromName(input); 96 | }, function (equal) { 97 | equal('january', 0); 98 | equal('januar', 0); 99 | equal('janua', 0); 100 | equal('janu', 0); 101 | equal('jan', 0); 102 | equal('ja', 0); 103 | equal('june', 5); 104 | equal('jun', 5); 105 | equal('july', 6); 106 | equal('jul', 6); 107 | equal('ju', null); 108 | equal('j', null); 109 | equal('foo', null); 110 | equal('foobar', null); 111 | }); 112 | 113 | describe('parseWordyDate', function (input) { 114 | return dehumanize.parseWordyDate(input, new Date(2012, 1, 1)); 115 | }, function (equal) { 116 | equal('june 1st 2012', '2012-06-01'); 117 | equal('1st june 2012', '2012-06-01'); 118 | equal('june 1 2012', '2012-06-01'); 119 | equal('1 june 2012', '2012-06-01'); 120 | equal('12th june 2012', '2012-06-12'); 121 | equal('12 june 2012', '2012-06-12'); 122 | equal('12th june', '2012-06-12'); 123 | equal('12th ju', null); 124 | equal('12th ju 2012', null); 125 | equal('36th june', null); 126 | equal('36th june 2012', null); 127 | 128 | equal('1st december 2012', '2012-12-01'); 129 | }); 130 | 131 | describe('dehumanize cutoff', function (input) { 132 | return dehumanize(input, {cutoff: 40}); 133 | }, function (equal) { 134 | equal('28/2/60', '1960-02-28'); 135 | equal('28/2/30', '2030-02-28'); 136 | equal('28/2/15', '2015-02-28'); 137 | equal('29/2/2004', '2004-02-29'); 138 | equal('june 1 1960', '1960-06-01'); 139 | equal('jun 1 60', '1960-06-01'); 140 | }); 141 | 142 | describe('dehumanize', function (input) { 143 | return dehumanize(input); 144 | }, function (equal) { 145 | equal(' 14th July 2012 ', '2012-07-14'); 146 | equal('foo', null); 147 | equal('foobar', null); 148 | equal('29/2/15', null); 149 | equal('28/2/15', '2015-02-28'); 150 | equal('29/2/2004', '2004-02-29'); 151 | equal('31-6-2012', null); 152 | }); 153 | 154 | describe('dehumanize usa', function (input) { 155 | return dehumanize(input, {usa: true}); 156 | }, function (equal) { 157 | equal(' 14th July 2012 ', '2012-07-14'); 158 | equal('foo', null); 159 | equal('foobar', null); 160 | equal('29/2/15', null); 161 | equal('2/28/15', '2015-02-28'); 162 | equal('2/29/2004', '2004-02-29'); 163 | equal('31-16-2012', null); 164 | }); 165 | 166 | describe('dehumanize ISO date with different or mixed markers', function (input) { 167 | return dehumanize(input); 168 | }, function (equal) { 169 | equal('2004/02/29', '2004-02-29'); 170 | equal('2004-02/29', '2004-02-29'); 171 | equal('2004/02-29', '2004-02-29'); 172 | equal('2004\\02\\29', '2004-02-29'); 173 | equal('2004-02\\29', '2004-02-29'); 174 | equal('2004\\02-29', '2004-02-29'); 175 | equal('2004\\02/29', '2004-02-29'); 176 | equal('2004/02\\29', '2004-02-29'); 177 | equal('2004/2/29', '2004-02-29'); 178 | equal('2004-2/29', '2004-02-29'); 179 | equal('2004/2-29', '2004-02-29'); 180 | equal('2004\\2\\29', '2004-02-29'); 181 | equal('2004-2\\29', '2004-02-29'); 182 | equal('2004\\2-29', '2004-02-29'); 183 | equal('2004\\2/29', '2004-02-29'); 184 | equal('2004/2\\29', '2004-02-29'); 185 | }); 186 | 187 | describe('dehumanize ISO date with single digits', function (input) { 188 | return dehumanize(input); 189 | }, function (equal) { 190 | equal('2004-2-9', '2004-02-09'); 191 | equal('2004/2/9', '2004-02-09'); 192 | equal('2004-2/9', '2004-02-09'); 193 | equal('2004-2/9', '2004-02-09'); 194 | equal('2004-12-9', '2004-12-09'); 195 | equal('2004/12/9', '2004-12-09'); 196 | equal('2004-12/9', '2004-12-09'); 197 | equal('2004-12/9', '2004-12-09'); 198 | }); 199 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | balanced-match@^1.0.0: 6 | version "1.0.0" 7 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 8 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 9 | 10 | brace-expansion@^1.1.7: 11 | version "1.1.11" 12 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 13 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 14 | dependencies: 15 | balanced-match "^1.0.0" 16 | concat-map "0.0.1" 17 | 18 | concat-map@0.0.1: 19 | version "0.0.1" 20 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 21 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 22 | 23 | deep-equal@~1.0.1: 24 | version "1.0.1" 25 | resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" 26 | integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= 27 | 28 | define-properties@^1.1.2, define-properties@^1.1.3: 29 | version "1.1.3" 30 | resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" 31 | integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== 32 | dependencies: 33 | object-keys "^1.0.12" 34 | 35 | defined@~1.0.0: 36 | version "1.0.0" 37 | resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" 38 | integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= 39 | 40 | es-abstract@^1.5.0: 41 | version "1.17.4" 42 | resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184" 43 | integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ== 44 | dependencies: 45 | es-to-primitive "^1.2.1" 46 | function-bind "^1.1.1" 47 | has "^1.0.3" 48 | has-symbols "^1.0.1" 49 | is-callable "^1.1.5" 50 | is-regex "^1.0.5" 51 | object-inspect "^1.7.0" 52 | object-keys "^1.1.1" 53 | object.assign "^4.1.0" 54 | string.prototype.trimleft "^2.1.1" 55 | string.prototype.trimright "^2.1.1" 56 | 57 | es-to-primitive@^1.2.1: 58 | version "1.2.1" 59 | resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" 60 | integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== 61 | dependencies: 62 | is-callable "^1.1.4" 63 | is-date-object "^1.0.1" 64 | is-symbol "^1.0.2" 65 | 66 | for-each@~0.3.3: 67 | version "0.3.3" 68 | resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" 69 | integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== 70 | dependencies: 71 | is-callable "^1.1.3" 72 | 73 | fs.realpath@^1.0.0: 74 | version "1.0.0" 75 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 76 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 77 | 78 | function-bind@^1.0.2, function-bind@^1.1.1, function-bind@~1.1.1: 79 | version "1.1.1" 80 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 81 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 82 | 83 | glob@~7.1.4: 84 | version "7.1.6" 85 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 86 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 87 | dependencies: 88 | fs.realpath "^1.0.0" 89 | inflight "^1.0.4" 90 | inherits "2" 91 | minimatch "^3.0.4" 92 | once "^1.3.0" 93 | path-is-absolute "^1.0.0" 94 | 95 | has-symbols@^1.0.0, has-symbols@^1.0.1: 96 | version "1.0.1" 97 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" 98 | integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== 99 | 100 | has@^1.0.3, has@~1.0.3: 101 | version "1.0.3" 102 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 103 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 104 | dependencies: 105 | function-bind "^1.1.1" 106 | 107 | inflight@^1.0.4: 108 | version "1.0.6" 109 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 110 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 111 | dependencies: 112 | once "^1.3.0" 113 | wrappy "1" 114 | 115 | inherits@2, inherits@~2.0.3: 116 | version "2.0.4" 117 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 118 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 119 | 120 | is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.1.5: 121 | version "1.1.5" 122 | resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" 123 | integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== 124 | 125 | is-date-object@^1.0.1: 126 | version "1.0.2" 127 | resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" 128 | integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== 129 | 130 | is-leap-year@^0.1.0: 131 | version "0.1.0" 132 | resolved "https://registry.yarnpkg.com/is-leap-year/-/is-leap-year-0.1.0.tgz#e3ee04508d878c3c68a5012361c5b806d1e0fbeb" 133 | integrity sha1-4+4EUI2HjDxopQEjYcW4BtHg++s= 134 | 135 | is-regex@^1.0.5: 136 | version "1.0.5" 137 | resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" 138 | integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== 139 | dependencies: 140 | has "^1.0.3" 141 | 142 | is-symbol@^1.0.2: 143 | version "1.0.3" 144 | resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" 145 | integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== 146 | dependencies: 147 | has-symbols "^1.0.1" 148 | 149 | minimatch@^3.0.4: 150 | version "3.0.4" 151 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 152 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 153 | dependencies: 154 | brace-expansion "^1.1.7" 155 | 156 | minimist@~1.2.0: 157 | version "1.2.5" 158 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 159 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 160 | 161 | object-inspect@^1.7.0: 162 | version "1.7.0" 163 | resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" 164 | integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== 165 | 166 | object-inspect@~1.6.0: 167 | version "1.6.0" 168 | resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" 169 | integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ== 170 | 171 | object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: 172 | version "1.1.1" 173 | resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" 174 | integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== 175 | 176 | object.assign@^4.1.0: 177 | version "4.1.0" 178 | resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" 179 | integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== 180 | dependencies: 181 | define-properties "^1.1.2" 182 | function-bind "^1.1.1" 183 | has-symbols "^1.0.0" 184 | object-keys "^1.0.11" 185 | 186 | once@^1.3.0: 187 | version "1.4.0" 188 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 189 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 190 | dependencies: 191 | wrappy "1" 192 | 193 | path-is-absolute@^1.0.0: 194 | version "1.0.1" 195 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 196 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 197 | 198 | path-parse@^1.0.6: 199 | version "1.0.6" 200 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" 201 | integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== 202 | 203 | resolve@~1.10.1: 204 | version "1.10.1" 205 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.1.tgz#664842ac960795bbe758221cdccda61fb64b5f18" 206 | integrity sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA== 207 | dependencies: 208 | path-parse "^1.0.6" 209 | 210 | resumer@~0.0.0: 211 | version "0.0.0" 212 | resolved "https://registry.yarnpkg.com/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759" 213 | integrity sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k= 214 | dependencies: 215 | through "~2.3.4" 216 | 217 | string.prototype.trim@~1.1.2: 218 | version "1.1.2" 219 | resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea" 220 | integrity sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo= 221 | dependencies: 222 | define-properties "^1.1.2" 223 | es-abstract "^1.5.0" 224 | function-bind "^1.0.2" 225 | 226 | string.prototype.trimleft@^2.1.1: 227 | version "2.1.1" 228 | resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" 229 | integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== 230 | dependencies: 231 | define-properties "^1.1.3" 232 | function-bind "^1.1.1" 233 | 234 | string.prototype.trimright@^2.1.1: 235 | version "2.1.1" 236 | resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" 237 | integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== 238 | dependencies: 239 | define-properties "^1.1.3" 240 | function-bind "^1.1.1" 241 | 242 | tape@~4.10.0: 243 | version "4.10.2" 244 | resolved "https://registry.yarnpkg.com/tape/-/tape-4.10.2.tgz#129fcf62f86df92687036a52cce7b8ddcaffd7a6" 245 | integrity sha512-mgl23h7W2yuk3N85FOYrin2OvThTYWdwbk6XQ1pr2PMJieyW2FM/4Bu/+kD/wecb3aZ0Enm+Syinyq467OPq2w== 246 | dependencies: 247 | deep-equal "~1.0.1" 248 | defined "~1.0.0" 249 | for-each "~0.3.3" 250 | function-bind "~1.1.1" 251 | glob "~7.1.4" 252 | has "~1.0.3" 253 | inherits "~2.0.3" 254 | minimist "~1.2.0" 255 | object-inspect "~1.6.0" 256 | resolve "~1.10.1" 257 | resumer "~0.0.0" 258 | string.prototype.trim "~1.1.2" 259 | through "~2.3.8" 260 | 261 | through@~2.3.4, through@~2.3.8: 262 | version "2.3.8" 263 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 264 | integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= 265 | 266 | wrappy@1: 267 | version "1.0.2" 268 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 269 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 270 | --------------------------------------------------------------------------------