Repository: node-influx/node-influx (ts)

Issue:
INanoDate.toISOString can produce an incorrectly rounded timestamp
## Expected Behavior
`INanoDate.toISOString()` should return the correct ISO 8601 string with millisecond precision. 

## Actual Behavior
`INanoDate.toISOString()` has a rounding error that produces an ISO 8601 that is off by 1 millisecond in some situations.

## Steps/Code to Reproduce the Problem
The following code sample demonstrates the problem:
```
import { toNanoDate } from 'influx';

// This is a nanosecond "precision" string timestamp with no significant nanosecond digits
const date = new Date('2019-05-30T08:55:41.002Z');
const nanoTimestamp = `${date.getTime()}000000`;

// Generate the INanoDate
const result = toNanoDate(nanoTimestamp);

// Despite no nanoseconds, so there should be no rounding the significant millisecond is rounded down
console.log(result.toISOString() === '2019-05-30T08:55:41.001Z');

// The nanosecond timestamp is, however, correct
console.log(result.toNanoISOString() === '2019-05-30T08:55:41.002000000Z');
```

The timestamp is correct in cases where there are 10s of significant milliseconds. So, I suspect the bug is due to an erroneous call to Math.floor or something in the rounding logic for producing the truncated timestamp.

## Specifications

  - Version: 5.5.1
  - Platform: Ubuntu 19.04



Pull request description:
fix: fix timestamps rounding in INanoDate
Fixes #487

## Proposed Changes
- issue related to floating-point arithmetics in JS.
- first proposal was to use third-party [big.js](https://github.com/MikeMcl/big.js), but actually could manage a workaround based on string parsing.
---

## Checklist

- [x] A test has been added if appropriate
- [x] `npm test` completes successfully
- [x] Commit messages are in [semantic format](https://seesparkbox.com/foundry/semantic_commit_messages)


Patch:
diff --git a/src/grammar/escape.ts b/src/grammar/escape.ts
index dbb67a0..24dc085 100644
--- a/src/grammar/escape.ts
+++ b/src/grammar/escape.ts
@@ -60,7 +60,9 @@ class Escaper {
 
     let match;
 
-    val = val.replace(/\\'/, "\\\\'");
+    if (typeof val === "string") {
+      val = val.replace(/\\'/, "\\\\'");
+    }
 
     /* eslint-disable no-cond-assign */
     while ((match = this._re.exec(val))) {
diff --git a/src/grammar/times.ts b/src/grammar/times.ts
index 20f7598..c198ade 100644
--- a/src/grammar/times.ts
+++ b/src/grammar/times.ts
@@ -263,7 +263,11 @@ const nanoDateMethods = {
  * expect(date.toNanoISOString()).to.equal('2016-10-09T03:58:00.231035600Z');
  */
 export function toNanoDate(timestamp: string): INanoDate {
-  const date = new Date(Math.floor(Number(timestamp) / nsPer.ms)) as any;
+  const secs = Math.floor(Number(timestamp) / nsPer.s);
+  const remainingNs = timestamp.slice(String(secs).length);
+  const remainingMs = Number(remainingNs) / nsPer.ms;
+  const date = new Date(0) as any;
+  date.setUTCSeconds(secs, remainingMs);
   date._nanoTime = timestamp;
   date.getNanoTime = nanoDateMethods.getNanoTimeFromStamp;
   date.toNanoISOString = nanoDateMethods.toNanoISOStringFromStamp;


Test patch:
diff --git a/test/unit/grammar.test.ts b/test/unit/grammar.test.ts
index cde9593..af1aa6f 100644
--- a/test/unit/grammar.test.ts
+++ b/test/unit/grammar.test.ts
@@ -21,11 +21,12 @@ describe("grammar", () => {
     ).to.equal('don"t escape');
   });
 
-  it("escapes backslashes (issue #486)", () => {
+  it("escapes backslashes (issues #486, #516)", () => {
     // eslint-disable-next-line quotes
     expect(grammar.escape.stringLit("GAZP()\\' or 1=1 --")).to.equal(
       "'GAZP()\\\\\\' or 1=1 --'"
     );
+    expect(grammar.escape.tag(1 as any)).to.equal("1");
   });
 
   it("escapes complex values (issue #242)", () => {
@@ -50,6 +51,13 @@ describe("grammar", () => {
     expect(date.toNanoISOString()).to.equal("2016-10-09T03:58:00.231035600Z");
   });
 
+  it("converts a nanoseconds timestamp with trailing zeroes to a nano date", () => {
+    const date = grammar.toNanoDate("1254646541002000000");
+    expect(date.getTime()).to.equal(1254646541002);
+    expect(date.getNanoTime()).to.equal("1254646541002000000"); // Precision is lost
+    expect(date.toNanoISOString()).to.equal("2009-10-04T08:55:41.002000000Z");
+  });
+
   describe("formatting", () => {
     it("formats nanosecond dates", () => {
       expect(grammar.formatDate(nanoDate)).to.equal(