Repository: moment/luxon (js)

Issue:
Add a version const (or at least a comment)
We can't tell the version of Luxon by reading an API constant, or even looking at the source. This is common practice in Javascript libraries.

Thanks!

Pull request:
Export VERSION information
This is an attempt to resolve #747 and #799.

Closes #800.

I've read the [contribution guide](https://github.com/pgcalixto/luxon/blob/master/contributing.md), but I do not know it there is anything to update in the documentation regarding the exports.

Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..dca9e456
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,24 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ""
+labels: bug
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Please share a minimal code example that triggers the problem:
+
+**Actual vs Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Desktop (please complete the following information):**
+
+- OS: [e.g. iOS]
+- Browser [e.g. Chrome 84, safari 14.0]
+- Luxon version [e.g. 1.25.0]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..34981977
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,18 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ""
+labels: enhancement
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/README.md b/README.md
index 44e59ba6..84bda4f3 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 [![MIT License][license-image]][license] [![Build Status][travis-image]][travis-url] [![NPM version][npm-version-image]][npm-url] [![Coverage Status][test-coverage-image]][test-coverage-url] [![Doc coverage][doc-coverage-image]][doc-url] [![PRs welcome][contributing-image]][contributing-url]
 
-Luxon is a library for working with dates and times in Javascript.
+Luxon is a library for working with dates and times in JavaScript.
 
 ```js
 DateTime.now().setZone("America/New_York").minus({ weeks: 1 }).endOf("day").toISO();
diff --git a/docs/math.md b/docs/math.md
index 1a08c9a6..c04b4db6 100644
--- a/docs/math.md
+++ b/docs/math.md
@@ -35,7 +35,7 @@ These units use time math:
  * **Minutes** are always 60 seconds
  * **Seconds** are always 1000 milliseconds
 
-Don't worry about leap seconds. Javascript and most other programming environments don't account for them; they just happen as abrupt, invisible changes to the underlying system's time.
+Don't worry about leap seconds. JavaScript and most other programming environments don't account for them; they just happen as abrupt, invisible changes to the underlying system's time.
 
 ### How to think about calendar math
 
diff --git a/docs/tour.md b/docs/tour.md
index d1328822..98d73bb7 100644
--- a/docs/tour.md
+++ b/docs/tour.md
@@ -1,6 +1,6 @@
 # A quick tour
 
-Luxon is a library that makes it easier to work with dates and times in Javascript. If you want, add and subtract them, format and parse them, ask them hard questions, and so on, Luxon provides a much easier and comprehensive interface than the native types it wraps. We're going to talk about the most immediately useful subset of that interface.
+Luxon is a library that makes it easier to work with dates and times in JavaScript. If you want, add and subtract them, format and parse them, ask them hard questions, and so on, Luxon provides a much easier and comprehensive interface than the native types it wraps. We're going to talk about the most immediately useful subset of that interface.
 
 This is going to be a bit brisk, but keep in mind that the API docs are comprehensive, so if you want to know more, feel free to [dive into them](../identifiers.html).
 
@@ -12,7 +12,7 @@ The most important class in Luxon is [DateTime](../class/src/datetime.js~DateTim
 var dt = DateTime.local(2017, 5, 15, 8, 30);
 ```
 
-[DateTime.local](../class/src/datetime.js~DateTime.html#static-method-local) takes any number of arguments, all the way out to milliseconds. Underneath, this is similar to a Javascript Date object. But we've decorated it with lots of useful methods.
+[DateTime.local](../class/src/datetime.js~DateTime.html#static-method-local) takes any number of arguments, all the way out to milliseconds. Underneath, this is similar to a JavaScript Date object. But we've decorated it with lots of useful methods.
 
 ## Creating a DateTime
 
diff --git a/package-lock.json b/package-lock.json
index ad3d762d..14de6a9a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "luxon",
-  "version": "1.24.1",
+  "version": "1.25.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -8398,9 +8398,9 @@
       }
     },
     "node-fetch": {
-      "version": "2.6.0",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
-      "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==",
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
+      "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
       "dev": true
     },
     "node-int64": {
@@ -10613,19 +10613,26 @@
         "is-typedarray": "^1.0.0"
       }
     },
