Repository: mlaursen/react-md
Pull request #605 (2017-11-02 03:09:03)

Fix Date picker problems caused by different time zones and DST.
Due to different time zones, the Date picker presented inconsistencies in formatting
and also displaying of days of the month and days of weeks. For example, in some time zones,
when due to DST the time forwards one hour at midnight, the browser simply skips the 00:00
hours. For example, in Paraguay where the time is forwarded 1 hour on Oct 1st,
new Date(2017, 9, 1, 0) returns Sun Oct 01 2017 01:00:00 GMT-0300 (-03).
This was causing problems such as rendering 2017-11-1 on thursday and not on wednesday, and
also broke the active and today displaying in the calendar.

To fix this issues, this commit does the following:

- Add required string prop timeZone to DatePickerContainer which defaults to UTC.
- Propagates timeZone prop to child/descendant components such as DatePicker, CalendarHeader, DatePickerCalendar, CalendarMonth and CalendarDate
- Always strips time when calculating the next date in CalendarMonth to avoid not displaying active/today dates.
- Updates Date Pickers docs' Formatting section.

## Screenshots of the problems taken from the docs page

1. React console showing the date assigned to the component

![react-console](https://user-images.githubusercontent.com/836133/32307775-f8d8e0d0-bf60-11e7-8455-fa4fc91f6ae8.png)

2. Component opened first time (wrong date and wrong days of the week)

![dialog_opened_1st_time](https://user-images.githubusercontent.com/836133/32307817-2c115c7a-bf61-11e7-86bc-08dcb1a74ebb.png)

3. Date picker switched to October. No active or today working; the screenshot was taken on October 30th.

![dialog_oct](https://user-images.githubusercontent.com/836133/32307858-6d2e1f86-bf61-11e7-8918-617266deefe3.png)

4. Date picker switched back to November. All dates displaced one day.

![dialog_nov_after_oct](https://user-images.githubusercontent.com/836133/32307879-9a87768a-bf61-11e7-882f-e728efe8f9ea.png)


## Caveats

1. Could not make the new timeZone prop to appear in the PROP TYPES section of the docs. How can I do that?
2. This does not address Time picker issues.






Patch:
diff --git a/docs/src/components/Components/Pickers/Date/index.jsx b/docs/src/components/Components/Pickers/Date/index.jsx
index 5cc6ecedc9..906617b0a5 100644
--- a/docs/src/components/Components/Pickers/Date/index.jsx
+++ b/docs/src/components/Components/Pickers/Date/index.jsx
@@ -55,7 +55,9 @@ as you desire.
   title: 'Formatting',
   description: `
 As stated above, the date and time formatting is done with the \`Intl.DateTimeFormat\`
-formatter. The examples below will show the default formatting differences between:
+formatter. By default, all formatting operations are done using **UTC** \`timeZone\`. 
+
+The examples below will show the default formatting differences between:
 - the browser's locale
 - an en-US locale
 - a da-DK locale
diff --git a/src/js/Pickers/CalendarDate.js b/src/js/Pickers/CalendarDate.js
index eb571a972d..305e5fb8f3 100644
--- a/src/js/Pickers/CalendarDate.js
+++ b/src/js/Pickers/CalendarDate.js
@@ -20,6 +20,7 @@ export default class CalendarDate extends PureComponent {
     onClick: PropTypes.func.isRequired,
     active: PropTypes.bool,
     today: PropTypes.bool,
+    timeZone: PropTypes.string.isRequired,
   };
 
   constructor(props) {
@@ -35,9 +36,9 @@ export default class CalendarDate extends PureComponent {
     }
   }
 
-  _getFormattedDate({ DateTimeFormat, locales, date }) {
+  _getFormattedDate({ DateTimeFormat, locales, date, timeZone }) {
     return {
-      date: new DateTimeFormat(locales, { day: 'numeric' }).format(date),
+      date: new DateTimeFormat(locales, { day: 'numeric', timeZone }).format(date),
     };
   }
 
diff --git a/src/js/Pickers/CalendarHeader.js b/src/js/Pickers/CalendarHeader.js
index 38fdf59669..344e612203 100644
--- a/src/js/Pickers/CalendarHeader.js
+++ b/src/js/Pickers/CalendarHeader.js
@@ -54,6 +54,12 @@ export default class CalendarHeader extends PureComponent {
      * The DateTimeFormat option to apply to format a weekday.
      */
     weekdayFormat: PropTypes.oneOf(['narrow', 'short', 'long']),
+
+    /**
+     * The timeZone to be used in all formatting operations.
+     * For a full list of possible timeZone values check https://www.iana.org/time-zones.
+     */
+    timeZone: PropTypes.string.isRequired,
   };
 
   static defaultProps = {
@@ -83,9 +89,10 @@ export default class CalendarHeader extends PureComponent {
     titleFormat,
     weekdayClassName,
     weekdayFormat,
+    timeZone,
   } = this.props) {
     const firstDay = getDayOfWeek(date, firstDayOfWeek);
-    const formatter = new DateTimeFormat(locales, { weekday: weekdayFormat });
+    const formatter = new DateTimeFormat(locales, { weekday: weekdayFormat, timeZone });
     const dows = [];
     for (let i = 0; i < 7; i++) {
       const dow = formatter.format(addDate(firstDay, i, 'D'));
@@ -101,7 +108,7 @@ export default class CalendarHeader extends PureComponent {
 
     return {
       dows,
-      title: new DateTimeFormat(locales, titleFormat).format(date),
+      title: new DateTimeFormat(locales, { ...titleFormat, timeZone }).format(date),
     };
   }
 
diff --git a/src/js/Pickers/CalendarMonth.js b/src/js/Pickers/CalendarMonth.js
index ebeb6c00cd..feba0684fd 100644
--- a/src/js/Pickers/CalendarMonth.js
+++ b/src/js/Pickers/CalendarMonth.js
@@ -78,6 +78,12 @@ export default class CalendarMonth extends PureComponent {
       PropTypes.string,
       PropTypes.arrayOf(PropTypes.string),
     ]).isRequired,
+
+    /**
+     * The timeZone to be used in all formatting operations.
+     * For a full list of possible timeZone values check https://www.iana.org/time-zones.
+     */
+    timeZone: PropTypes.string.isRequired,
   };
 
   static defaultProps = {
@@ -100,6 +106,7 @@ export default class CalendarMonth extends PureComponent {
       showAllDays,
       outerDateClassName,
       disableOuterDates,
+      timeZone,
       ...props
     } = this.props;
 
@@ -142,6 +149,7 @@ export default class CalendarMonth extends PureComponent {
             date={currentDate}
             DateTimeFormat={DateTimeFormat}
             locales={locales}
+            timeZone={timeZone}
           />
         );
       } else {
@@ -149,7 +157,8 @@ export default class CalendarMonth extends PureComponent {
       }
 
       days.push(date);
-      currentDate = addDate(currentDate, 1, 'D');
+      // stripTime again to avoid problems when time is forwarded an hour for DST
+      currentDate = stripTime(addDate(currentDate, 1, 'D'));
     }
 
     return (
diff --git a/src/js/Pickers/DatePicker.js b/src/js/Pickers/DatePicker.js
index 10ae5c6e95..6dc3868251 100644
--- a/src/js/Pickers/DatePicker.js
+++ b/src/js/Pickers/DatePicker.js
@@ -104,6 +104,12 @@ export default class DatePicker extends PureComponent {
      * The DateTimeFormat option to apply to format a weekday in calendar header.
      */
     calendarWeekdayFormat: PropTypes.oneOf(['narrow', 'short', 'long']),
+
+    /**
+     * The timeZone to be used in all formatting operations.
+     * For a full list of possible timeZone values check https://www.iana.org/time-zones.
+     */
+    timeZone: PropTypes.string.isRequired,
   };
 
   render() {
@@ -139,6 +145,7 @@ export default class DatePicker extends PureComponent {
       calendarTitleFormat,
       calendarWeekdayClassName,
       calendarWeekdayFormat,
+      timeZone,
       ...props
     } = this.props;
 
@@ -162,6 +169,7 @@ export default class DatePicker extends PureComponent {
           titleFormat={calendarTitleFormat}
           weekdayClassName={calendarWeekdayClassName}
           weekdayFormat={calendarWeekdayFormat}
+          timeZone={timeZone}
         />
       );
     } else {
@@ -207,6 +215,7 @@ export default class DatePicker extends PureComponent {
           calendarTempDate={calendarTempDate}
           calendarMode={calendarMode}
           changeCalendarMode={changeCalendarMode}
+          timeZone={timeZone}
         />
         <div className={cn('md-picker-content-container', contentClassName)}>
           {picker}
diff --git a/src/js/Pickers/DatePickerCalendar.js b/src/js/Pickers/DatePickerCalendar.js
index 537e0c57fc..08b34e744e 100644
--- a/src/js/Pickers/DatePickerCalendar.js
+++ b/src/js/Pickers/DatePickerCalendar.js
@@ -72,6 +72,12 @@ export default class DatePickerCalendar extends PureComponent {
      * The DateTimeFormat option to apply to format a weekday in calendar header.
      */
     weekdayFormat: PropTypes.oneOf(['narrow', 'short', 'long']),
+
+    /**
+     * The timeZone to be used in all formatting operations.
+     * For a full list of possible timeZone values check https://www.iana.org/time-zones.
+     */
+    timeZone: PropTypes.string.isRequired,
   };
 
   render() {
@@ -98,6 +104,7 @@ export default class DatePickerCalendar extends PureComponent {
       titleFormat,
       weekdayClassName,
       weekdayFormat,
+      timeZone,
     } = this.props;
 
     return (
@@ -117,6 +124,7 @@ export default class DatePickerCalendar extends PureComponent {
           titleFormat={titleFormat}
           weekdayClassName={weekdayClassName}
           weekdayFormat={weekdayFormat}
+          timeZone={timeZone}
         />
         <CalendarMonth
           key={new DateTimeFormat(locales).format(calendarDate)}
@@ -133,6 +141,7 @@ export default class DatePickerCalendar extends PureComponent {
           disableOuterDates={disableOuterDates}
           dateClassName={dateClassName}
           outerDateClassName={outerDateClassName}
+          timeZone={timeZone}
         />
       </section>
     );
diff --git a/src/js/Pickers/DatePickerContainer.js b/src/js/Pickers/DatePickerContainer.js
index 0528aef462..6f22ca20ba 100644
--- a/src/js/Pickers/DatePickerContainer.js
+++ b/src/js/Pickers/DatePickerContainer.js
@@ -293,6 +293,12 @@ export default class DatePickerContainer extends PureComponent {
       timeZoneName: PropTypes.oneOf(['short', 'long']),
     }),
 
+    /**
+     * The timeZone to be used in all formatting operations.
+     * For a full list of possible timeZone values check https://www.iana.org/time-zones.
+     */
+    timeZone: PropTypes.string.isRequired,
+
     /**
      * Boolean if the text field for the Date Picker should be displayed as full width.
      */
@@ -539,6 +545,7 @@ export default class DatePickerContainer extends PureComponent {
     closeOnEsc: true,
     disableScrollLocking: false,
     'aria-label': 'Pick a date',
+    timeZone: 'UTC',
   };
 
   constructor(props) {
@@ -550,7 +557,6 @@ export default class DatePickerContainer extends PureComponent {
       defaultValue,
       DateTimeFormat,
       locales,
-      formatOptions,
       minDate,
       maxDate,
     } = props;
@@ -561,7 +567,7 @@ export default class DatePickerContainer extends PureComponent {
       date = this._getDate(defaultValue);
       value = typeof defaultValue === 'string'
         ? defaultValue
-        : DateTimeFormat(locales, formatOptions).format(defaultValue);
+        : DateTimeFormat(locales, this._getFormatOptions()).format(defaultValue);
     } else {
       date = new Date();
       value = '';
@@ -657,6 +663,11 @@ export default class DatePickerContainer extends PureComponent {
     return value;
   }
 
+  _getFormatOptions() {
+    const { formatOptions, timeZone } = this.props;
+    return { ...formatOptions, timeZone };
+  }
+
   _setContainer= (container) => {
     this._container = container;
   };
@@ -700,8 +711,8 @@ export default class DatePickerContainer extends PureComponent {
   };
 
   _handleOkClick = (e) => {
-    const { DateTimeFormat, locales, onChange, formatOptions, onVisibilityChange } = this.props;
-    const value = DateTimeFormat(locales, formatOptions).format(this.state.calendarTempDate);
+    const { DateTimeFormat, locales, onChange, onVisibilityChange } = this.props;
+    const value = DateTimeFormat(locales, this._getFormatOptions()).format(this.state.calendarTempDate);
     if (onChange) {
       onChange(value, new Date(this.state.calendarTempDate), e);
     }
@@ -755,11 +766,11 @@ export default class DatePickerContainer extends PureComponent {
   };
 
   _setCalendarTempDate = (calendarTempDate) => {
-    const { autoOk, DateTimeFormat, locales, onChange, formatOptions } = this.props;
+    const { autoOk, DateTimeFormat, locales, onChange } = this.props;
 
     const state = { calendarTempDate };
     if (autoOk) {
-      const value = DateTimeFormat(locales, formatOptions).format(calendarTempDate);
+      const value = DateTimeFormat(locales, this._getFormatOptions()).format(calendarTempDate);
       if (onChange) {
         onChange(value, new Date(calendarTempDate));
       }
@@ -815,12 +826,12 @@ export default class DatePickerContainer extends PureComponent {
    * @return {String} a formatted date string or the empty string.
    */
   _getFormattedValue(props, state) {
-    const { DateTimeFormat, locales, formatOptions } = props;
+    const { DateTimeFormat, locales } = props;
     const value = getField(props, state, 'value');
     if (!value) {
       return '';
     } else if (value instanceof Date) {
-      return DateTimeFormat(locales, formatOptions).format(new Date(value));
+      return DateTimeFormat(locales, this._getFormatOptions()).format(new Date(value));
     } else {
       return value;
     }
diff --git a/src/js/Pickers/DatePickerHeader.js b/src/js/Pickers/DatePickerHeader.js
index 1de8a05ee5..3b9692dba0 100644
--- a/src/js/Pickers/DatePickerHeader.js
+++ b/src/js/Pickers/DatePickerHeader.js
@@ -21,6 +21,7 @@ export default class DatePickerHeader extends PureComponent {
     calendarTempDate: PropTypes.instanceOf(Date).isRequired,
     calendarMode: PropTypes.oneOf(['calendar', 'year']).isRequired,
     changeCalendarMode: PropTypes.func.isRequired,
+    timeZone: PropTypes.string.isRequired,
   };
 
   constructor(props) {
@@ -39,11 +40,11 @@ export default class DatePickerHeader extends PureComponent {
     }
   }
 
-  _getFormattedDate({ DateTimeFormat, locales, calendarTempDate }) {
+  _getFormattedDate({ DateTimeFormat, locales, calendarTempDate, timeZone }) {
     return {
-      year: DateTimeFormat(locales, { year: 'numeric' }).format(calendarTempDate),
-      weekday: DateTimeFormat(locales, { weekday: 'short' }).format(calendarTempDate),
-      date: DateTimeFormat(locales, { month: 'short', day: '2-digit' }).format(calendarTempDate),
+      year: DateTimeFormat(locales, { year: 'numeric', timeZone }).format(calendarTempDate),
+      weekday: DateTimeFormat(locales, { weekday: 'short', timeZone }).format(calendarTempDate),
+      date: DateTimeFormat(locales, { month: 'short', day: '2-digit', timeZone }).format(calendarTempDate),
     };
   }
 


Test patch:
diff --git a/src/js/Pickers/__tests__/CalendarDate.js b/src/js/Pickers/__tests__/CalendarDate.js
index 7867e28dab..1e1c486431 100644
--- a/src/js/Pickers/__tests__/CalendarDate.js
+++ b/src/js/Pickers/__tests__/CalendarDate.js
@@ -20,7 +20,7 @@ describe('CalendarDate', () => {
       date: new Date(2016, 1, 1),
       disabled: false,
       onClick: jest.fn(),
-      index: 0,
+      timeZone: 'UTC',
     };
 
     const date = renderIntoDocument(<CalendarDate {...props} />);
@@ -35,7 +35,7 @@ describe('CalendarDate', () => {
       date: new Date(2016, 1, 1),
       disabled: false,
       onClick: jest.fn(),
-      index: 0,
+      timeZone: 'UTC',
     };
 
     let date = renderIntoDocument(<CalendarDate {...props} />);
@@ -50,17 +50,21 @@ describe('CalendarDate', () => {
     expect(props.onClick.mock.calls.length).toBe(1);
   });
 
-  it('formats the date as the state', () => {
+  it('formats the date into the state using timeZone passed as prop', () => {
     const props = {
-      DateTimeFormat,
+      DateTimeFormat: jest.fn(() => ({ format: (date) => date.getDate() })),
       locales: 'en-US',
-      date: new Date(2016, 1, 1),
+      date: new Date(2017, 10, 2),
       disabled: false,
       onClick: jest.fn(),
-      index: 0,
+      timeZone: 'UTC',
     };
 
     const date = renderIntoDocument(<CalendarDate {...props} />);
-    expect(date.state.date).toBe('');
+    expect(date.state.date).toBe(props.date.getDate());
+    expect(props.DateTimeFormat).toHaveBeenCalledWith(props.locales, {
+      day: 'numeric',
+      timeZone: props.timeZone,
+    });
   });
 });
diff --git a/src/js/Pickers/__tests__/CalendarHeader.js b/src/js/Pickers/__tests__/CalendarHeader.js
index fe485d884a..cc17c62f51 100644
--- a/src/js/Pickers/__tests__/CalendarHeader.js
+++ b/src/js/Pickers/__tests__/CalendarHeader.js
@@ -15,20 +15,25 @@ describe('CalendarHeader', () => {
     return jest.fn(() => ({ format: (date) => date.toLocaleString() }));
   }
 
-  it('renders the day of week abbreviations', () => {
+  it('renders the day of week abbreviations (using the timeZone passed as prop)', () => {
     const props = {
-      DateTimeFormat,
+      DateTimeFormat: getDateTimeFormatMock(),
       locales: 'en-US',
       onPreviousClick: jest.fn(),
       onNextClick: jest.fn(),
       previousIconChildren: 'a',
       nextIconChildren: 'a',
       date: new Date(),
+      timeZone: 'UTC',
     };
 
     const header = renderIntoDocument(<CalendarHeader {...props} />);
     const dows = findRenderedDOMComponentWithClass(header, 'md-calendar-dows');
     expect(dows.childNodes.length).toBe(7);
+    expect(props.DateTimeFormat).toHaveBeenCalledWith(props.locales, {
+      weekday: CalendarHeader.defaultProps.weekdayFormat,
+      timeZone: props.timeZone,
+    });
   });
 
   it('should change order of the day of week abbreviations when "firstDayOfWeek" property is not 0', () => {
@@ -41,6 +46,7 @@ describe('CalendarHeader', () => {
       nextIconChildren: 'a',
       date: new Date(),
       firstDayOfWeek: 3,
+      timeZone: 'America/Los_Angeles',
     };
 
     const header = renderIntoDocument(<CalendarHeader {...props} />);
@@ -57,13 +63,14 @@ describe('CalendarHeader', () => {
       previousIconChildren: 'a',
       nextIconChildren: 'a',
       date: new Date(),
+      timeZone: 'America/Los_Angeles',
     };
 
     renderIntoDocument(<CalendarHeader {...props} />);
     // first call is from generateDows
     const formatCall = props.DateTimeFormat.mock.calls[1];
     expect(formatCall[0]).toBe(props.locales);
-    expect(formatCall[1]).toEqual({ month: 'long', year: 'numeric' });
+    expect(formatCall[1]).toEqual({ month: 'long', year: 'numeric', timeZone: props.timeZone });
   });
 
   it('should use "titleClassName" property', () => {
@@ -74,6 +81,7 @@ describe('CalendarHeader', () => {
       onNextClick: jest.fn(),
       date: new Date(),
       titleClassName: 'test-title-class',
+      timeZone: 'UTC',
     };
 
     const header = renderIntoDocument(<CalendarHeader {...props} />);
@@ -91,13 +99,14 @@ describe('CalendarHeader', () => {
       nextIconChildren: 'a',
       date: new Date(),
       titleFormat: { month: 'numeric', year: '2-digit' },
+      timeZone: 'America/New_York',
     };
 
     renderIntoDocument(<CalendarHeader {...props} />);
     // first call is from generateDows
     const formatCall = props.DateTimeFormat.mock.calls[1];
     expect(formatCall[0]).toBe(props.locales);
-    expect(formatCall[1]).toEqual(props.titleFormat);
+    expect(formatCall[1]).toEqual({ ...props.titleFormat, timeZone: props.timeZone });
   });
 
   it('should use "weekdayClassName" property', () => {
@@ -109,6 +118,7 @@ describe('CalendarHeader', () => {
       date: new Date(),
       titleClassName: 'test-title-class',
       weekdayClassName: 'test-weekday-class',
+      timeZone: 'UTC',
     };
 
     const header = renderIntoDocument(<CalendarHeader {...props} />);
@@ -126,11 +136,12 @@ describe('CalendarHeader', () => {
       nextIconChildren: 'a',
       date: new Date(),
       weekdayFormat: 'short',
+      timeZone: 'America/New_York',
     };
 
     renderIntoDocument(<CalendarHeader {...props} />);
     const formatCall = props.DateTimeFormat.mock.calls[0];
     expect(formatCall[0]).toBe(props.locales);
-    expect(formatCall[1]).toEqual({ weekday: props.weekdayFormat });
+    expect(formatCall[1]).toEqual({ weekday: props.weekdayFormat, timeZone: props.timeZone });
   });
 });
diff --git a/src/js/Pickers/__tests__/CalendarMonth.js b/src/js/Pickers/__tests__/CalendarMonth.js
index f473b35cec..cfb45d95e5 100644
--- a/src/js/Pickers/__tests__/CalendarMonth.js
+++ b/src/js/Pickers/__tests__/CalendarMonth.js
@@ -25,6 +25,7 @@ describe('CalendarMonth', () => {
       onCalendarDateClick: jest.fn(),
       DateTimeFormat,
       locales: 'en-US',
+      timeZone: 'UTC',
     };
     const calendarMonth = renderIntoDocument(<CalendarMonth {...props} />);
 
@@ -40,12 +41,14 @@ describe('CalendarMonth', () => {
       calendarDate: new Date(2016, 3, 12),
       calendarTempDate: new Date(2016, 3, 12),
       onCalendarDateClick: jest.fn(),
+      timeZone: 'UTC',
     };
 
     const calendarMonth = renderIntoDocument(<CalendarMonth {...props} />);
     const days = scryRenderedComponentsWithType(calendarMonth, CalendarDate);
     expect(days.length).toBe(30);
     expect(days[11].props.active).toBe(true);
+    expect(days[11].props.timeZone).toEqual(props.timeZone);
   });
 
   it('renders days from adjacent months', () => {
@@ -57,12 +60,14 @@ describe('CalendarMonth', () => {
       onCalendarDateClick: jest.fn(),
       firstDayOfWeek: 1,
       showAllDays: true,
+      timeZone: 'UTC',
     };
 
     const calendarMonth = renderIntoDocument(<CalendarMonth {...props} />);
     const days = scryRenderedComponentsWithType(calendarMonth, CalendarDate);
     expect(days.length).toBe(35);
     expect(days[32].props.active).toBe(true);
+    expect(days[11].props.timeZone).toEqual(props.timeZone);
   });
 
   it('should disable days from the other months when showAllDays and disableOuterDates is enabled', () => {
@@ -75,6 +80,7 @@ describe('CalendarMonth', () => {
       firstDayOfWeek: 1,
       showAllDays: true,
       disableOuterDates: true,
+      timeZone: 'UTC',
     };
     const month = shallow(<CalendarMonth {...props} />);
     const days = month.find(CalendarDate);
@@ -100,6 +106,7 @@ describe('CalendarMonth', () => {
       calendarTempDate: new Date(2017, 6, 1),
       onCalendarDateClick: jest.fn(),
       firstDayOfWeek: 5,
+      timeZone: 'UTC',
     };
 
     let header = renderIntoDocument(<CalendarMonth {...props} />);
diff --git a/src/js/Pickers/__tests__/DatePicker.js b/src/js/Pickers/__tests__/DatePicker.js
index c2ba00e36d..1f4590dc3c 100644
--- a/src/js/Pickers/__tests__/DatePicker.js
+++ b/src/js/Pickers/__tests__/DatePicker.js
@@ -5,6 +5,7 @@ import {
   findRenderedDOMComponentWithClass,
   scryRenderedComponentsWithType,
 } from 'react-dom/test-utils';
+import { mount } from 'enzyme';
 
 import DatePicker from '../DatePicker';
 import DatePickerHeader from '../DatePickerHeader';
@@ -38,6 +39,7 @@ const PROPS = {
   nextIcon: <FontIcon>a</FontIcon>,
   previousIcon: <FontIcon>a</FontIcon>,
   yearsDisplayed: 30,
+  timeZone: 'UTC',
 };
 
 describe('DatePicker', () => {
@@ -55,6 +57,19 @@ describe('DatePicker', () => {
     expect(footers.length).toBe(1);
   });
 
+  it('passes props.timeZone to rendered child components', () => {
+    const wrapper = mount(<DatePicker {...PROPS} />);
+
+    // test timeZone in header
+    const header = wrapper.find('DatePickerHeader');
+    expect(header.prop('timeZone')).toEqual(PROPS.timeZone);
+
+    // test timeZone in calendar
+    wrapper.setProps({ calendarMode: 'calendar' });
+    const picker = wrapper.find('DatePickerCalendar');
+    expect(picker.prop('timeZone')).toEqual(PROPS.timeZone);
+  });
+
   it('renders a calendar when the calendarMode is calendar', () => {
     const props = {
       ...PROPS,
diff --git a/src/js/Pickers/__tests__/DatePickerContainer.js b/src/js/Pickers/__tests__/DatePickerContainer.js
index d2dd522a89..3e90381cf0 100644
--- a/src/js/Pickers/__tests__/DatePickerContainer.js
+++ b/src/js/Pickers/__tests__/DatePickerContainer.js
@@ -1,7 +1,7 @@
 /* eslint-env jest*/
 /* eslint-disable max-len */
 import React from 'react';
-import { mount } from 'enzyme';
+import { mount, shallow } from 'enzyme';
 import { findDOMNode } from 'react-dom';
 import {
   renderIntoDocument,
@@ -12,6 +12,62 @@ import DatePickerContainer from '../DatePickerContainer';
 import Portal from '../../Helpers/Portal';
 
 describe('DatePickerContainer', () => {
+  it('default timeZone prop is UTC', () => {
+    expect(DatePickerContainer.defaultProps.timeZone).toBe('UTC');
+  });
+
+  it('calls DateTimeFormat using props.timeZone', () => {
+    const date = new Date(2017, 9, 1);
+    const DateTimeFormat = jest.fn(() => ({ format: () => {} }));
+    const timeZone = DatePickerContainer.defaultProps.timeZone;
+    const locales = DatePickerContainer.defaultProps.locales;
+    const render = (props) => shallow(
+      <DatePickerContainer
+        id="test"
+        DateTimeFormat={DateTimeFormat}
+        {...props}
+      />
+    );
+    const assertFirstCallOk = (formatterMock) => {
+      expect(formatterMock.mock.calls[0][0]).toEqual(locales);
+      expect(formatterMock.mock.calls[0][1]).toEqual({ timeZone });
+    };
+
+    // test with defaultValue
+    render({ defaultValue: date });
+    expect(DateTimeFormat).toHaveBeenCalledWith(locales, { timeZone });
+
+    // test with value
+    DateTimeFormat.mockClear();
+    render({ value: date, onChange: () => {} });
+    expect(DateTimeFormat).toHaveBeenCalledWith(locales, { timeZone });
+
+    // test when _handleOkClick()
+    let wrapper = render({ value: date, onChange: () => {} });
+    DateTimeFormat.mockClear(); // clear after "rendering"
+    wrapper.instance()._handleOkClick({ preventDefault: () => {} });
+    assertFirstCallOk(DateTimeFormat);
+
+    // test when _setCalendarTempDate() with autoOk
+    wrapper = render({ autoOk: true, value: date, onChange: () => {} });
+    DateTimeFormat.mockClear(); // clear after "rendering"
+    wrapper.instance()._setCalendarTempDate(new Date());
+    assertFirstCallOk(DateTimeFormat);
+
+    // test with custom formatOptions
+    DateTimeFormat.mockClear();
+    const formatOptions = { timeZoneName: 'long' };
+    render({ defaultValue: date, formatOptions });
+    expect(DateTimeFormat).toHaveBeenCalledWith(locales, { ...formatOptions, timeZone });
+  });
+
+  it('passes props.timeZone to rendered DatePicker', () => {
+    const wrapper = mount(<DatePickerContainer id="test" defaultVisible />);
+    const timeZone = DatePickerContainer.defaultProps.timeZone;
+
+    expect(wrapper.find('DatePicker').prop('timeZone')).toEqual(timeZone);
+  });
+
   it('merges className and style', () => {
     const style = { display: 'block' };
     const className = 'test';
diff --git a/src/js/Pickers/__tests__/DatePickerHeader.js b/src/js/Pickers/__tests__/DatePickerHeader.js
index dfd1a72444..e2d904a11a 100644
--- a/src/js/Pickers/__tests__/DatePickerHeader.js
+++ b/src/js/Pickers/__tests__/DatePickerHeader.js
@@ -1,10 +1,6 @@
 /* eslint-env jest*/
 import React from 'react';
-import { findDOMNode } from 'react-dom';
-import {
-  renderIntoDocument,
-  scryRenderedComponentsWithType,
-} from 'react-dom/test-utils';
+import { mount } from 'enzyme';
 
 import DatePickerHeader from '../DatePickerHeader';
 import PickerControl from '../PickerControl';
@@ -21,12 +17,12 @@ describe('DatePickerHeader', () => {
       locales: 'en-US',
       changeCalendarMode: jest.fn(),
       calendarMode: 'year',
+      timeZone: 'UTC',
       DateTimeFormat,
     };
 
-    const header = renderIntoDocument(<DatePickerHeader {...props} />);
-    const node = findDOMNode(header);
-    expect(node.classList.contains(props.className)).toBe(true);
+    const header = mount(<DatePickerHeader {...props} />);
+    expect(header.prop('className')).toContain(props.className);
   });
 
   it('displays a picker control for selecting the year and a picker control for selecting the calendar', () => {
@@ -36,15 +32,16 @@ describe('DatePickerHeader', () => {
       locales: 'en-US',
       calendarTempDate: new Date(2016, 1, 1),
       calendarMode: 'year',
+      timeZone: 'UTC',
     };
 
-    const header = renderIntoDocument(<DatePickerHeader {...props} />);
-    const controls = scryRenderedComponentsWithType(header, PickerControl);
+    const header = mount(<DatePickerHeader {...props} />);
+    const controls = header.find(PickerControl);
     expect(controls.length).toBe(2);
 
     const [year, calendar] = controls;
-    expect(year.props.onClick).toBe(header._selectYear);
-    expect(calendar.props.onClick).toBe(header._selectCalendar);
+    expect(year.props.onClick).toBe(header.instance()._selectYear);
+    expect(calendar.props.onClick).toBe(header.instance()._selectCalendar);
   });
 
   it('formats the calendar temp date', () => {
@@ -53,23 +50,26 @@ describe('DatePickerHeader', () => {
       locales: 'en-US',
       changeCalendarMode: jest.fn(),
       calendarMode: 'year',
+      timeZone: 'UTC',
       DateTimeFormat,
     };
 
-    const header = renderIntoDocument(<DatePickerHeader {...props} />);
+    const header = mount(<DatePickerHeader {...props} />);
+    const timeZone = header.prop('timeZone');
+    expect(timeZone).toEqual(props.timeZone);
 
     const [time, weekday, date] = DateTimeFormat.mock.calls;
     expect(time[0]).toBe(props.locales);
-    expect(time[1]).toEqual({ year: 'numeric' });
+    expect(time[1]).toEqual({ year: 'numeric', timeZone });
 
     expect(weekday[0]).toBe(props.locales);
-    expect(weekday[1]).toEqual({ weekday: 'short' });
+    expect(weekday[1]).toEqual({ weekday: 'short', timeZone });
 
     expect(date[0]).toBe(props.locales);
-    expect(date[1]).toEqual({ month: 'short', day: '2-digit' });
+    expect(date[1]).toEqual({ month: 'short', day: '2-digit', timeZone });
 
-    expect(header.state.year).toBe('');
-    expect(header.state.weekday).toBe('');
-    expect(header.state.date).toBe('');
+    expect(header.state('year')).toBe('');
+    expect(header.state('weekday')).toBe('');
+    expect(header.state('date')).toBe('');
   });
 });