From e8b9d0c2250911f125c664c8df3404d33bbf4eab Mon Sep 17 00:00:00 2001
From: giulianovarriale <giuliano.oca@gmail.com>
Date: Fri, 11 Nov 2016 10:57:51 -0200
Subject: [PATCH] Pass date as integer params on instantiate new Date in order
 to avoid time zone inconsistency

---
 app/assets/javascripts/due_date_select.js.es6 | 11 +++++--
 .../javascripts/lib/utils/datetime_utility.js | 13 ++++++++
 .../fix-timezone-due-date-picker.yml          |  4 +++
 spec/javascripts/datetime_utility_spec.js.es6 | 32 +++++++++++++++++++
 spec/javascripts/due_date_select_spec.js.es6  | 25 +++++++++++++++
 .../fixtures/due_date_select.html.haml        | 29 +++++++++++++++++
 6 files changed, 111 insertions(+), 3 deletions(-)
 create mode 100644 changelogs/unreleased/fix-timezone-due-date-picker.yml
 create mode 100644 spec/javascripts/due_date_select_spec.js.es6
 create mode 100644 spec/javascripts/fixtures/due_date_select.html.haml

diff --git a/app/assets/javascripts/due_date_select.js.es6 b/app/assets/javascripts/due_date_select.js.es6
index 2b7d57d86c6..f35c024e716 100644
--- a/app/assets/javascripts/due_date_select.js.es6
+++ b/app/assets/javascripts/due_date_select.js.es6
@@ -2,9 +2,10 @@
 
 (function(global) {
   class DueDateSelect {
-    constructor({ $dropdown, $loading } = {}) {
+    constructor({ $dropdown, $loading, $context } = {}) {
       const $dropdownParent = $dropdown.closest('.dropdown');
       const $block = $dropdown.closest('.block');
+      this.$context = $context || $('body');
       this.$loading = $loading;
       this.$dropdown = $dropdown;
       this.$dropdownParent = $dropdownParent;
@@ -80,9 +81,12 @@
     }
 
     parseSelectedDate() {
-      this.rawSelectedDate = $("input[name='" + this.fieldName + "']").val();
+      this.rawSelectedDate = this.$context.find(`input[name='${this.fieldName}']`).val();
+
       if (this.rawSelectedDate.length) {
-        let dateObj = new Date(this.rawSelectedDate);
+        // Avoid time zone inconsistency using the utils.createDateObject
+        // method, instead of the native Date object.
+        const dateObj = gl.utils.createDateObject(this.rawSelectedDate);
         this.displayedDate = $.datepicker.formatDate('M d, yy', dateObj);
       } else {
         this.displayedDate = 'No due date';
@@ -176,5 +180,6 @@
   }
 
   global.DueDateSelectors = DueDateSelectors;
+  global.DueDateSelect = DueDateSelect;
 
 })(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index e8e502694d6..7a136a355ec 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -16,6 +16,19 @@
     }
     w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
 
+    // createDateObject must be used instead of using native Date object
+    // to create a new Date instance using string as param - '2016-11-10' or
+    // '2016/11/10' in order to avoid time zone inconsistency.
+    w.gl.utils.createDateObject = function(string) {
+      var dateSeparator = string.indexOf('-') > -1 ? '-' : '/';
+
+      var dateArray = string.split(dateSeparator).map(function(dateItem) {
+        return parseInt(dateItem, 10);
+      });
+
+      return new Date(dateArray[0],  dateArray[1] - 1, dateArray[2]);
+    }
+
     w.gl.utils.formatDate = function(datetime) {
       return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
     };
diff --git a/changelogs/unreleased/fix-timezone-due-date-picker.yml b/changelogs/unreleased/fix-timezone-due-date-picker.yml
new file mode 100644
index 00000000000..02f5ed890c6
--- /dev/null
+++ b/changelogs/unreleased/fix-timezone-due-date-picker.yml
@@ -0,0 +1,4 @@
+---
+title: Fix date inconsistency on due date picker
+merge_request:
+author: Giuliano Varriale
diff --git a/spec/javascripts/datetime_utility_spec.js.es6 b/spec/javascripts/datetime_utility_spec.js.es6
index 8ece24555c5..756c888cad7 100644
--- a/spec/javascripts/datetime_utility_spec.js.es6
+++ b/spec/javascripts/datetime_utility_spec.js.es6
@@ -2,6 +2,38 @@
 
 (() => {
   describe('Date time utils', () => {
+    describe('create date object', () => {
+      describe('using dashes', () => {
+        it('should instantiate the date object using integer params', () => {
+          spyOn(window, 'Date')
+          gl.utils.createDateObject('2016-11-12');
+          expect(window.Date).toHaveBeenCalledWith(2016, 10, 12);
+        });
+
+        it('should return the right date object ', () => {
+          const date = gl.utils.createDateObject('2016-11-12');
+          expect(date.getDate()).toBe(12);
+          expect(date.getMonth()).toBe(10);
+          expect(date.getFullYear()).toBe(2016);
+        });
+      });
+
+      describe('using slashes', () => {
+        it('should instantiate the date object using integer params', () => {
+          spyOn(window, 'Date')
+          gl.utils.createDateObject('2016/08/02');
+          expect(window.Date).toHaveBeenCalledWith(2016, 7, 2);
+        });
+
+        it('should return the right date object', () => {
+          const date = gl.utils.createDateObject('2016/08/02');
+          expect(date.getDate()).toBe(2);
+          expect(date.getMonth()).toBe(7);
+          expect(date.getFullYear()).toBe(2016);
+        });
+      });
+    });
+
     describe('get day name', () => {
       it('should return Sunday', () => {
         const day = gl.utils.getDayName(new Date('07/17/2016'));
diff --git a/spec/javascripts/due_date_select_spec.js.es6 b/spec/javascripts/due_date_select_spec.js.es6
new file mode 100644
index 00000000000..d491c25b6fe
--- /dev/null
+++ b/spec/javascripts/due_date_select_spec.js.es6
@@ -0,0 +1,25 @@
+/* eslint-disable */
+//= require lib/utils/datetime_utility
+//= require jquery
+/*= require jquery-ui/datepicker */
+/*= require gl_dropdown */
+//= require due_date_select
+(() => {
+  describe('Due Date Select', () => {
+    describe('parseSelectedDate()', () => {
+      it('call create date object', () => {
+        const $dom = $(fixture.preload('due_date_select.html')[0]);
+
+        const dueDateSelect = new gl.DueDateSelect({
+          $context: $dom,
+          $dropdown: $dom.find('.js-due-date-select'),
+          $loading: $dom.find('.block-loading')
+        });
+
+        spyOn(gl.utils, 'createDateObject');
+        dueDateSelect.parseSelectedDate();
+        expect(gl.utils.createDateObject).toHaveBeenCalledWith('2016-11-20');
+      });
+    });
+  });
+})();
diff --git a/spec/javascripts/fixtures/due_date_select.html.haml b/spec/javascripts/fixtures/due_date_select.html.haml
new file mode 100644
index 00000000000..ed7dcb0d658
--- /dev/null
+++ b/spec/javascripts/fixtures/due_date_select.html.haml
@@ -0,0 +1,29 @@
+.block.due_date
+  .sidebar-collapsed-icon
+    %i.fa.fa-calendar
+    %span.js-due-date-sidebar-value
+      Nov 20, 2016
+  .title.hide-collapsed
+    Due date
+    %i.fa.fa-spinner.fa-spin.block-loading
+    %a.edit-link.pull-right{ href: "#"} Edit
+  .value.hide-collapsed
+    %span.value-content
+      %span.bold
+        Nov 20, 2016
+      %span.no-value.js-remove-due-date-holder
+        %a.js-remove-due-date{ href: "#", role: "button" }
+          remove due date
+    .selectbox.hide-collapsed
+      %input{type: "hidden", name: "issue[due_date]", id: "issue[due_date]", value: "2016-11-20"}
+      .dropdown
+        %button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue", issue_update: "/h5bp/html5-boilerplate/issues/10.json" } }
+          %span.dropdown-toggle-text Due date
+          %i.fa.fa-chevron-down
+        .dropdown-menu.dropdown-menu-due-date
+          .dropdown-title
+            %span Due Date
+            %button.dropdown-title-button.dropdown-menu-close{ type: "button" }
+              %i.fa.fa-times.dropdown-menu-close-icon
+          .dropdown-content
+            .js-due-date-calendar
-- 
GitLab