-    "uglify-js": {
-      "version": "3.9.0",
-      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.0.tgz",
-      "integrity": "sha512-j5wNQBWaql8gr06dOUrfaohHlscboQZ9B8sNsoK5o4sBjm7Ht9dxSbrMXyktQpA16Acaij8AcoozteaPYZON0g==",
+    "uglify-es": {
+      "version": "3.3.9",
+      "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz",
+      "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==",
       "dev": true,
       "requires": {
-        "commander": "~2.20.3"
+        "commander": "~2.13.0",
+        "source-map": "~0.6.1"
       },
       "dependencies": {
         "commander": {
-          "version": "2.20.3",
-          "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
-          "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+          "version": "2.13.0",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz",
+          "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
           "dev": true
         }
       }
diff --git a/package.json b/package.json
index 73d994bb..e55d9086 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,7 @@
     "rollup": "^1.32.1",
     "rollup-plugin-babel": "^4.4.0",
     "rollup-plugin-babel-minify": "^6.2.0",
-    "uglify-js": "^3.9.0"
+    "uglify-es": "^3.3.9"
   },
   "main": "build/node/luxon.js",
   "module": "src/luxon.js",
diff --git a/scripts/release b/scripts/release
index 43bd6d88..df80bcc3 100755
--- a/scripts/release
+++ b/scripts/release
@@ -1,4 +1,6 @@
 #!/usr/bin/env bash
+./scripts/version || exit $?
+
 npm run build
 npm run docs
 npm run site
diff --git a/scripts/tag b/scripts/tag
index 724baae2..9f19d5f0 100755
--- a/scripts/tag
+++ b/scripts/tag
@@ -1,10 +1,7 @@
 #!/usr/bin/env bash
+./scripts/version || exit $?
 
 PACKAGE_VERSION=$(node -p "require('./package.json').version")
 
-if [ $(git tag -l "$PACKAGE_VERSION") ]; then
-  echo "tag already exists"
-else
-  git tag $PACKAGE_VERSION
-  git push origin master $PACKAGE_VERSION 
-fi
+git tag $PACKAGE_VERSION
+git push origin master $PACKAGE_VERSION
diff --git a/scripts/version b/scripts/version
new file mode 100755
index 00000000..1dd81a94
--- /dev/null
+++ b/scripts/version
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+abort() {
+  echo "ERROR: $1"
+  exit 1
+}
+
+PACKAGE_VERSION=$(node -p "require('./package.json').version")
+PACKAGE_LOCK_VERSION=$(node -p "require('./package-lock.json').version")
+EXPORTED_VERSION=$(grep -oP "(?<=const VERSION = \").*(?=\";)" ./src/luxon.js)
+
+if [ $PACKAGE_VERSION != $PACKAGE_LOCK_VERSION ]; then
+  abort "package-lock.json's version differs from package.json's"
+elif [ $PACKAGE_VERSION != $EXPORTED_VERSION ]; then
+  abort "exported version differs from package.json's"
+elif [ $(git tag -l "$PACKAGE_VERSION") ]; then
+  abort "tag already exists"
+fi
diff --git a/site/index.html b/site/index.html
index 93042b8d..6890ecf6 100644
--- a/site/index.html
+++ b/site/index.html
@@ -40,7 +40,7 @@
       <h1 class="display-1">Luxon</h1>
 
       <p class="lead">
-        A powerful, modern, and friendly wrapper for Javascript dates and times.
+        A powerful, modern, and friendly wrapper for JavaScript dates and times.
       </p>
 
       <pre>
diff --git a/src/datetime.js b/src/datetime.js
index 856f8c73..97159b98 100644
--- a/src/datetime.js
+++ b/src/datetime.js
@@ -531,8 +531,8 @@ export default class DateTime {
   }
 
   /**
-   * Create a DateTime from a Javascript Date object. Uses the default zone.
-   * @param {Date} date - a Javascript Date object
+   * Create a DateTime from a JavaScript Date object. Uses the default zone.
+   * @param {Date} date - a JavaScript Date object
    * @param {Object} options - configuration options for the DateTime
    * @param {string|Zone} [options.zone='local'] - the zone to place the DateTime into
    * @return {DateTime}
@@ -605,7 +605,7 @@ export default class DateTime {
   }
 
   /**
-   * Create a DateTime from a Javascript object with keys like 'year' and 'hour' with reasonable defaults.
+   * Create a DateTime from a JavaScript object with keys like 'year' and 'hour' with reasonable defaults.
    * @param {Object} obj - the object to create the DateTime from
    * @param {number} obj.year - a year, such as 1987
    * @param {number} obj.month - a month, 1-12
@@ -1389,6 +1389,7 @@ export default class DateTime {
    * @param {string} unit - The unit to go to the beginning of. Can be 'year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', or 'millisecond'.
    * @example DateTime.local(2014, 3, 3).startOf('month').toISODate(); //=> '2014-03-01'
    * @example DateTime.local(2014, 3, 3).startOf('year').toISODate(); //=> '2014-01-01'
+   * @example DateTime.local(2014, 3, 3).startOf('week').toISODate(); //=> '2014-03-03', weeks always start on Mondays
    * @example DateTime.local(2014, 3, 3, 5, 30).startOf('day').toISOTime(); //=> '00:00.000-05:00'
    * @example DateTime.local(2014, 3, 3, 5, 30).startOf('hour').toISOTime(); //=> '05:00:00.000-05:00'
    * @return {DateTime}
@@ -1440,6 +1441,7 @@ export default class DateTime {
    * @param {string} unit - The unit to go to the end of. Can be 'year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', or 'millisecond'.
    * @example DateTime.local(2014, 3, 3).endOf('month').toISO(); //=> '2014-03-31T23:59:59.999-05:00'
    * @example DateTime.local(2014, 3, 3).endOf('year').toISO(); //=> '2014-12-31T23:59:59.999-05:00'
+   * @example DateTime.local(2014, 3, 3).endOf('week').toISO(); // => '2014-03-09T23:59:59.999-05:00', weeks start on Mondays
    * @example DateTime.local(2014, 3, 3, 5, 30).endOf('day').toISO(); //=> '2014-03-03T23:59:59.999-05:00'
    * @example DateTime.local(2014, 3, 3, 5, 30).endOf('hour').toISO(); //=> '2014-03-03T05:59:59.999-05:00'
    * @return {DateTime}
@@ -1707,7 +1709,7 @@ export default class DateTime {
   }
 
   /**
-   * Returns a Javascript object with this DateTime's year, month, day, and so on.
+   * Returns a JavaScript object with this DateTime's year, month, day, and so on.
    * @param opts - options for generating the object
    * @param {boolean} [opts.includeConfig=false] - include configuration attributes in the output
    * @example DateTime.now().toObject() //=> { year: 2017, month: 4, day: 22, hour: 20, minute: 49, second: 42, millisecond: 268 }
@@ -1727,7 +1729,7 @@ export default class DateTime {
   }
 
   /**
-   * Returns a Javascript Date equivalent to this DateTime.
+   * Returns a JavaScript Date equivalent to this DateTime.
    * @return {Date}
    */
   toJSDate() {
diff --git a/src/duration.js b/src/duration.js
index 3c6889a6..9bddefe8 100644
--- a/src/duration.js
+++ b/src/duration.js
@@ -216,7 +216,7 @@ export default class Duration {
   }
 
   /**
-   * Create a Duration from a Javascript object with keys like 'years' and 'hours.
+   * Create a Duration from a JavaScript object with keys like 'years' and 'hours.
    * If this object is empty then a zero milliseconds duration is returned.
    * @param {Object} obj - the object to create the DateTime from
    * @param {number} obj.years
@@ -383,7 +383,7 @@ export default class Duration {
   }
 
   /**
-   * Returns a Javascript object with this Duration's values.
+   * Returns a JavaScript object with this Duration's values.
    * @param opts - options for generating the object
    * @param {boolean} [opts.includeConfig=false] - include configuration attributes in the output
    * @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toObject() //=> { years: 1, days: 6, seconds: 2 }
@@ -762,8 +762,14 @@ export default class Duration {
       return false;
     }
 
+    function eq(v1, v2) {
+      // Consider 0 and undefined as equal
+      if (v1 === undefined || v1 === 0) return v2 === undefined || v2 === 0;
+      return v1 === v2;
+    }
+
     for (const u of orderedUnits) {
-      if (this.values[u] !== other.values[u]) {
+      if (!eq(this.values[u], other.values[u])) {
         return false;
       }
     }
diff --git a/src/luxon.js b/src/luxon.js
index 31b97387..f84bb770 100644
--- a/src/luxon.js
+++ b/src/luxon.js
@@ -9,7 +9,10 @@ import InvalidZone from "./zones/invalidZone.js";
 import LocalZone from "./zones/localZone.js";
 import Settings from "./settings.js";
 
+const VERSION = "1.25.0";
+
 export {
+  VERSION,
   DateTime,
   Duration,
   Interval,
diff --git a/src/zones/localZone.js b/src/zones/localZone.js
index b8cbcdc2..dae9b2a3 100644
--- a/src/zones/localZone.js
+++ b/src/zones/localZone.js
@@ -4,7 +4,7 @@ import Zone from "../zone.js";
 let singleton = null;
 
 /**
- * Represents the local zone for this Javascript environment.
+ * Represents the local zone for this JavaScript environment.
  * @implements {Zone}
  */
 export default class LocalZone extends Zone {
diff --git a/tasks/build.js b/tasks/build.js
index 775bc52a..e5643a04 100644
--- a/tasks/build.js
+++ b/tasks/build.js
@@ -5,7 +5,7 @@ const rollup = require("rollup"),
   rollupMinify = require("rollup-plugin-babel-minify"),
   rollupNode = require("@rollup/plugin-node-resolve"),
   rollupCommonJS = require("@rollup/plugin-commonjs"),
-  UglifyJS = require("uglify-js"),
+  UglifyES = require("uglify-es"),
   fs = require("fs");
 
 // For some reason, the minifier is currently producing total giberrish, at least for the global build.
@@ -103,7 +103,7 @@ async function buildLibrary(dest, opts) {
 
   if (opts.minify && !TRUST_MINIFY) {
     const code = fs.readFileSync(`build/${dest}/luxon.js`, "utf8"),
-      ugly = UglifyJS.minify(code, {
+      ugly = UglifyES.minify(code, {
         toplevel: !opts.global,
         output: {
           comments: false
@@ -175,7 +175,8 @@ async function cjsBrowser() {
 async function es6() {
   await buildLibrary("es6", {
     format: "es",
-    compile: false
+    compile: false,
+    minify: true
   });
 }
 


Test patch:
diff --git a/test/duration/equality.test.js b/test/duration/equality.test.js
index 6d22dfca..bfbd50de 100644
--- a/test/duration/equality.test.js
+++ b/test/duration/equality.test.js
@@ -18,6 +18,14 @@ test("equals identically constructed but one has sting type values", () => {
   expect(l1.equals(l2)).toBe(true);
 });
 
+// #809
+test("equals with extra zero units", () => {
+  const l1 = Duration.fromObject({ years: 5, days: 6 }),
+    l2 = Duration.fromObject({ years: 5, days: 6, minutes: 0, seconds: -0 });
+  expect(l1.equals(l2)).toBe(true);
+  expect(l2.equals(l1)).toBe(true);
+});
+
 test("does not equal an invalid duration", () => {
   const l1 = Duration.fromObject({ years: 5, days: 6 }),
     l2 = Duration.invalid("because");