From 2bebf2242e7bc9392a14206a0f0f36d3d5778e99 Mon Sep 17 00:00:00 2001 From: Ivan Gilchrist Date: Fri, 7 Sep 2018 14:37:16 -0700 Subject: [PATCH] Initial implementation of timespan-parser --- .eslintrc.json | 956 ++++++++++++++++++++++++++++++++++++++++++ .gitignore | 2 + README.md | 82 +++- package.json | 30 ++ test/.eslintrc.json | 7 + test/timespan.test.js | 22 + timespan.js | 109 +++++ 7 files changed, 1207 insertions(+), 1 deletion(-) create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 package.json create mode 100644 test/.eslintrc.json create mode 100644 test/timespan.test.js create mode 100644 timespan.js diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..57873f1 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,956 @@ +{ + "parserOptions": { + "ecmaVersion": 8 + }, + "env": { + "node": true + }, + "root": true, + "rules": { + // from eslint:recommended + "constructor-super": "error", + "no-class-assign": "error", + "no-const-assign": "error", + "no-dupe-class-members": "error", + "no-new-symbol": "error", + "no-this-before-super": "error", + "require-yield": "error", + + //------------------------------------------Best Practices-------------------------------------- + + // enforces getter/setter pairs in objects + "accessor-pairs": "off", + + // enforces return statements in callbacks of array"s methods + // http://eslint.org/docs/rules/array-callback-return + "array-callback-return": "error", + // treat var statements as if they were block scoped + "block-scoped-var": "error", + // specify the maximum cyclomatic complexity allowed in a program + "complexity": [ + "off", + 11 + ], + // enforce that class methods use "this" + // http://eslint.org/docs/rules/class-methods-use-this + "class-methods-use-this": [ + "error", + { + "exceptMethods": [] + } + ], + // require return statements to either always or never specify values + "consistent-return": "error", + // specify curly brace conventions for all control statements + "curly": [ + "error", + "multi-line" + ], + // require default case in switch statements + "default-case": [ + "error", + { + "commentPattern": "^no default$" + } + ], + // encourages use of dot notation whenever possible + "dot-notation": [ + "error", + { + "allowKeywords": true + } + ], + // enforces consistent newlines before or after dots + // http://eslint.org/docs/rules/dot-location + "dot-location": [ + "error", + "property" + ], + // require the use of === and !== + // http://eslint.org/docs/rules/eqeqeq + //"eqeqeq": [ + // "error", + // "always", + // { "null": "ignore" } + //], + // make sure for-in loops have an if statement + "guard-for-in": "error", + // disallow the use of alert, confirm, and prompt + "no-alert": "warn", + // disallow use of arguments.caller or arguments.callee + "no-caller": "error", + // disallow lexical declarations in case/default clauses + // http://eslint.org/docs/rules/no-case-declarations.html + "no-case-declarations": "error", + // disallow division operators explicitly at beginning of regular expression + // http://eslint.org/docs/rules/no-div-regex + "no-div-regex": "off", + // disallow else after a return in an if + "no-else-return": "error", + // disallow empty functions, except for standalone funcs/arrows + // http://eslint.org/docs/rules/no-empty-function + "no-empty-function": [ + "error", + { + "allow": [ + "arrowFunctions", + "functions", + "methods" + ] + } + ], + // disallow empty destructuring patterns + // http://eslint.org/docs/rules/no-empty-pattern + "no-empty-pattern": "error", + // disallow comparisons to null without a type-checking operator + "no-eq-null": "off", + // disallow use of eval() + "no-eval": "error", + // disallow adding to native types + "no-extend-native": "error", + // disallow unnecessary function binding + "no-extra-bind": "error", + // disallow Unnecessary Labels + // http://eslint.org/docs/rules/no-extra-label + "no-extra-label": "error", + // disallow fallthrough of case statements + "no-fallthrough": "error", + // disallow the use of leading or trailing decimal points in numeric literals + "no-floating-decimal": "error", + // disallow reassignments of native objects or read-only globals + // http://eslint.org/docs/rules/no-global-assign + "no-global-assign": [ + "error", + { + "exceptions": [] + } + ], + // deprecated in favor of no-global-assign + "no-native-reassign": "off", + // disallow implicit type conversions + // http://eslint.org/docs/rules/no-implicit-coercion + "no-implicit-coercion": [ + "off", + { + "boolean": false, + "number": true, + "string": true, + "allow": [] + } + ], + // disallow var and named functions in global scope + // http://eslint.org/docs/rules/no-implicit-globals + "no-implicit-globals": "off", + // disallow use of eval()-like methods + "no-implied-eval": "error", + // disallow this keywords outside of classes or class-like objects + "no-invalid-this": "off", + // disallow usage of __iterator__ property + "no-iterator": "error", + // disallow use of labels for anything other then loops and switches + "no-labels": [ + "error", + { + "allowLoop": false, + "allowSwitch": false + } + ], + // disallow unnecessary nested blocks + "no-lone-blocks": "error", + // disallow creation of functions within loops + "no-loop-func": "error", + // disallow magic numbers + // http://eslint.org/docs/rules/no-magic-numbers + "no-magic-numbers": [ + "off", + { + "ignore": [], + "ignoreArrayIndexes": true, + "enforceConst": true, + "detectObjects": false + } + ], + // disallow use of multiple spaces + "no-multi-spaces": [ + "error", + { + "ignoreEOLComments": true, + "exceptions": { "Property": true } + } + ], + // disallow use of multiline strings + "no-multi-str": "error", + // disallow use of new operator when not part of the assignment or comparison + "no-new": "error", + // disallow use of new operator for Function object + "no-new-func": "error", + // disallows creating new instances of String, Number, and Boolean + "no-new-wrappers": "error", + // disallow use of (old style) octal literals + "no-octal": "error", + + // disallow use of octal escape sequences in string literals, such as + // var foo = "Copyright \251"; + "no-octal-escape": "error", + // disallow reassignment of function parameters + // disallow parameter object manipulation + // rule: http://eslint.org/docs/rules/no-param-reassign.html + //"no-param-reassign": [ + // "error", + // { + // "props": false + // } + //], + // disallow usage of __proto__ property + "no-proto": "error", + // disallow declaring the same variable more then once + "no-redeclare": "error", + // disallow certain object properties + // http://eslint.org/docs/rules/no-restricted-properties + "no-restricted-properties": [ + "error", + { + "object": "arguments", + "property": "callee", + "message": "arguments.callee is deprecated" + }, + { + "property": "__defineGetter__", + "message": "Please use Object.defineProperty instead." + }, + { + "property": "__defineSetter__", + "message": "Please use Object.defineProperty instead." + } + ], + // disallow use of assignment in return statement + "no-return-assign": "off", // TODO: enable, but not for arrow functions. See https://github.com/eslint/eslint/issues/5150 + // disallow use of `javascript:` urls. + "no-script-url": "error", + // disallow self assignment + // http://eslint.org/docs/rules/no-self-assign + "no-self-assign": "error", + // disallow comparisons where both sides are exactly the same + "no-self-compare": "error", + // disallow use of comma operator + "no-sequences": "error", + // restrict what can be thrown as an exception + "no-throw-literal": "off", + // disallow unmodified conditions of loops + // http://eslint.org/docs/rules/no-unmodified-loop-condition + "no-unmodified-loop-condition": "off", + // disallow usage of expressions in statement position + "no-unused-expressions": [ + "error", + { + "allowShortCircuit": false, + "allowTernary": false + } + ], + // disallow unused labels + // http://eslint.org/docs/rules/no-unused-labels + "no-unused-labels": "error", + // disallow unnecessary .call() and .apply() + "no-useless-call": "off", + // disallow useless string concatenation + // http://eslint.org/docs/rules/no-useless-concat + "no-useless-concat": "error", + // disallow unnecessary string escaping + // http://eslint.org/docs/rules/no-useless-escape + "no-useless-escape": "off", + // disallow redundant return; keywords + // http://eslint.org/docs/rules/no-useless-return + "no-useless-return": "error", + // use let or const instead of var + // http://eslint.org/docs/rules/no-var + "no-var": "error", + "prefer-const": "error", + // disallow use of void operator + // http://eslint.org/docs/rules/no-void + "no-void": "error", + // disallow usage of configurable warning terms in comments: e.g. todo + "no-warning-comments": [ + "off", + { + "terms": [ + "todo", + "fixme", + "xxx" + ], + "location": "start" + } + ], + // disallow use of the with statement + "no-with": "error", + // require use of the second argument for parseInt() + "radix": "error", + // requires to declare all vars on top of their containing scope + "vars-on-top": "off", + // require immediate function invocation to be wrapped in parentheses + // http://eslint.org/docs/rules/wrap-iife.html + "wrap-iife": [ + "error", + "any", + { + "functionPrototypeMethods": false + } + ], + // require or disallow Yoda conditions + "yoda": "error", + //-----------------------------------Errors------------------------------------- + + + // require trailing commas in multiline object literals + "comma-dangle": [ + "error", + { + "arrays": "never", + "objects": "never", + "imports": "never", + "exports": "never", + "functions": "never" + } + ], + // disallow assignment in conditional expressions + "no-cond-assign": [ + "error", + "except-parens" + ], + // disallow use of console + "no-console": "warn", + // disallow use of constant expressions in conditions + "no-constant-condition": "warn", + // disallow control characters in regular expressions + "no-control-regex": "error", + // disallow use of debugger + "no-debugger": "error", + // disallow duplicate arguments in functions + "no-dupe-args": "error", + // disallow duplicate keys when creating object literals + "no-dupe-keys": "error", + // disallow a duplicate case label. + "no-duplicate-case": "error", + // disallow empty statements + "no-empty": [ + "error", + { "allowEmptyCatch": true } + ], + // disallow the use of empty character classes in regular expressions + "no-empty-character-class": "error", + // disallow assigning to the exception in a catch block + "no-ex-assign": "error", + // disallow double-negation boolean casts in a boolean context + // http://eslint.org/docs/rules/no-extra-boolean-cast + "no-extra-boolean-cast": "error", + // disallow unnecessary parentheses + // http://eslint.org/docs/rules/no-extra-parens + "no-extra-parens": [ + "off", + "all", + { + "conditionalAssign": true, + "nestedBinaryExpressions": false, + "returnAssign": false + } + ], + // disallow unnecessary semicolons + "no-extra-semi": "error", + // disallow overwriting functions written as function declarations + "no-func-assign": "error", + // disallow function or variable declarations in nested blocks + "no-inner-declarations": "error", + // disallow invalid regular expression strings in the RegExp constructor + "no-invalid-regexp": "error", + // disallow irregular whitespace outside of strings and comments + "no-irregular-whitespace": "error", + // disallow the use of object properties of the global object (Math and JSON) as functions + "no-obj-calls": "error", + // disallow use of Object.prototypes builtins directly + // http://eslint.org/docs/rules/no-prototype-builtins + "no-prototype-builtins": "error", + // disallow multiple spaces in a regular expression literal + "no-regex-spaces": "error", + // disallow sparse arrays + "no-sparse-arrays": "error", + // Disallow template literal placeholder syntax in regular strings + // http://eslint.org/docs/rules/no-template-curly-in-string + "no-template-curly-in-string": "error", + // Avoid code that looks like two expressions but is actually one + // http://eslint.org/docs/rules/no-unexpected-multiline + "no-unexpected-multiline": "error", + // disallow unreachable statements after a return, throw, continue, or break statement + "no-unreachable": "error", + // disallow return/throw/break/continue inside finally blocks + // http://eslint.org/docs/rules/no-unsafe-finally + "no-unsafe-finally": "error", + // disallow negating the left operand of relational operators + // http://eslint.org/docs/rules/no-unsafe-negation + "no-unsafe-negation": "error", + // disallow negation of the left operand of an in expression + // deprecated in favor of no-unsafe-negation + "no-negated-in-lhs": "off", + // disallow comparisons with the value NaN + "use-isnan": "error", + // ensure JSDoc comments are valid + // http://eslint.org/docs/rules/valid-jsdoc + "valid-jsdoc": "off", + // ensure that the results of typeof are compared against a valid string + // http://eslint.org/docs/rules/valid-typeof + "valid-typeof": [ + "error", + { + "requireStringLiterals": true + } + ], + + //------------------------------- strict-------------------- + + "strict": "error", + + //------------------------------ styling --------------------- + + // enforce spacing inside array brackets + "array-bracket-spacing": [ "error", "never" ], + + // enforce spacing inside single-line blocks + // http://eslint.org/docs/rules/block-spacing + "block-spacing": [ "error", "always" ], + + // enforce one true brace style + "brace-style": [ + "error", + "stroustrup", + { "allowSingleLine": true } + ], + + // require camel case names + "camelcase": [ + "error", + { "properties": "never" } + ], + + // enforce spacing before and after comma + "comma-spacing": [ + "error", + { + "before": false, + "after": true + } + ], + + // enforce one true comma style + "comma-style": [ "error", "last" ], + + // disallow padding inside computed properties + "computed-property-spacing": [ "error", "never" ], + + // enforces consistent naming when capturing the current execution context + "consistent-this": "off", + + // enforce newline at the end of file, with no multiple empty lines + "eol-last": [ "error", "always" ], + + // enforce spacing between functions and their invocations + // http://eslint.org/docs/rules/func-call-spacing + "func-call-spacing": [ "error", "never" ], + + // requires function names to match the name of the variable or property to which they are + // assigned + // http://eslint.org/docs/rules/func-name-matching + "func-name-matching": [ + "off", + "always", + { + "includeCommonJSModuleExports": false + } + ], + + // require function expressions to have a name + // http://eslint.org/docs/rules/func-names + "func-names": "off", + + // enforces use of function declarations or expressions + // http://eslint.org/docs/rules/func-style + // TODO: enable + "func-style": [ + "error", + "declaration", + { "allowArrowFunctions": true } + ], + + // Blacklist certain identifiers to prevent them being used + // http://eslint.org/docs/rules/id-blacklist + "id-blacklist": "off", + + // this option enforces minimum and maximum identifier lengths + // (variable names, property names etc.) + "id-length": "off", + + // require identifiers to match the provided regular expression + "id-match": "off", + + // this option sets a specific tab width for your code + // http://eslint.org/docs/rules/indent + "indent": [ + "error", + 4, + { + "SwitchCase": 1, + "VariableDeclarator": 1, + "outerIIFEBody": 1, + // MemberExpression: null, + // CallExpression: { + // parameters: null, + // }, + "FunctionDeclaration": { + "parameters": 1, + "body": 1 + }, + "FunctionExpression": { + "parameters": 1, + "body": 1 + } + } + ], + + // specify whether double or single quotes should be used in JSX attributes + // http://eslint.org/docs/rules/jsx-quotes + "jsx-quotes": [ "off", "prefer-double" ], + + // enforces spacing between keys and values in object literal properties + "key-spacing": [ + "error", + { + "beforeColon": false, + "afterColon": true, + "mode": "minimum" + } + ], + + // require a space before & after certain keywords + "keyword-spacing": [ + "error", + { + "before": true, + "after": true, + "overrides": { + "return": { "after": true }, + "throw": { "after": true }, + "case": { "after": true } + } + } + ], + + // enforce position of line comments + // http://eslint.org/docs/rules/line-comment-position + // TODO: enable? + "line-comment-position": [ + "off", + { + "position": "above", + "ignorePattern": "", + "applyDefaultPatterns": true + } + ], + + // disallow mixed "LF" and "CRLF" as linebreaks + // http://eslint.org/docs/rules/linebreak-style + // "linebreak-style": "off", + + // enforces empty lines around comments + "lines-around-comment": "off", + + // require or disallow newlines around directives + // http://eslint.org/docs/rules/lines-around-directive + "lines-around-directive": [ + "error", + { + "before": "always", + "after": "always" + } + ], + + // specify the maximum depth that blocks can be nested + "max-depth": [ "off", 4 ], + + // specify the maximum length of a line in your program + // http://eslint.org/docs/rules/max-len + "max-len": [ + "error", + 160, + 2, + { + "ignoreUrls": true, + "ignoreComments": false, + "ignoreRegExpLiterals": true, + "ignoreStrings": true, + "ignoreTemplateLiterals": true + } + ], + + // specify the max number of lines in a file + // http://eslint.org/docs/rules/max-lines + "max-lines": [ + "off", + { + "max": 300, + "skipBlankLines": true, + "skipComments": true + } + ], + + // specify the maximum depth callbacks can be nested + "max-nested-callbacks": "off", + + // limits the number of parameters that can be used in the function declaration. + "max-params": [ "off", 3 ], + + // specify the maximum number of statement allowed in a function + "max-statements": [ "off", 10 ], + + // restrict the number of statements per line + // http://eslint.org/docs/rules/max-statements-per-line + "max-statements-per-line": [ + "off", + { "max": 1 } + ], + + // require multiline ternary + // http://eslint.org/docs/rules/multiline-ternary + // TODO: enable? + "multiline-ternary": [ "off", "never" ], + + // require a capital letter for constructors + "new-cap": [ + "error", + { + "newIsCap": true, + "newIsCapExceptions": [], + "capIsNew": false, + "capIsNewExceptions": [ "Immutable.Map", "Immutable.Set", "Immutable.List" ] + } + ], + + // disallow the omission of parentheses when invoking a constructor with no arguments + // http://eslint.org/docs/rules/new-parens + "new-parens": "error", + + // allow/disallow an empty newline after var statement + "newline-after-var": "off", + + // http://eslint.org/docs/rules/newline-before-return + "newline-before-return": "off", + + // enforces new line after each method call in the chain to make it + // more readable and easy to maintain + // http://eslint.org/docs/rules/newline-per-chained-call + "newline-per-chained-call": [ + "error", + { "ignoreChainWithDepth": 4 } + ], + + // disallow use of the Array constructor + "no-array-constructor": "error", + + // disallow use of bitwise operators + // http://eslint.org/docs/rules/no-bitwise + "no-bitwise": "off", + + // disallow use of the continue statement + // http://eslint.org/docs/rules/no-continue + "no-continue": "error", + + // disallow comments inline after code + "no-inline-comments": "off", + + // disallow if as the only statement in an else block + // http://eslint.org/docs/rules/no-lonely-if + "no-lonely-if": "error", + + // disallow un-paren"d mixes of different operators + // http://eslint.org/docs/rules/no-mixed-operators + "no-mixed-operators": [ + "error", + { + "groups": [ + [ "+", "-", "*", "/", "%", "**" ], + [ "&", "|", "^", "~", "<<", ">>", ">>>" ], + [ "==", "!=", "===", "!==", ">", ">=", "<", "<=" ], + [ "&&", "||" ], + [ "in", "instanceof" ] + ], + "allowSamePrecedence": false + } + ], + + // disallow mixed spaces and tabs for indentation + "no-mixed-spaces-and-tabs": "error", + + // disallow multiple empty lines and only one newline at the end + "no-multiple-empty-lines": [ + "error", + { + "max": 2, + "maxEOF": 1 + } + ], + + // disallow negated conditions + // http://eslint.org/docs/rules/no-negated-condition + "no-negated-condition": "off", + + // disallow nested ternary expressions + "no-nested-ternary": "off", + + // disallow use of the Object constructor + "no-new-object": "error", + + // disallow use of unary operators, ++ and -- + // http://eslint.org/docs/rules/no-plusplus + //"no-plusplus": "error", + + // disallow certain syntax forms + // http://eslint.org/docs/rules/no-restricted-syntax + "no-restricted-syntax": [ + "error", + "ForInStatement", + "LabeledStatement", + "WithStatement" + ], + + // disallow space between function identifier and application + "no-spaced-func": "error", + + // disallow tab characters entirely + "no-tabs": "error", + + // disallow the use of ternary operators + "no-ternary": "off", + + // disallow trailing whitespace at the end of lines + "no-trailing-spaces": "error", + + // disallow dangling underscores in identifiers + "no-underscore-dangle": [ + "error", + { "allowAfterThis": false } + ], + + // disallow the use of Boolean literals in conditional expressions + // also, prefer `a || b` over `a ? a : b` + // http://eslint.org/docs/rules/no-unneeded-ternary + "no-unneeded-ternary": [ + "error", + { "defaultAssignment": false } + ], + + // disallow whitespace before properties + // http://eslint.org/docs/rules/no-whitespace-before-property + "no-whitespace-before-property": "error", + + // require padding inside curly braces + "object-curly-spacing": [ "error", "always" ], + + // enforce line breaks between braces + // http://eslint.org/docs/rules/object-curly-newline + // TODO: enable once https://github.com/eslint/eslint/issues/6488 is resolved + "object-curly-newline": [ + "off", + { + "ObjectExpression": { + "minProperties": 0, + "multiline": true + }, + "ObjectPattern": { + "minProperties": 0, + "multiline": true + } + } + ], + + // enforce "same line" or "multiple line" on object properties. + // http://eslint.org/docs/rules/object-property-newline + "object-property-newline": [ + "error", + { + "allowMultiplePropertiesPerLine": true + } + ], + + // allow just one var statement per function + "one-var": [ + "error", + { + "initialized": "never", + "uninitialized": "always" + } + ], + + // require a newline around variable declaration + // http://eslint.org/docs/rules/one-var-declaration-per-line + "one-var-declaration-per-line": [ "error" ], + + // require assignment operator shorthand where possible or prohibit it entirely + // http://eslint.org/docs/rules/operator-assignment + "operator-assignment": [ "error", "always" ], + + // enforce operators to be placed before or after line breaks + "operator-linebreak": "off", + + // enforce padding within blocks + "padded-blocks": [ "error", "never" ], + + // require quotes around object literal property names + // http://eslint.org/docs/rules/quote-props.html + "quote-props": [ + "error", + "as-needed", + { + "keywords": true + } + ], + + // specify whether double or single quotes should be used + "quotes": [ + "error", + "double", + { "avoidEscape": true } + ], + + // do not require jsdoc + // http://eslint.org/docs/rules/require-jsdoc + "require-jsdoc": "off", + + // require or disallow use of semicolons instead of ASI + "semi": [ "error", "always" ], + + // enforce spacing before and after semicolons + "semi-spacing": [ + "error", + { + "before": false, + "after": true + } + ], + + // requires object keys to be sorted + "sort-keys": [ + "off", + "asc", + { + "caseSensitive": false, + "natural": true + } + ], + + // sort variables within the same declaration block + "sort-vars": "off", + + // require or disallow space before blocks + "space-before-blocks": "error", + + // require or disallow space before function opening parenthesis + // http://eslint.org/docs/rules/space-before-function-paren + "space-before-function-paren": [ + "error", + { + "anonymous": "always", + "named": "never", + "asyncArrow": "always" + } + ], + + // require or disallow spaces inside parentheses + "space-in-parens": [ "error", "never" ], + + // require spaces around operators + "space-infix-ops": "error", + + // Require or disallow spaces before/after unary operators + // http://eslint.org/docs/rules/space-unary-ops + "space-unary-ops": [ + "error", + { + "words": true, + "nonwords": false, + "overrides": { + } + } + ], + + // require or disallow a space immediately following the // or /* in a comment + // http://eslint.org/docs/rules/spaced-comment + // TODO: semver-major: set balanced to "false" + //"spaced-comment": [ + // "error", + // "always", + // { + // "line": { + // "exceptions": [ "-", "+" ], + // "markers": [ "=", "!" ] // space here to support sprockets directives + // }, + // "block": { + // "exceptions": [ "-", "+" ], + // "markers": [ "=", "!" ], // space here to support sprockets directives + // "balanced": false + // } + // } + //], + + // require or disallow the Unicode Byte Order Mark + // http://eslint.org/docs/rules/unicode-bom + "unicode-bom": [ "error", "never" ], + + // require regex literals to be wrapped in parentheses + "wrap-regex": "off", + + // ------------------------ Variables -------------------------- + // enforce or disallow variable initializations at definition + "init-declarations": "off", + + // disallow the catch clause parameter name being the same as a variable in the outer scope + "no-catch-shadow": "off", + + // disallow deletion of variables + "no-delete-var": "error", + + // disallow labels that share a name with a variable + // http://eslint.org/docs/rules/no-label-var + "no-label-var": "error", + + // disallow specific globals + "no-restricted-globals": "off", + + // disallow declaration of variables already declared in the outer scope + "no-shadow": "error", + + // disallow shadowing of names such as arguments + "no-shadow-restricted-names": "error", + + // disallow use of undeclared variables unless mentioned in a /*global */ block + "no-undef": "error", + + // disallow use of undefined when initializing variables + "no-undef-init": "error", + + // disallow use of undefined variable + // http://eslint.org/docs/rules/no-undefined + // TODO: enable? + "no-undefined": "off", + + // disallow declaration of variables that are not used in the code + "no-unused-vars": [ + "error", + { + "vars": "local", + "args": "none" + } + ], + + // disallow use of variables before they are defined + "no-use-before-define": [ + "error", + { "functions": false } + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df8e062 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules +shrinkwrap.yaml diff --git a/README.md b/README.md index 36935a2..6b68192 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,82 @@ # timespan-parser -Node package for parsing timespans like systemd.time(7) +Node package for parsing timespans like [systemd.time(7)](https://www.freedesktop.org/software/systemd/man/systemd.time.html). The following is adapted from its man page: + +> ## PARSING TIME SPANS +> When parsing, systemd will accept the same time span syntax. +> Separating spaces may be omitted. The following time units are +> understood: +> +> * msec, ms +> * seconds, second, sec, s +> * minutes, minute, min, m +> * hours, hour, hr, h +> * days, day, d +> * weeks, week, w +> * months, month, M (defined as 30.44 days) +> * years, year, y (defined as 365.25 days) +> +> If no time unit is specified, seconds are assumed. +> +> Examples for valid time span specifications: +> +> 2 h +> 2hours +> 48hr +> 1y 12month +> 55s500ms +> 300ms20s 5day + +## Install + +```bash +$ npm install timespan-parser +``` + +## Usage + +```javascript +const timespan = require("timespan-parser"); + +timespan.parse("17y 4w 6d 3h 45m 12s"); // returns 539430312 +timespan.getString(539430312); // returns "17y 4w 6d 3h 45m 12s" +``` + +## API +### `timespan.parse(timespan [, unit])` +*Converts a timespan string into a number of `unit`s.*\ +**`timespan`** - *string* - The timespan string to be parsed.\ +**`unit`** - *string* - The unit to use for the conversion. Defaults to `"seconds"`.\ +**Return Value** - *number* - The number of `unit`s represented by `timespan`. + +### `timespan.getString(value [, unit])` +*Converts a numeric value to a timespan string. The timespan string produced uses the shortest label available.*\ +**`value`** - *number* - The number of `unit`s to convert to a timespan string.\ +**`unit`** - *string* - The unit that `value` represents. Defaults to `"seconds"`.\ +**Return Value** - *string* - A timespan string. + +### `timespan(unit)` +*Returns a new `timespan` object with the default unit changed from `"seconds"` to `unit`.*\ +**`unit`** - *string* - The new default unit.\ +**Return Value** - *object* - The same functions as above, with the default unit set to `unit`. + +## Examples + +```javascript +timespan.parse("4 hours 30 seconds"); // returns 14430 +timespan.parse("4 hours 30 seconds", "hours"); // returns 4.008333333333334 +timespan.parse("4 hours 30 seconds", "msec"); // returns 14430000 +timespan.parse("300ms20s5day"); // returns 432020.3 +timespan.parse("300ms20s5day", "y"); // returns 0.013689897203843131 +timespan.getString(456789); // returns "5d 6h 53m 9s" +timespan.getString(0.013689897203843131, "y"); // returns "5d 20s 300ms" +``` + +Changing the default unit: + +```javascript +const timespan = require("timespan-parser")("msec"); // Use milliseconds as the default unit + +timespan.parse("4 hours 30 seconds"); // returns 14430000 +timespan.parse("300ms20s5day"); // returns 432020300 +timespan.getString(456789); // returns "7m 36s 789ms" +``` diff --git a/package.json b/package.json new file mode 100644 index 0000000..96a118a --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "timespan-parser", + "version": "1.0.0", + "description": "Parses timespans like systemd.time(7)", + "main": "timespan.js", + "scripts": { + "test": "mocha" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/gilly3/timespan-parser.git" + }, + "keywords": [ + "timespan", + "systemd.time" + ], + "author": "Ivan Gilchrist", + "license": "MIT", + "bugs": { + "url": "https://github.com/gilly3/timespan-parser/issues" + }, + "homepage": "https://github.com/gilly3/timespan-parser#readme", + "engines": { + "node": ">=6.0.0" + }, + "devDependencies": { + "eslint": "^5.5.0", + "mocha": "^5.2.0" + } +} diff --git a/test/.eslintrc.json b/test/.eslintrc.json new file mode 100644 index 0000000..1f8d845 --- /dev/null +++ b/test/.eslintrc.json @@ -0,0 +1,7 @@ +{ + "env": { "mocha": true }, + "rules": { + // allow usage of expressions in statement position for chai + "no-unused-expressions": "off" + } +} \ No newline at end of file diff --git a/test/timespan.test.js b/test/timespan.test.js new file mode 100644 index 0000000..d6ea087 --- /dev/null +++ b/test/timespan.test.js @@ -0,0 +1,22 @@ +"use strict"; + +const assert = require("assert"); +const timespan = require("../timespan"); + +describe("parse", function () { + it("parses all systemd.time sample formats correctly", function () { + assert.strictEqual(timespan.parse("2 h"), 2 * 60 * 60, "2 h"); + assert.strictEqual(timespan.parse("2hours"), 2 * 60 * 60, "2hours"); + assert.strictEqual(timespan.parse("48hr"), 48 * 60 * 60, "48hr"); + assert.strictEqual(timespan.parse("1y 12month"), (365.25 * 24 * 60 * 60) + (12 * 30.44 * 24 * 60 * 60), "1y 12month"); + assert.strictEqual(timespan.parse("55s500ms"), 55.5, "55s500ms"); + assert.strictEqual(timespan.parse("300ms20s 5day"), 0.3 + 20 + (5 * 24 * 60 * 60), "300ms20s 5day"); + }); +}); + +describe("getString", function () { + it("returns parseable time strings", function () { + const value = Math.floor(Math.random() * 5000000000); + assert.strictEqual(timespan.parse(timespan.getString(value)), value); + }); +}); diff --git a/timespan.js b/timespan.js new file mode 100644 index 0000000..7e0d434 --- /dev/null +++ b/timespan.js @@ -0,0 +1,109 @@ +"use strict"; + +const assert = require("assert"); + +const ms = 1; +const s = 1000 * ms; +const m = 60 * s; +const h = 60 * m; +const d = 24 * h; +const w = 7 * d; +const M = 30.44 * d; +const y = 365.25 * d; + +const UNITS = { + msec: ms, + ms, + seconds: s, + second: s, + sec: s, + "": s, + s, + minutes: m, + minute: m, + min: m, + m, + hours: h, + hour: h, + hr: h, + h, + days: d, + day: d, + d, + weeks: w, + week: w, + w, + months: M, + month: M, + M, + years: y, + year: y, + y +}; + +function init(defaultUnit) { + const units = Object.assign({}, UNITS); + if (defaultUnit) { + assert.strictEqual(typeof defaultUnit, "string", `Invalid unit, expected string but got ${typeof defaultUnit}`); + const val = units[defaultUnit]; + assert.ok(val, `Unknown unit ${defaultUnit}`); + units[""] = val; + } + + return { + parse, + getString + }; + + function parse(timespan, unit) { + assert.ok(timespan, "Expected timespan"); + assert.strictEqual(typeof timespan, "string", `Invalid timespan, expected string but got ${typeof timespan}`); + if (unit == null) { + unit = ""; + } + assert.strictEqual(typeof unit, "string", `Invalid unit, expected string but got ${typeof unit}`); + const divider = units[unit]; + assert.ok(divider, `Unknown unit ${unit}`); + const tss = /^\s*(\d+\s*[a-z]*\s*)+$/.test(timespan) && timespan.match(/\d+\s*[a-z]*\s*/g); + assert.ok(tss, `Invalid format for timespan ${timespan}`); + const value = tss.reduce((sum, ts) => sum + getValue(ts), 0); + return value / divider; + } + + function getValue(ts) { + const n = parseInt(ts, 10); + const suf = ts.replace(/[\d\s]/g, ""); + const mutliplier = units[suf]; + assert.ok(mutliplier, `Invalid timespan, unknown unit ${suf}`); + return n * mutliplier; + } + + function getString(value, unit) { + assert.strictEqual(typeof value, "number", `Invalid value, expected number but got ${typeof value}`); + assert.ok(isFinite(value), `Invalid value ${value}`); + if (unit == null) { + unit = ""; + } + assert.strictEqual(typeof unit, "string", `Invalid unit, expected string but got ${typeof unit}`); + assert.ok(units[unit], `Unknown unit ${unit}`); + const tss = []; + value = Math.round(value * units[unit]); + const writeValue = unitName => { + const unitVal = units[unitName]; + if (value >= unitVal) { + tss.push(Math.floor(value / unitVal) + unitName); + value %= unitVal; + } + }; + writeValue("y"); + writeValue("w"); + writeValue("d"); + writeValue("h"); + writeValue("m"); + writeValue("s"); + writeValue("ms"); + return tss.join(" "); + } +} + +module.exports = Object.assign(init, init());