Repository: amzn/ion-js
Pull request #686 (2021-04-08 21:59:50)

added changes for creating Timestamp from date
*Issue #675*

*Description of changes:*
This PR works on creating `Timestamp` based on a JavaScript `Date` object.

*Changes:*
- Changed ion.Timestamp to be multi constructor 
- Implemented a constructor to create Timestamp object from date.
- Using `localOffset` to set `Date` object in the constructor.

*Test:*
added unit tests for Timestamp created from Date.


Patch:
diff --git a/src/IonTimestamp.ts b/src/IonTimestamp.ts
index 76a16eb..193cedf 100644
--- a/src/IonTimestamp.ts
+++ b/src/IonTimestamp.ts
@@ -52,6 +52,12 @@ export class Timestamp {
   private _precision: TimestampPrecision;
   private readonly _secondsDecimal: Decimal;
 
+  /** Creates a new Ion Timestamp with millisecond precision from a JavaScript Date.
+   *
+   * @param date a valid JavaScript date object
+   */
+  constructor(date: Date);
+
   /**
    * Creates a new Timestamp, with precision determined by which parameters
    * are provided.  If a parameter is not specified, it defaults to its lowest
@@ -73,71 +79,110 @@ export class Timestamp {
   constructor(
     localOffset: number,
     year: number,
+    month?: number | null,
+    day?: number | null,
+    hour?: number | null,
+    minutes?: number | null,
+    seconds?: number | Decimal | null
+  );
+
+  constructor(
+    dateOrLocalOffset: number | Date | null = null,
+    year: number | null = null,
     month: number | null = null,
     day: number | null = null,
     hour: number | null = null,
     minutes: number | null = null,
     seconds: number | Decimal | null = null
   ) {
-    this._localOffset = localOffset;
-    this._year = year;
-
-    this._precision = TimestampPrecision.YEAR;
-    this._checkRequiredField(
-      "Offset",
-      this._localOffset,
-      Timestamp._MIN_OFFSET,
-      Timestamp._MAX_OFFSET
-    );
-    this._checkRequiredField(
-      "Year",
-      this._year,
-      Timestamp._MIN_YEAR,
-      Timestamp._MAX_YEAR
-    );
-    this._month = this._checkOptionalField(
-      "Month",
-      month,
-      Timestamp._MIN_MONTH,
-      Timestamp._MAX_MONTH,
-      1,
-      TimestampPrecision.MONTH
-    );
-    this._day = this._checkOptionalField(
-      "Day",
-      day,
-      Timestamp._MIN_DAY,
-      Timestamp._MAX_DAY,
-      1,
-      TimestampPrecision.DAY
-    );
-    this._hour = this._checkOptionalField(
-      "Hour",
-      hour,
-      Timestamp._MIN_HOUR,
-      Timestamp._MAX_HOUR,
-      0,
-      TimestampPrecision.HOUR_AND_MINUTE
-    );
-    this._minutes = this._checkOptionalField(
-      "Minutes",
-      minutes,
-      Timestamp._MIN_MINUTE,
-      Timestamp._MAX_MINUTE,
-      0,
-      TimestampPrecision.HOUR_AND_MINUTE
-    );
+    if (dateOrLocalOffset instanceof Date) {
+      const date = dateOrLocalOffset;
+      const seconds = new Decimal(
+        // The coefficient is the total number of milliseconds as an integer
+        date.getSeconds() + date.getMilliseconds(),
+        // And the exponent is 0 to indicate the scale of that integer
+        0
+      );
+
+      this._localOffset = date.getTimezoneOffset() * -1;
+      this._year = date.getFullYear();
+      this._month = date.getMonth() + 1;
+      this._day = date.getDate();
+      this._hour = date.getHours();
+      this._minutes = date.getMinutes();
+      this._secondsDecimal = seconds;
+      this._precision = TimestampPrecision.YEAR;
+    } else {
+      const localOffset = dateOrLocalOffset;
 
-    if (typeof seconds === "number") {
-      if (!Number.isInteger(seconds)) {
+      if (localOffset === null) {
         throw new Error(
-          "The provided seconds number was not an integer (" + seconds + ")"
+          "Timestamp's constructor was called without localOffset"
         );
+      } else if (year === null) {
+        throw new Error("Timestamp's constructor was called without year");
+      } else {
+        this._localOffset = localOffset;
+        this._year = year;
       }
-      this._secondsDecimal = new Decimal(seconds, 0);
-    } else {
-      if (seconds !== null) {
-        this._secondsDecimal = seconds;
+
+      this._precision = TimestampPrecision.YEAR;
+      this._checkRequiredField(
+        "Offset",
+        this._localOffset,
+        Timestamp._MIN_OFFSET,
+        Timestamp._MAX_OFFSET
+      );
+      this._checkRequiredField(
+        "Year",
+        this._year,
+        Timestamp._MIN_YEAR,
+        Timestamp._MAX_YEAR
+      );
+      this._month = this._checkOptionalField(
+        "Month",
+        month,
+        Timestamp._MIN_MONTH,
+        Timestamp._MAX_MONTH,
+        1,
+        TimestampPrecision.MONTH
+      );
+      this._day = this._checkOptionalField(
+        "Day",
+        day,
+        Timestamp._MIN_DAY,
+        Timestamp._MAX_DAY,
+        1,
+        TimestampPrecision.DAY
+      );
+      this._hour = this._checkOptionalField(
+        "Hour",
+        hour,
+        Timestamp._MIN_HOUR,
+        Timestamp._MAX_HOUR,
+        0,
+        TimestampPrecision.HOUR_AND_MINUTE
+      );
+      this._minutes = this._checkOptionalField(
+        "Minutes",
+        minutes,
+        Timestamp._MIN_MINUTE,
+        Timestamp._MAX_MINUTE,
+        0,
+        TimestampPrecision.HOUR_AND_MINUTE
+      );
+
+      if (typeof seconds === "number") {
+        if (!Number.isInteger(seconds)) {
+          throw new Error(
+            "The provided seconds number was not an integer (" + seconds + ")"
+          );
+        }
+        this._secondsDecimal = new Decimal(seconds, 0);
+      } else {
+        if (seconds !== null) {
+          this._secondsDecimal = seconds;
+        }
       }
     }
     if (this._secondsDecimal === null || this._secondsDecimal === undefined) {
@@ -184,7 +229,6 @@ export class Timestamp {
       Timestamp._MAX_YEAR
     );
   }
-
   /**
    * Parses a string and returns a corresponding Timestamp object.
    * The provided string must be a text-encoded Timestamp as specified


Test patch:
diff --git a/test/IonTimestamp.ts b/test/IonTimestamp.ts
index 56a43aa..e94a700 100644
--- a/test/IonTimestamp.ts
+++ b/test/IonTimestamp.ts
@@ -242,4 +242,18 @@ describe("Timestamp", () => {
         assert.deepEqual(ts.getSecondsDecimal(), ion.Decimal.parse('45'));
         assert.deepEqual(ts._getFractionalSeconds(), ion.Decimal.ZERO);
     });
+    it('constructor with Date', () => {
+        const date = new Date(2000, 0, 1, 12, 30, 45);
+        let timestamp1 = new ion.Timestamp(date.getTimezoneOffset() * -1, 2000, 1, 1, 12, 30, 45);
+        let timestamp2 = new ion.Timestamp(date);
+        assert.isTrue(timestamp1!.equals(timestamp2!));
+
+        const date2 = new Date(2000, 0, 1, 12, 30, 20, 20);
+        let timestamp3 = new ion.Timestamp(date2.getTimezoneOffset() * -1, 2000, 1, 1, 12, 30, new ion.Decimal(
+            40,
+            0
+        ));
+        let timestamp4 = new ion.Timestamp(date2);
+        assert.isTrue(timestamp3!.equals(timestamp4!));
+    });
 });
\ No newline at end of file