Repository: mlaursen/react-md
Pull request #680 (2018-02-07 14:59:34)

Bugfix/date pickers
This _should_ fix the major issues in #634 (#677 for the picker rewrite and all fixes). I tested using a few different time zones and locales, and it seems to display correctly in the browsers I support.

Going to open this now to see what I need to add back after reverting a PR and see if I missed anything.


Patch:
diff --git a/docs/src/components/Components/Pickers/Date/index.jsx b/docs/src/components/Components/Pickers/Date/index.jsx
index 906617b0a5..5cc6ecedc9 100644
--- a/docs/src/components/Components/Pickers/Date/index.jsx
+++ b/docs/src/components/Components/Pickers/Date/index.jsx
@@ -55,9 +55,7 @@ as you desire.
   title: 'Formatting',
   description: `
 As stated above, the date and time formatting is done with the \`Intl.DateTimeFormat\`
-formatter. By default, all formatting operations are done using **UTC** \`timeZone\`. 
-
-The examples below will show the default formatting differences between:
+formatter. 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 305e5fb8f3..eb571a972d 100644
--- a/src/js/Pickers/CalendarDate.js
+++ b/src/js/Pickers/CalendarDate.js
@@ -20,7 +20,6 @@ export default class CalendarDate extends PureComponent {
     onClick: PropTypes.func.isRequired,
     active: PropTypes.bool,
     today: PropTypes.bool,
-    timeZone: PropTypes.string.isRequired,
   };
 
   constructor(props) {
@@ -36,9 +35,9 @@ export default class CalendarDate extends PureComponent {
     }
   }
 
-  _getFormattedDate({ DateTimeFormat, locales, date, timeZone }) {
+  _getFormattedDate({ DateTimeFormat, locales, date }) {
     return {
-      date: new DateTimeFormat(locales, { day: 'numeric', timeZone }).format(date),
+      date: new DateTimeFormat(locales, { day: 'numeric' }).format(date),
     };
   }
 
diff --git a/src/js/Pickers/CalendarHeader.js b/src/js/Pickers/CalendarHeader.js
index 344e612203..8d696fb6e9 100644
--- a/src/js/Pickers/CalendarHeader.js
+++ b/src/js/Pickers/CalendarHeader.js
@@ -3,9 +3,9 @@ import PropTypes from 'prop-types';
 import cn from 'classnames';
 
 import themeColors from '../utils/themeColors';
-import isMonthBefore from '../utils/DateUtils/isMonthBefore';
-import getDayOfWeek from '../utils/DateUtils/getDayOfWeek';
-import addDate from '../utils/DateUtils/addDate';
+import isMonthBefore from '../utils/dates/isMonthBefore';
+import toDayOfWeek from '../utils/dates/toDayOfWeek';
+import addDate from '../utils/dates/addDate';
 import Button from '../Buttons/Button';
 
 /**
@@ -54,12 +54,6 @@ 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 = {
@@ -89,10 +83,9 @@ export default class CalendarHeader extends PureComponent {
     titleFormat,
     weekdayClassName,
     weekdayFormat,
-    timeZone,
   } = this.props) {
-    const firstDay = getDayOfWeek(date, firstDayOfWeek);
-    const formatter = new DateTimeFormat(locales, { weekday: weekdayFormat, timeZone });
+    const firstDay = toDayOfWeek(date, firstDayOfWeek);
+    const formatter = new DateTimeFormat(locales, { weekday: weekdayFormat });
     const dows = [];
     for (let i = 0; i < 7; i++) {
       const dow = formatter.format(addDate(firstDay, i, 'D'));
@@ -108,7 +101,7 @@ export default class CalendarHeader extends PureComponent {
 
     return {
       dows,
-      title: new DateTimeFormat(locales, { ...titleFormat, timeZone }).format(date),
+      title: new DateTimeFormat(locales, titleFormat).format(date),
     };
   }
 
diff --git a/src/js/Pickers/CalendarMonth.js b/src/js/Pickers/CalendarMonth.js
index feba0684fd..2acf37b1ab 100644
--- a/src/js/Pickers/CalendarMonth.js
+++ b/src/js/Pickers/CalendarMonth.js
@@ -2,10 +2,10 @@ import React, { PureComponent } from 'react';
 import PropTypes from 'prop-types';
 import cn from 'classnames';
 
-import addDate from '../utils/DateUtils/addDate';
-import stripTime from '../utils/DateUtils/stripTime';
-import getLastDay from '../utils/DateUtils/getLastDay';
-import getDayOfWeek from '../utils/DateUtils/getDayOfWeek';
+import addDate from '../utils/dates/addDate';
+import getLastDay from '../utils/dates/getLastDay';
+import stripTime from '../utils/dates/stripTime';
+import toDayOfWeek from '../utils/dates/toDayOfWeek';
 
 import CalendarDate from './CalendarDate';
 
@@ -78,12 +78,6 @@ 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 = {
@@ -106,23 +100,23 @@ export default class CalendarMonth extends PureComponent {
       showAllDays,
       outerDateClassName,
       disableOuterDates,
-      timeZone,
       ...props
     } = this.props;
 
     const days = [];
-    const firstDay = new Date(calendarDate);
+    const firstDay = stripTime(calendarDate);
     firstDay.setDate(1);
     const lastDay = getLastDay(calendarDate);
-    let currentDate = stripTime(getDayOfWeek(firstDay, 0));
-    let endDate = stripTime(getDayOfWeek(lastDay, 6));
-    const activeDateTime = stripTime(new Date(calendarTempDate)).getTime();
-    const todayTime = stripTime(new Date()).getTime();
+    let currentDate = toDayOfWeek(firstDay, 0);
+    let endDate = toDayOfWeek(lastDay, 6);
+    const activeDateTime = stripTime(calendarTempDate).getTime();
+    const todayTime = new Date().getTime();
 
     if (firstDayOfWeek) {
       currentDate = addDate(currentDate, firstDayOfWeek > firstDay.getDay() ? firstDayOfWeek - 7 : firstDayOfWeek, 'D');
       endDate = addDate(endDate, firstDayOfWeek > lastDay.getDay() ? firstDayOfWeek - 7 : firstDayOfWeek, 'D');
     }
+
     while (currentDate <= endDate) {
       const key = `${currentDate.getMonth()}-${currentDate.getDate()}`;
       const currentMonth = currentDate.getMonth() === calendarDate.getMonth();
@@ -149,7 +143,6 @@ export default class CalendarMonth extends PureComponent {
             date={currentDate}
             DateTimeFormat={DateTimeFormat}
             locales={locales}
-            timeZone={timeZone}
           />
         );
       } else {
@@ -157,8 +150,7 @@ export default class CalendarMonth extends PureComponent {
       }
 
       days.push(date);
-      // stripTime again to avoid problems when time is forwarded an hour for DST
-      currentDate = stripTime(addDate(currentDate, 1, 'D'));
+      currentDate = addDate(currentDate, 1, 'D');
     }
 
     return (
diff --git a/src/js/Pickers/DatePicker.js b/src/js/Pickers/DatePicker.js
index 6dc3868251..10ae5c6e95 100644
--- a/src/js/Pickers/DatePicker.js
+++ b/src/js/Pickers/DatePicker.js
@@ -104,12 +104,6 @@ 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() {
@@ -145,7 +139,6 @@ export default class DatePicker extends PureComponent {
       calendarTitleFormat,
       calendarWeekdayClassName,
       calendarWeekdayFormat,
-      timeZone,
       ...props
     } = this.props;
 
@@ -169,7 +162,6 @@ export default class DatePicker extends PureComponent {
           titleFormat={calendarTitleFormat}
           weekdayClassName={calendarWeekdayClassName}
           weekdayFormat={calendarWeekdayFormat}
-          timeZone={timeZone}
         />
       );
     } else {
@@ -215,7 +207,6 @@ 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 08b34e744e..537e0c57fc 100644
--- a/src/js/Pickers/DatePickerCalendar.js
+++ b/src/js/Pickers/DatePickerCalendar.js
@@ -72,12 +72,6 @@ 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() {
@@ -104,7 +98,6 @@ export default class DatePickerCalendar extends PureComponent {
       titleFormat,
       weekdayClassName,
       weekdayFormat,
-      timeZone,
     } = this.props;
 
     return (
@@ -124,7 +117,6 @@ export default class DatePickerCalendar extends PureComponent {
           titleFormat={titleFormat}
           weekdayClassName={weekdayClassName}
           weekdayFormat={weekdayFormat}
-          timeZone={timeZone}
         />
         <CalendarMonth
           key={new DateTimeFormat(locales).format(calendarDate)}
@@ -141,7 +133,6 @@ 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 6f22ca20ba..bcd2701a4e 100644
--- a/src/js/Pickers/DatePickerContainer.js
+++ b/src/js/Pickers/DatePickerContainer.js
@@ -10,8 +10,8 @@ import getField from '../utils/getField';
 import handleWindowClickListeners from '../utils/EventUtils/handleWindowClickListeners';
 import handleKeyboardAccessibility from '../utils/EventUtils/handleKeyboardAccessibility';
 import controlled from '../utils/PropTypes/controlled';
-import isDateEqual from '../utils/DateUtils/isDateEqual';
-import addDate from '../utils/DateUtils/addDate';
+import addDate from '../utils/dates/addDate';
+import isSameDay from '../utils/dates/isSameDay';
 import DateTimeFormat from '../utils/DateUtils/DateTimeFormat';
 
 import Collapse from '../Helpers/Collapse';
@@ -293,12 +293,6 @@ 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.
      */
@@ -545,7 +539,6 @@ export default class DatePickerContainer extends PureComponent {
     closeOnEsc: true,
     disableScrollLocking: false,
     'aria-label': 'Pick a date',
-    timeZone: 'UTC',
   };
 
   constructor(props) {
@@ -557,6 +550,7 @@ export default class DatePickerContainer extends PureComponent {
       defaultValue,
       DateTimeFormat,
       locales,
+      formatOptions,
       minDate,
       maxDate,
     } = props;
@@ -567,7 +561,7 @@ export default class DatePickerContainer extends PureComponent {
       date = this._getDate(defaultValue);
       value = typeof defaultValue === 'string'
         ? defaultValue
-        : DateTimeFormat(locales, this._getFormatOptions()).format(defaultValue);
+        : DateTimeFormat(locales, formatOptions).format(defaultValue);
     } else {
       date = new Date();
       value = '';
@@ -602,8 +596,8 @@ export default class DatePickerContainer extends PureComponent {
 
   componentWillReceiveProps(nextProps) {
     const { value, minDate, maxDate } = nextProps;
-    const minEqual = isDateEqual(this.props.minDate, minDate);
-    const maxEqual = isDateEqual(this.props.maxDate, maxDate);
+    const minEqual = isSameDay(this.props.minDate, minDate);
+    const maxEqual = isSameDay(this.props.maxDate, maxDate);
     if (this.props.value !== value || !minEqual || !maxEqual) {
       let { calendarDate } = this.state;
       if (typeof value !== 'undefined') {
@@ -612,7 +606,7 @@ export default class DatePickerContainer extends PureComponent {
 
       calendarDate = this._validateDateRange(calendarDate, minDate, maxDate);
 
-      if (!isDateEqual(this.state.calendarDate, calendarDate)) {
+      if (!isSameDay(this.state.calendarDate, calendarDate)) {
         this.setState({ calendarDate, calendarTempDate: calendarDate });
       }
     }
@@ -663,11 +657,6 @@ export default class DatePickerContainer extends PureComponent {
     return value;
   }
 
-  _getFormatOptions() {
-    const { formatOptions, timeZone } = this.props;
-    return { ...formatOptions, timeZone };
-  }
-
   _setContainer= (container) => {
     this._container = container;
   };
@@ -711,8 +700,8 @@ export default class DatePickerContainer extends PureComponent {
   };
 
   _handleOkClick = (e) => {
-    const { DateTimeFormat, locales, onChange, onVisibilityChange } = this.props;
-    const value = DateTimeFormat(locales, this._getFormatOptions()).format(this.state.calendarTempDate);
+    const { DateTimeFormat, locales, onChange, formatOptions, onVisibilityChange } = this.props;
+    const value = DateTimeFormat(locales, formatOptions).format(this.state.calendarTempDate);
     if (onChange) {
       onChange(value, new Date(this.state.calendarTempDate), e);
     }
@@ -766,11 +755,11 @@ export default class DatePickerContainer extends PureComponent {
   };
 
   _setCalendarTempDate = (calendarTempDate) => {
-    const { autoOk, DateTimeFormat, locales, onChange } = this.props;
+    const { autoOk, DateTimeFormat, locales, onChange, formatOptions } = this.props;
 
     const state = { calendarTempDate };
     if (autoOk) {
-      const value = DateTimeFormat(locales, this._getFormatOptions()).format(calendarTempDate);
+      const value = DateTimeFormat(locales, formatOptions).format(calendarTempDate);
       if (onChange) {
         onChange(value, new Date(calendarTempDate));
       }
@@ -826,12 +815,12 @@ export default class DatePickerContainer extends PureComponent {
    * @return {String} a formatted date string or the empty string.
    */
   _getFormattedValue(props, state) {
-    const { DateTimeFormat, locales } = props;
+    const { DateTimeFormat, locales, formatOptions } = props;
     const value = getField(props, state, 'value');
     if (!value) {
       return '';
     } else if (value instanceof Date) {
-      return DateTimeFormat(locales, this._getFormatOptions()).format(new Date(value));
+      return DateTimeFormat(locales, formatOptions).format(new Date(value));
     } else {
       return value;
     }
diff --git a/src/js/Pickers/DatePickerHeader.js b/src/js/Pickers/DatePickerHeader.js
index 3b9692dba0..1de8a05ee5 100644
--- a/src/js/Pickers/DatePickerHeader.js
+++ b/src/js/Pickers/DatePickerHeader.js
@@ -21,7 +21,6 @@ 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) {
@@ -40,11 +39,11 @@ export default class DatePickerHeader extends PureComponent {
     }
   }
 
-  _getFormattedDate({ DateTimeFormat, locales, calendarTempDate, timeZone }) {
+  _getFormattedDate({ DateTimeFormat, locales, calendarTempDate }) {
     return {
-      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),
+      year: DateTimeFormat(locales, { year: 'numeric' }).format(calendarTempDate),
+      weekday: DateTimeFormat(locales, { weekday: 'short' }).format(calendarTempDate),
+      date: DateTimeFormat(locales, { month: 'short', day: '2-digit' }).format(calendarTempDate),
     };
   }
 
diff --git a/src/js/utils/DateUtils/addDate.js b/src/js/utils/DateUtils/addDate.js
deleted file mode 100644
index 0688b9a790..0000000000
--- a/src/js/utils/DateUtils/addDate.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/** @module utils/DateUtils/addDate */
-
-/**
- * Adds a given amount to a date.
- *
- * @param {date} sourceDate the date to add
- * @param {number} amt the amount to add
- * @param {string} part the date part to add to. ['D', 'M', 'Y']
- * @return a new Date with the part added or the date if the part is not valid.
- */
-export default function addDate(sourceDate, amt, part) {
-  const date = new Date(sourceDate);
-
-  switch (part) {
-    case 'D':
-      return new Date(date.setDate(date.getDate() + amt));
-    case 'M':
-      date.setDate(1);
-      return new Date(date.setMonth(date.getMonth() + amt));
-    case 'Y':
-      return new Date(date.setFullYear(date.getFullYear() + amt));
-    default:
-      return date;
-  }
-}
diff --git a/src/js/utils/DateUtils/getDayOfWeek.js b/src/js/utils/DateUtils/getDayOfWeek.js
deleted file mode 100644
index eba729c685..0000000000
--- a/src/js/utils/DateUtils/getDayOfWeek.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/** @module utils/DateUtils/getDayOfWeek */
-
-/**
- * Gets a day in the week. 0 = Sunday 6 = Saturday
- *
- * @param {date} sourceDate the date to find a relative day of week from
- * @param {number} dow the day of the week to find
- * @return a new Date as the given day of week
- */
-export default function getDayOfWeek(sourceDate, dow = 0) {
-  const date = new Date(sourceDate);
-  const day = date.getDay();
-
-  const diff = date.getDate() - day + dow;
-  return new Date(date.setDate(diff));
-}
diff --git a/src/js/utils/DateUtils/getLastDay.js b/src/js/utils/DateUtils/getLastDay.js
deleted file mode 100644
index bc522d57f3..0000000000
--- a/src/js/utils/DateUtils/getLastDay.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/** @module utils/DateUtils/getLastDay */
-
-/**
- * Gets the last day in a month
- *
- * @param {Date} sourceDate the date to get the last day from.
- * @return a new Date as the last day of the month.
- */
-export default function getLastDay(sourceDate) {
-  return new Date(sourceDate.getFullYear(), sourceDate.getMonth() + 1, 0);
-}
diff --git a/src/js/utils/DateUtils/isDateEqual.js b/src/js/utils/DateUtils/isDateEqual.js
deleted file mode 100644
index 36b37eb3ce..0000000000
--- a/src/js/utils/DateUtils/isDateEqual.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/** @module utils/DateUtils/isDateEqual */
-
-/**
- * Checks if two dates are equal by comparing the exact time.
- * This allows null or undefined.
- *
- * @param {Date} d1 - The first date to compare.
- * @param {Date} d2 - The second date to compare.
- * @return {bool} - true if the dates are equal.
- */
-export default function isDateEqual(d1, d2) {
-  if (!d1 && !d2) {
-    return true;
-  } else if ((!d1 && d2) || (d1 && !d2)) {
-    return false;
-  }
-
-  return d1.getTime() === d2.getTime();
-}
-
diff --git a/src/js/utils/DateUtils/stripTime.js b/src/js/utils/DateUtils/stripTime.js
deleted file mode 100644
index a1a5fa63ae..0000000000
--- a/src/js/utils/DateUtils/stripTime.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/** @module utils/DateUtils/stripTime */
-
-/**
- * Removes all time from a date. Only keeps year, month, and date.
- * @param {Date} date the date to strip
- * @return a new Date with the time stripped.
- */
-export default function stripTime(date) {
-  if (!date || !(date instanceof Date)) {
-    return null;
-  }
-
-  return new Date(date.getFullYear(), date.getMonth(), date.getDate());
-}
diff --git a/src/js/utils/dates/addDate.js b/src/js/utils/dates/addDate.js
new file mode 100644
index 0000000000..a086365db5
--- /dev/null
+++ b/src/js/utils/dates/addDate.js
@@ -0,0 +1,27 @@
+import addDay from './addDay';
+import addMonth from './addMonth';
+import addYear from './addYear';
+
+/**
+ * Adds a given amount to a date.
+ *
+ * @param {Date} date - The date to add
+ * @param {number} amount - The amount to add
+ * @param {string} part - The date part to add to. ['D', 'M', 'Y']
+ * @param {number=1} newMonthDate - An optional date to set in the new month
+ *    if the new month does not have the old date. This only applies to month
+ *    addition.
+ * @return a new Date with the part added or the date if the part is not valid.
+ */
+export default function addDate(date, amount, part, newMonthDate) {
+  switch (part) {
+    case 'D':
+      return addDay(date, amount);
+    case 'M':
+      return addMonth(date, amount, newMonthDate);
+    case 'Y':
+      return addYear(date, amount);
+    default:
+      return date;
+  }
+}
diff --git a/src/js/utils/dates/addDay.js b/src/js/utils/dates/addDay.js
new file mode 100644
index 0000000000..65a5c0caee
--- /dev/null
+++ b/src/js/utils/dates/addDay.js
@@ -0,0 +1,21 @@
+import isValidDate from './isValidDate';
+
+/**
+ * Adds the specified number of days to a provided date. If the date
+ * is null, an invalid formatted date, or not a Date instance, null
+ * will be returned instead.
+ *
+ * @param {Date} date - The date to update
+ * @param {number} amount - The number of days to add. This can be positive
+ *    or negative.
+ * @return {Date} a new date with the number of days added or null.
+ */
+export default function addDay(date, amount) {
+  if (!isValidDate(date)) {
+    return null;
+  }
+
+  const d = new Date(date);
+  d.setDate(d.getDate() + amount);
+  return d;
+}
diff --git a/src/js/utils/dates/addMonth.js b/src/js/utils/dates/addMonth.js
new file mode 100644
index 0000000000..70ef6d1be5
--- /dev/null
+++ b/src/js/utils/dates/addMonth.js
@@ -0,0 +1,42 @@
+import isValidDate from './isValidDate';
+import getDaysInMonth from './getDaysInMonth';
+
+
+/**
+ * Adds the specified number of months to a provided date. If the date
+ * is null, an invalid formatted date, or not a Date instance, null
+ * will be returned instead.
+ *
+ * When a new month is returned, it will make sure that the date matches
+ * the date in the old month if possible, if the new month does not have
+ * include the new date, the `newMonthDate` will be used instead.
+ *
+ * @param {Date} date - The date to update
+ * @param {number} amount - The number of months to add. This can be positive
+ *    or negative.
+ * @param {number=1} newMonthDate - An optional date to set in the new month
+ *    if the new month does not have the old date.
+ * @return {Date} a new date with the number of days added or null.
+ */
+export default function addMonth(date, amount, newMonthDate) {
+  if (!isValidDate(date)) {
+    return null;
+  } else if (amount === 0) {
+    return new Date(date);
+  }
+
+  if (!newMonthDate) { // 0 is invalid for this case
+    newMonthDate = date.getDate();
+  }
+
+  const d = new Date(date);
+  d.setDate(1); // reset to first day to prevent month overflows
+  d.setMonth(d.getMonth() + amount);
+
+  if (newMonthDate !== 1 && newMonthDate <= getDaysInMonth(d)) {
+    // set the date back to the correct day if it still exists within the month
+    d.setDate(newMonthDate);
+  }
+
+  return d;
+}
diff --git a/src/js/utils/dates/addYear.js b/src/js/utils/dates/addYear.js
new file mode 100644
index 0000000000..e51521b96e
--- /dev/null
+++ b/src/js/utils/dates/addYear.js
@@ -0,0 +1,22 @@
+import isValidDate from './isValidDate';
+
+/**
+ * Adds the specified number of years to a provided date. If the date
+ * is null, an invalid formatted date, or not a Date instance, null
+ * will be returned instead.
+ *
+ * @param {Date} date - The date to update
+ * @param {number} amount - The number of years to add. This can be positive
+ *    or negative.
+ * @return {Date} a new date with the number of years added or null.
+ */
+export default function addYear(date, amount) {
+  if (!isValidDate(date)) {
+    return null;
+  }
+
+  const d = new Date(date);
+  d.setFullYear(d.getFullYear() + amount);
+
+  return d;
+}
diff --git a/src/js/utils/dates/getDaysInMonth.js b/src/js/utils/dates/getDaysInMonth.js
new file mode 100644
index 0000000000..68c2ab39e4
--- /dev/null
+++ b/src/js/utils/dates/getDaysInMonth.js
@@ -0,0 +1,13 @@
+import getLastDay from './getLastDay';
+
+/**
+ * Gets the number of days in a month for the provided date.
+ *
+ * @param {Date} date - The date to get the number of days for
+ * @return {number} the number of days in the month or -1 if it is false-ish
+ *    or an invalid date object.
+ */
+export default function getDaysInMonth(date) {
+  const day = getLastDay(date);
+  return day === null ? -1 : day.getDate();
+}
diff --git a/src/js/utils/dates/getLastDay.js b/src/js/utils/dates/getLastDay.js
new file mode 100644
index 0000000000..54d6d1c96d
--- /dev/null
+++ b/src/js/utils/dates/getLastDay.js
@@ -0,0 +1,15 @@
+import isValidDate from './isValidDate';
+
+/**
+ * Gets the last day in a month as a new Date.
+ *
+ * @param {Date} date - The date to get the last date in a month for
+ * @return {Date} the last day in the month as a date object or null.
+ */
+export default function getLastDay(date) {
+  if (!isValidDate(date)) {
+    return null;
+  }
+
+  return new Date(date.getFullYear(), date.getMonth() + 1, 0);
+}
diff --git a/src/js/utils/dates/getWeekNumber.js b/src/js/utils/dates/getWeekNumber.js
new file mode 100644
index 0000000000..9840fe4f9b
--- /dev/null
+++ b/src/js/utils/dates/getWeekNumber.js
@@ -0,0 +1,21 @@
+import isValidDate from './isValidDate';
+
+/**
+ * Gets the current week number within a month for a provided date. If
+ * the date is invalid, -1 will be returned instead.
+ *
+ * @param {Date} date - The date to get a week number for.
+ * @return {number} the week number with the date's month or -1.
+ */
+export default function getWeekNumber(date) {
+  if (!isValidDate(date)) {
+    return -1;
+  }
+
+  const d = new Date(date);
+  d.setDate(1);
+
+  const firstDay = d.getDay();
+  const dateOffset = date.getDate() + (firstDay - 1);
+  return Math.floor(dateOffset / 7) + 1;
+}
diff --git a/src/js/utils/dates/index.js b/src/js/utils/dates/index.js
new file mode 100644
index 0000000000..d2cadcfae7
--- /dev/null
+++ b/src/js/utils/dates/index.js
@@ -0,0 +1,20 @@
+export addDay from './addDay';
+export addMonth from './addMonth';
+export addYear from './addYear';
+export addDate from './addDate';
+
+export getDaysInMonth from './getDaysInMonth';
+export getWeekNumber from './getWeekNumber';
+export getLastDay from './getLastDay';
+
+export isFirstWeek from './isFirstWeek';
+export isLastWeek from './isLastWeek';
+
+export isSameMinute from './isSameMinute';
+export isSameHour from './isSameHour';
+export isSameDay from './isSameDay';
+export isSameMonth from './isSameMonth';
+export isSameYear from './isSameYear';
+
+export isValidDate from './isValidDate';
+export stripTime from './stripTime';
diff --git a/src/js/utils/dates/isFirstWeek.js b/src/js/utils/dates/isFirstWeek.js
new file mode 100644
index 0000000000..39cd0639d9
--- /dev/null
+++ b/src/js/utils/dates/isFirstWeek.js
@@ -0,0 +1,11 @@
+import getWeekNumber from './getWeekNumber';
+
+/**
+ * Checks if a provided date is within the first week of its month.
+ *
+ * @param {Date} date - the date to check
+ * @return {boolean} true if the date is within the first week.
+ */
+export default function isFirstWeek(date) {
+  return getWeekNumber(date) === 1;
+}
diff --git a/src/js/utils/dates/isLastWeek.js b/src/js/utils/dates/isLastWeek.js
new file mode 100644
index 0000000000..1afb5ab230
--- /dev/null
+++ b/src/js/utils/dates/isLastWeek.js
@@ -0,0 +1,22 @@
+import getWeekNumber from './getWeekNumber';
+
+/**
+ * Checks if a provided date is the last week within its month.
+ *
+ * @param {Date} date - the date to check
+ * @return {boolean} true if the date is within the last week within its month.
+ */
+export default function isLastWeek(date) {
+  const weekNumber = getWeekNumber(date);
+  if (weekNumber === -1) {
+    return false;
+  }
+
+  const d = new Date(date);
+  d.setDate(1);
+
+  const firstDay = d.getDay();
+  const lastDay = new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate();
+  const weeksInMonth = Math.ceil((firstDay + lastDay) / 7);
+  return weeksInMonth === weekNumber;
+}
diff --git a/src/js/utils/DateUtils/isMonthBefore.js b/src/js/utils/dates/isMonthBefore.js
similarity index 57%
rename from src/js/utils/DateUtils/isMonthBefore.js
rename to src/js/utils/dates/isMonthBefore.js
index abc2d6e39c..6d3c0b4ced 100644
--- a/src/js/utils/DateUtils/isMonthBefore.js
+++ b/src/js/utils/dates/isMonthBefore.js
@@ -1,5 +1,4 @@
-/** @module utils/DateUtils/isMonthBefore */
-import stripTime from './stripTime';
+import isValidDate from './isValidDate';
 
 /**
  * Checks if a date is the month before another date without time
@@ -9,11 +8,11 @@ import stripTime from './stripTime';
  * @return true if the date is before the other date's first day of month.
  */
 export default function isMonthBefore(date, toCompare) {
-  if (!date || !toCompare) {
+  if (!isValidDate(date) || !isValidDate(toCompare)) {
     return false;
   }
 
-  const d1 = stripTime(new Date(date.getFullYear(), date.getMonth(), 1));
-  const d2 = stripTime(new Date(toCompare.getFullYear(), toCompare.getMonth() - 1, 1));
+  const d1 = new Date(date.getFullYear(), date.getMonth(), 1);
+  const d2 = new Date(toCompare.getFullYear(), toCompare.getMonth() - 1, 1);
   return d1 > d2;
 }
diff --git a/src/js/utils/dates/isSameDay.js b/src/js/utils/dates/isSameDay.js
new file mode 100644
index 0000000000..bb778b68e0
--- /dev/null
+++ b/src/js/utils/dates/isSameDay.js
@@ -0,0 +1,20 @@
+import isSameMonth from './isSameMonth';
+
+/**
+ * Checks if two dates are part of the same date. This will also
+ * make sure the dates are part of the same year and month.
+ *
+ * @param {Date} d1 - The first date to compare.
+ * @param {Date} d2 - The second date to compare.
+ * @param {boolean=false} bothNullValue - boolean for what should be returned if
+ *  both values are null.
+ * @return {boolean} true if both the dates are defined and part of the same year, month, and day
+ *  or if both values are null it will return the `bothNullValue`.
+ */
+export default function isSameDay(d1, d2, bothNullValue = false) {
+  if (!d1 && !d2) {
+    return bothNullValue;
+  }
+
+  return isSameMonth(d1, d2, bothNullValue) && d1.getDate() === d2.getDate();
+}
diff --git a/src/js/utils/dates/isSameMonth.js b/src/js/utils/dates/isSameMonth.js
new file mode 100644
index 0000000000..3266f68cb2
--- /dev/null
+++ b/src/js/utils/dates/isSameMonth.js
@@ -0,0 +1,20 @@
+import isSameYear from './isSameYear';
+
+/**
+ * Checks if two dates are part of the same month. This will also
+ * make sure the dates are part of the same year.
+ *
+ * @param {Date} d1 - The first date to compare.
+ * @param {Date} d2 - The second date to compare.
+ * @param {boolean=false} bothNullValue - boolean for what should be returned if
+ *  both values are null.
+ * @return {boolean} true if both the dates are defined and part of the same year and month
+ *  or if both values are null it will return the `bothNullValue`
+ */
+export default function isSameMonth(d1, d2, bothNullValue = false) {
+  if (!d1 && !d2) {
+    return bothNullValue;
+  }
+
+  return isSameYear(d1, d2, bothNullValue) && d1.getMonth() === d2.getMonth();
+}
diff --git a/src/js/utils/dates/isSameYear.js b/src/js/utils/dates/isSameYear.js
new file mode 100644
index 0000000000..4b87d547b5
--- /dev/null
+++ b/src/js/utils/dates/isSameYear.js
@@ -0,0 +1,21 @@
+import isValidDate from './isValidDate';
+
+/**
+ * Checks if two dates are part of the same year.
+ *
+ * @param {Date} d1 - The first date to compare.
+ * @param {Date} d2 - The second date to compare.
+ * @param {boolean=false} bothNullValue - boolean for what should be returned if
+ *  both values are null.
+ * @return {boolean} true if both the dates are defined and part of the same year or
+ *  if both values are null it will return the `bothNullValue`.
+ */
+export default function isSameYear(d1, d2, bothNullValue = false) {
+  if (!d1 && !d2) {
+    return bothNullValue;
+  } else if (!isValidDate(d1) || !isValidDate(d2)) {
+    return false;
+  }
+
+  return d1.getFullYear() === d2.getFullYear();
+}
diff --git a/src/js/utils/dates/isValidDate.js b/src/js/utils/dates/isValidDate.js
new file mode 100644
index 0000000000..6629a3cbaa
--- /dev/null
+++ b/src/js/utils/dates/isValidDate.js
@@ -0,0 +1,17 @@
+/**
+ * Checks if a variable provided is defined and a correctly formatted date.
+ *
+ * Examples:
+ * - isValidDate(null) -> false
+ * - isValidDate('') -> false
+ * - isValidDate(new Date('23--33')) - false (malformed date)
+ * - isValidDate(new Date()) -> true
+ *
+ * @param d - Any value that should be checked if it is a valid date
+ * @return {boolean} true if it is a valid date.
+ */
+export default function isValidDate(d) {
+  // just checking if getTime is a function is ridiculously quicker than instanceof Date
+  // and _seems_ safe enough
+  return !!d && typeof d.getTime === 'function' && d.getTime() === d.getTime();
+}
diff --git a/src/js/utils/dates/stripTime.js b/src/js/utils/dates/stripTime.js
new file mode 100644
index 0000000000..8406fcaa1f
--- /dev/null
+++ b/src/js/utils/dates/stripTime.js
@@ -0,0 +1,21 @@
+import isValidDate from './isValidDate';
+
+/**
+ * Removes all the time parts (hours, minutes, seconds, milliseconds) from a date.
+ * The hour of the stripped time is set to 1 by default to help with cross-browser
+ * date implementations and how hour 0 sometimes is the previous day.
+ *
+ * @param {Date} date the date to strip
+ * @param {number=0} hours - the fake hours to set for the stripped time date.
+ * @return a new Date with the time stripped.
+ */
+export default function stripTime(date, hours = 0) {
+  if (!isValidDate(date)) {
+    return null;
+  }
+
+  const d = new Date(date);
+  d.setHours(hours, 0, 0, 0);
+
+  return d;
+}
diff --git a/src/js/utils/dates/toDayOfWeek.js b/src/js/utils/dates/toDayOfWeek.js
new file mode 100644
index 0000000000..a1baf74659
--- /dev/null
+++ b/src/js/utils/dates/toDayOfWeek.js
@@ -0,0 +1,27 @@
+import isValidDate from './isValidDate';
+
+/**
+ * Takes in a date and returns a new date at the specified day of week.
+ *
+ * Examples:
+ * // to Sunday
+ * - toDayOfWeek(new Date(2018, 0, 1), 0) == new Date(2017, 11, 31)
+ *
+ * // to Monday
+ * - toDayOfWeek(new Date(2018, 0, 1), 1) == new Date(2018, 0, 1)
+ *
+ * @param {Date} date - The date to convert to a day of week
+ * @param {number=0} dow - The day of the week to convert to
+ * @return {Date} the new date set at the day of week or null if the
+ *    date is invalid or false-ish.
+ */
+export default function toDayOfWeek(date, dow = 0) {
+  if (!isValidDate(date)) {
+    return null;
+  }
+
+  const d = new Date(date);
+  const day = date.getDay();
+  const diff = (d.getDate() - day) + dow;
+  return new Date(d.setDate(diff));
+}


Test patch:
diff --git a/src/js/Pickers/__tests__/CalendarDate.js b/src/js/Pickers/__tests__/CalendarDate.js
index 1e1c486431..3f32b9764c 100644
--- a/src/js/Pickers/__tests__/CalendarDate.js
+++ b/src/js/Pickers/__tests__/CalendarDate.js
@@ -20,7 +20,6 @@ describe('CalendarDate', () => {
       date: new Date(2016, 1, 1),
       disabled: false,
       onClick: jest.fn(),
-      timeZone: 'UTC',
     };
 
     const date = renderIntoDocument(<CalendarDate {...props} />);
@@ -35,7 +34,6 @@ describe('CalendarDate', () => {
       date: new Date(2016, 1, 1),
       disabled: false,
       onClick: jest.fn(),
-      timeZone: 'UTC',
     };
 
     let date = renderIntoDocument(<CalendarDate {...props} />);
@@ -50,21 +48,16 @@ describe('CalendarDate', () => {
     expect(props.onClick.mock.calls.length).toBe(1);
   });
 
-  it('formats the date into the state using timeZone passed as prop', () => {
+  it('formats the date as the state', () => {
     const props = {
-      DateTimeFormat: jest.fn(() => ({ format: (date) => date.getDate() })),
+      DateTimeFormat,
       locales: 'en-US',
-      date: new Date(2017, 10, 2),
+      date: new Date(2016, 1, 1),
       disabled: false,
       onClick: jest.fn(),
-      timeZone: 'UTC',
     };
 
     const date = renderIntoDocument(<CalendarDate {...props} />);
-    expect(date.state.date).toBe(props.date.getDate());
-    expect(props.DateTimeFormat).toHaveBeenCalledWith(props.locales, {
-      day: 'numeric',
-      timeZone: props.timeZone,
-    });
+    expect(date.state.date).toBe('');
   });
 });
diff --git a/src/js/Pickers/__tests__/CalendarHeader.js b/src/js/Pickers/__tests__/CalendarHeader.js
index cc17c62f51..fe485d884a 100644
--- a/src/js/Pickers/__tests__/CalendarHeader.js
+++ b/src/js/Pickers/__tests__/CalendarHeader.js
@@ -15,25 +15,20 @@ describe('CalendarHeader', () => {
     return jest.fn(() => ({ format: (date) => date.toLocaleString() }));
   }
 
-  it('renders the day of week abbreviations (using the timeZone passed as prop)', () => {
+  it('renders the day of week abbreviations', () => {
     const props = {
-      DateTimeFormat: getDateTimeFormatMock(),
+      DateTimeFormat,
       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', () => {
@@ -46,7 +41,6 @@ describe('CalendarHeader', () => {
       nextIconChildren: 'a',
       date: new Date(),
       firstDayOfWeek: 3,
-      timeZone: 'America/Los_Angeles',
     };
 
     const header = renderIntoDocument(<CalendarHeader {...props} />);
@@ -63,14 +57,13 @@ 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', timeZone: props.timeZone });
+    expect(formatCall[1]).toEqual({ month: 'long', year: 'numeric' });
   });
 
   it('should use "titleClassName" property', () => {
@@ -81,7 +74,6 @@ describe('CalendarHeader', () => {
       onNextClick: jest.fn(),
       date: new Date(),
       titleClassName: 'test-title-class',
-      timeZone: 'UTC',
     };
 
     const header = renderIntoDocument(<CalendarHeader {...props} />);
@@ -99,14 +91,13 @@ 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, timeZone: props.timeZone });
+    expect(formatCall[1]).toEqual(props.titleFormat);
   });
 
   it('should use "weekdayClassName" property', () => {
@@ -118,7 +109,6 @@ describe('CalendarHeader', () => {
       date: new Date(),
       titleClassName: 'test-title-class',
       weekdayClassName: 'test-weekday-class',
-      timeZone: 'UTC',
     };
 
     const header = renderIntoDocument(<CalendarHeader {...props} />);
@@ -136,12 +126,11 @@ 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, timeZone: props.timeZone });
+    expect(formatCall[1]).toEqual({ weekday: props.weekdayFormat });
   });
 });
diff --git a/src/js/Pickers/__tests__/CalendarMonth.js b/src/js/Pickers/__tests__/CalendarMonth.js
index cfb45d95e5..f473b35cec 100644
--- a/src/js/Pickers/__tests__/CalendarMonth.js
+++ b/src/js/Pickers/__tests__/CalendarMonth.js
@@ -25,7 +25,6 @@ describe('CalendarMonth', () => {
       onCalendarDateClick: jest.fn(),
       DateTimeFormat,
       locales: 'en-US',
-      timeZone: 'UTC',
     };
     const calendarMonth = renderIntoDocument(<CalendarMonth {...props} />);
 
@@ -41,14 +40,12 @@ 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', () => {
@@ -60,14 +57,12 @@ 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', () => {
@@ -80,7 +75,6 @@ describe('CalendarMonth', () => {
       firstDayOfWeek: 1,
       showAllDays: true,
       disableOuterDates: true,
-      timeZone: 'UTC',
     };
     const month = shallow(<CalendarMonth {...props} />);
     const days = month.find(CalendarDate);
@@ -106,7 +100,6 @@ 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 1f4590dc3c..c2ba00e36d 100644
--- a/src/js/Pickers/__tests__/DatePicker.js
+++ b/src/js/Pickers/__tests__/DatePicker.js
@@ -5,7 +5,6 @@ import {
   findRenderedDOMComponentWithClass,
   scryRenderedComponentsWithType,
 } from 'react-dom/test-utils';
-import { mount } from 'enzyme';
 
 import DatePicker from '../DatePicker';
 import DatePickerHeader from '../DatePickerHeader';
@@ -39,7 +38,6 @@ const PROPS = {
   nextIcon: <FontIcon>a</FontIcon>,
   previousIcon: <FontIcon>a</FontIcon>,
   yearsDisplayed: 30,
-  timeZone: 'UTC',
 };
 
 describe('DatePicker', () => {
@@ -57,19 +55,6 @@ 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 3e90381cf0..d2dd522a89 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, shallow } from 'enzyme';
+import { mount } from 'enzyme';
 import { findDOMNode } from 'react-dom';
 import {
   renderIntoDocument,
@@ -12,62 +12,6 @@ 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 e2d904a11a..dfd1a72444 100644
--- a/src/js/Pickers/__tests__/DatePickerHeader.js
+++ b/src/js/Pickers/__tests__/DatePickerHeader.js
@@ -1,6 +1,10 @@
 /* eslint-env jest*/
 import React from 'react';
-import { mount } from 'enzyme';
+import { findDOMNode } from 'react-dom';
+import {
+  renderIntoDocument,
+  scryRenderedComponentsWithType,
+} from 'react-dom/test-utils';
 
 import DatePickerHeader from '../DatePickerHeader';
 import PickerControl from '../PickerControl';
@@ -17,12 +21,12 @@ describe('DatePickerHeader', () => {
       locales: 'en-US',
       changeCalendarMode: jest.fn(),
       calendarMode: 'year',
-      timeZone: 'UTC',
       DateTimeFormat,
     };
 
-    const header = mount(<DatePickerHeader {...props} />);
-    expect(header.prop('className')).toContain(props.className);
+    const header = renderIntoDocument(<DatePickerHeader {...props} />);
+    const node = findDOMNode(header);
+    expect(node.classList.contains(props.className)).toBe(true);
   });
 
   it('displays a picker control for selecting the year and a picker control for selecting the calendar', () => {
@@ -32,16 +36,15 @@ describe('DatePickerHeader', () => {
       locales: 'en-US',
       calendarTempDate: new Date(2016, 1, 1),
       calendarMode: 'year',
-      timeZone: 'UTC',
     };
 
-    const header = mount(<DatePickerHeader {...props} />);
-    const controls = header.find(PickerControl);
+    const header = renderIntoDocument(<DatePickerHeader {...props} />);
+    const controls = scryRenderedComponentsWithType(header, PickerControl);
     expect(controls.length).toBe(2);
 
     const [year, calendar] = controls;
-    expect(year.props.onClick).toBe(header.instance()._selectYear);
-    expect(calendar.props.onClick).toBe(header.instance()._selectCalendar);
+    expect(year.props.onClick).toBe(header._selectYear);
+    expect(calendar.props.onClick).toBe(header._selectCalendar);
   });
 
   it('formats the calendar temp date', () => {
@@ -50,26 +53,23 @@ describe('DatePickerHeader', () => {
       locales: 'en-US',
       changeCalendarMode: jest.fn(),
       calendarMode: 'year',
-      timeZone: 'UTC',
       DateTimeFormat,
     };
 
-    const header = mount(<DatePickerHeader {...props} />);
-    const timeZone = header.prop('timeZone');
-    expect(timeZone).toEqual(props.timeZone);
+    const header = renderIntoDocument(<DatePickerHeader {...props} />);
 
     const [time, weekday, date] = DateTimeFormat.mock.calls;
     expect(time[0]).toBe(props.locales);
-    expect(time[1]).toEqual({ year: 'numeric', timeZone });
+    expect(time[1]).toEqual({ year: 'numeric' });
 
     expect(weekday[0]).toBe(props.locales);
-    expect(weekday[1]).toEqual({ weekday: 'short', timeZone });
+    expect(weekday[1]).toEqual({ weekday: 'short' });
 
     expect(date[0]).toBe(props.locales);
-    expect(date[1]).toEqual({ month: 'short', day: '2-digit', timeZone });
+    expect(date[1]).toEqual({ month: 'short', day: '2-digit' });
 
-    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('');
   });
 });
diff --git a/src/js/utils/DateUtils/__tests__/addDate.js b/src/js/utils/DateUtils/__tests__/addDate.js
deleted file mode 100644
index fa91b3cc08..0000000000
--- a/src/js/utils/DateUtils/__tests__/addDate.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/* eslint-env jest */
-import addDate from '../addDate';
-
-const march14 = new Date(2016, 2, 14, 15, 22, 18, 450);
-describe('addDate', () => {
-  it('adds days to a date', () => {
-    const march15 = new Date(2016, 2, 15, 15, 22, 18, 450);
-    const march22 = new Date(2016, 2, 22, 15, 22, 18, 450);
-
-    expect(addDate(march14, 1, 'D')).toEqual(march15);
-    expect(addDate(march14, 8, 'D')).toEqual(march22);
-  });
-
-  it('can add negative days to a date', () => {
-    const march13 = new Date(2016, 2, 13, 15, 22, 18, 450);
-    const march1 = new Date(2016, 2, 1, 15, 22, 18, 450);
-
-    expect(addDate(march14, -1, 'D')).toEqual(march13);
-    expect(addDate(march14, -13, 'D')).toEqual(march1);
-  });
-});
diff --git a/src/js/utils/DateUtils/__tests__/getDayOfWeek.js b/src/js/utils/DateUtils/__tests__/getDayOfWeek.js
deleted file mode 100644
index 531a31677e..0000000000
--- a/src/js/utils/DateUtils/__tests__/getDayOfWeek.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/* eslint-env jest */
-import getDayOfWeek from '../getDayOfWeek';
-
-describe('getDayOfWeek', () => {
-  it('gets a relative day of the week from any given date', () => {
-    const monday2016April4 = new Date(2016, 3, 4);
-    const sunday2016April3 = new Date(2016, 3, 3);
-
-    expect(getDayOfWeek(monday2016April4, 0)).toEqual(sunday2016April3);
-    expect(getDayOfWeek(monday2016April4, 1)).toEqual(monday2016April4);
-  });
-
-  it('can return a day of the week from the previous month', () => {
-    const april1 = new Date(2016, 3, 1);
-    const march31 = new Date(2016, 2, 31);
-
-    expect(getDayOfWeek(april1, 4)).toEqual(march31);
-  });
-
-  it('can return a day of the week from the next month', () => {
-    const march31 = new Date(2016, 2, 31);
-    const april1 = new Date(2016, 3, 1);
-
-    expect(getDayOfWeek(march31, 5)).toEqual(april1);
-  });
-});
diff --git a/src/js/utils/DateUtils/__tests__/getLastDay.js b/src/js/utils/DateUtils/__tests__/getLastDay.js
deleted file mode 100644
index 097b2680a1..0000000000
--- a/src/js/utils/DateUtils/__tests__/getLastDay.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/* eslint-env jest */
-import getLastDay from '../getLastDay';
-
-const march14 = new Date(2016, 2, 14, 15, 22, 18, 450);
-
-describe('getLastDate', () => {
-  it('strips the time from the last day in the month', () => {
-    const march31 = new Date(2016, 2, 31);
-    expect(getLastDay(march14)).toEqual(march31);
-  });
-});
diff --git a/src/js/utils/DateUtils/__tests__/isDateEqual.js b/src/js/utils/DateUtils/__tests__/isDateEqual.js
deleted file mode 100644
index b44a5fb590..0000000000
--- a/src/js/utils/DateUtils/__tests__/isDateEqual.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/* eslint-env jest */
-import isDateEqual from '../isDateEqual';
-
-describe('isDateEqual', () => {
-  it('allows nulls', () => {
-    expect(isDateEqual(null, new Date())).toBe(false);
-    expect(isDateEqual(new Date(), null)).toBe(false);
-    expect(isDateEqual(null, null)).toBe(true);
-  });
-
-  it('allows undefined', () => {
-    expect(isDateEqual(undefined, new Date())).toBe(false);
-    expect(isDateEqual(new Date(), undefined)).toBe(false);
-    expect(isDateEqual(undefined, undefined)).toBe(true);
-  });
-
-  it('returns true if the dates are equal by comparing getTime', () => {
-    const d1 = new Date(2016, 3, 2);
-    let d2 = new Date(2016, 3, 2);
-
-    expect(isDateEqual(d1, d2)).toBe(true);
-
-    d2 = new Date(2016, 3, 2, 100);
-    expect(isDateEqual(d1, d2)).toBe(false);
-  });
-});
diff --git a/src/js/utils/DateUtils/__tests__/stripTime.js b/src/js/utils/DateUtils/__tests__/stripTime.js
deleted file mode 100644
index 5a774d5502..0000000000
--- a/src/js/utils/DateUtils/__tests__/stripTime.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/* eslint-env jest */
-import stripTime from '../stripTime';
-
-const march14 = new Date(2016, 2, 14, 15, 22, 18, 450);
-
-describe('stripTime', () => {
-  it('removes all time from a date. Only keeps year, month, and date', () => {
-    const expected = new Date(2016, 2, 14);
-    expect(stripTime(march14)).toEqual(expected);
-  });
-
-  it('returns null if the date is undefined or null', () => {
-    expect(stripTime(undefined)).toEqual(null);
-    expect(stripTime(null)).toEqual(null);
-  });
-
-  it('returns null if the date is not a Date object', () => {
-    expect(stripTime('Hello!')).toEqual(null);
-  });
-});
diff --git a/src/js/utils/dates/__tests__/addDate.js b/src/js/utils/dates/__tests__/addDate.js
new file mode 100644
index 0000000000..a7899ad5ae
--- /dev/null
+++ b/src/js/utils/dates/__tests__/addDate.js
@@ -0,0 +1,48 @@
+/* eslint-env jest */
+import addDate from '../addDate';
+import addDay from '../addDay';
+import addMonth from '../addMonth';
+import addYear from '../addYear';
+
+jest.mock('../addDay');
+jest.mock('../addMonth');
+jest.mock('../addYear');
+
+describe('addDate', () => {
+  it('should call the correct helper utils based on the part param', () => {
+    const jan1 = new Date(2018, 0, 1);
+    addDate(null, 1, 'D');
+    addDate(jan1, 5, 'D');
+    expect(addDay).toBeCalledWith(null, 1);
+    expect(addDay).toBeCalledWith(jan1, 5);
+    expect(addMonth).not.toBeCalled();
+    expect(addYear).not.toBeCalled();
+
+    addDay.mockClear();
+    addDate(null, 1, 'M');
+    addDate(jan1, 1, 'M');
+    addDate(null, 1, 'M', 12);
+    addDate(jan1, 1, 'M', 1);
+    expect(addMonth).toBeCalledWith(null, 1, undefined);
+    expect(addMonth).toBeCalledWith(jan1, 1, undefined);
+    expect(addMonth).toBeCalledWith(null, 1, 12);
+    expect(addMonth).toBeCalledWith(jan1, 1, 1);
+    expect(addDay).not.toBeCalled();
+    expect(addYear).not.toBeCalled();
+
+    addMonth.mockClear();
+    addDate(null, 1, 'Y');
+    addDate(jan1, 1, 'Y');
+    expect(addYear).toBeCalledWith(null, 1);
+    expect(addYear).toBeCalledWith(jan1, 1);
+    expect(addDay).not.toBeCalled();
+    expect(addMonth).not.toBeCalled();
+  });
+
+  it('should return whatever value was provided if it is not a valid part', () => {
+    const jan1 = new Date(2018, 0, 1);
+    expect(addDate(null, 1, 'y')).toBe(null);
+    expect(addDate('hello', 1, '')).toBe('hello');
+    expect(addDate(jan1, 1, 'y')).toBe(jan1);
+  });
+});
diff --git a/src/js/utils/dates/__tests__/addDay.js b/src/js/utils/dates/__tests__/addDay.js
new file mode 100644
index 0000000000..644ecfa5b8
--- /dev/null
+++ b/src/js/utils/dates/__tests__/addDay.js
@@ -0,0 +1,72 @@
+/* eslint-env jest */
+import addDay from '../addDay';
+
+const JAN_1_2018 = new Date(2018, 0, 1);
+const JUNE_15_2018 = new Date(2018, 5, 15);
+
+describe('addDay', () => {
+  it('should return null for all invalid dates', () => {
+    expect(addDay(undefined, 0)).toBe(null);
+    expect(addDay(null, 0)).toBe(null);
+    expect(addDay('12', 0)).toBe(null);
+    expect(addDay(0, 0)).toBe(null);
+    expect(addDay(new Date('23--33'), 0)).toBe(null);
+  });
+
+  it('should return a new date even if the amount is 0', () => {
+    const result1 = addDay(JAN_1_2018, 0);
+    const result2 = addDay(JUNE_15_2018, 0);
+    expect(result1).toEqual(JAN_1_2018);
+    expect(result1).not.toBe(JAN_1_2018);
+    expect(result2).toEqual(JUNE_15_2018);
+    expect(result2).not.toBe(JUNE_15_2018);
+  });
+
+  it('should correctly add days that are positive', () => {
+    expect(addDay(JAN_1_2018, 1)).toEqual(new Date(2018, 0, 2));
+    expect(addDay(JAN_1_2018, 30)).toEqual(new Date(2018, 0, 31));
+    expect(addDay(JAN_1_2018, 31)).toEqual(new Date(2018, 1, 1));
+    expect(addDay(JAN_1_2018, 100)).toEqual(new Date(2018, 3, 11));
+    expect(addDay(JAN_1_2018, 365)).toEqual(new Date(2019, 0, 1));
+
+    expect(addDay(JUNE_15_2018, 1)).toEqual(new Date(2018, 5, 16));
+    expect(addDay(JUNE_15_2018, 30)).toEqual(new Date(2018, 6, 15));
+    expect(addDay(JUNE_15_2018, 31)).toEqual(new Date(2018, 6, 16));
+    expect(addDay(JUNE_15_2018, 100)).toEqual(new Date(2018, 8, 23));
+    expect(addDay(JUNE_15_2018, 365)).toEqual(new Date(2019, 5, 15));
+  });
+
+  it('should correctly add days that are negative', () => {
+    expect(addDay(JAN_1_2018, -1)).toEqual(new Date(2017, 11, 31));
+    expect(addDay(JAN_1_2018, -30)).toEqual(new Date(2017, 11, 2));
+    expect(addDay(JAN_1_2018, -31)).toEqual(new Date(2017, 10, 31));
+    expect(addDay(JAN_1_2018, -100)).toEqual(new Date(2017, 8, 23));
+    expect(addDay(JAN_1_2018, -365)).toEqual(new Date(2017, 0, 1));
+
+    expect(addDay(JUNE_15_2018, -1)).toEqual(new Date(2018, 5, 14));
+    expect(addDay(JUNE_15_2018, -30)).toEqual(new Date(2018, 4, 16));
+    expect(addDay(JUNE_15_2018, -31)).toEqual(new Date(2018, 4, 15));
+    expect(addDay(JUNE_15_2018, -100)).toEqual(new Date(2018, 2, 7));
+    expect(addDay(JUNE_15_2018, -365)).toEqual(new Date(2017, 5, 15));
+  });
+
+  it('should correctly keep the time when adding days', () => {
+    const date1 = new Date(2018, 1, 2, 8, 0, 30);
+    const expected1 = new Date(2018, 1, 3, 8, 0, 30);
+    const expected2 = new Date(2018, 2, 4, 8, 0, 30);
+
+    expect(addDay(date1, 1)).toEqual(expected1);
+    expect(addDay(date1, 30)).toEqual(expected2);
+
+    const date2 = new Date(2018, 5, 15, 23, 59, 0);
+    const expected3 = new Date(2018, 5, 14, 23, 59, 0);
+    const expected4 = new Date(2018, 5, 16, 23, 59, 0);
+    expect(addDay(date2, -1)).toEqual(expected3);
+    expect(addDay(date2, 1)).toEqual(expected4);
+  });
+
+  it('should be able to add fractional dates even though it is never used', () => {
+    expect(addDay(JAN_1_2018, 1.5)).toEqual(new Date(2018, 0, 2, 12));
+    expect(addDay(JAN_1_2018, 0.5)).toEqual(new Date(2018, 0, 1, 12));
+  });
+});
diff --git a/src/js/utils/dates/__tests__/addMonth.js b/src/js/utils/dates/__tests__/addMonth.js
new file mode 100644
index 0000000000..25a9225114
--- /dev/null
+++ b/src/js/utils/dates/__tests__/addMonth.js
@@ -0,0 +1,69 @@
+/* eslint-env jest */
+import addMonth from '../addMonth';
+
+const JAN_1_2018 = new Date(2018, 0, 1);
+
+describe('addMonth', () => {
+  it('should return null for invalid dates', () => {
+    expect(addMonth(undefined, 1)).toBe(null);
+    expect(addMonth(null, 1)).toBe(null);
+    expect(addMonth(0, 1)).toBe(null);
+    expect(addMonth('', 1)).toBe(null);
+    expect(addMonth(new Date('23--33'), 1)).toBe(null);
+  });
+
+  it('should return a new Date instance even if the amount is 0', () => {
+    const result = addMonth(JAN_1_2018, 0);
+    expect(result).toEqual(JAN_1_2018);
+    expect(result).not.toBe(JAN_1_2018);
+  });
+
+  it('should correctly add months that are positive', () => {
+    expect(addMonth(JAN_1_2018, 1)).toEqual(new Date(2018, 1, 1));
+    expect(addMonth(JAN_1_2018, 100)).toEqual(new Date(2026, 4, 1));
+    expect(addMonth(JAN_1_2018, 1000)).toEqual(new Date(2101, 4, 1));
+  });
+
+  it('should correctly add months that are negative', () => {
+    expect(addMonth(JAN_1_2018, -1)).toEqual(new Date(2017, 11, 1));
+    expect(addMonth(JAN_1_2018, -100)).toEqual(new Date(2009, 8, 1));
+    expect(addMonth(JAN_1_2018, -1000)).toEqual(new Date(1934, 8, 1));
+  });
+
+  it('should correctly keep the date within the month if the new month also contains it', () => {
+    const jan15 = new Date(2018, 0, 15);
+    expect(addMonth(jan15, 1)).toEqual(new Date(2018, 1, 15));
+    expect(addMonth(jan15, 3)).toEqual(new Date(2018, 3, 15));
+  });
+
+  it('should default to the first day of the month if the new month does not contain the day within the month', () => {
+    const jan31 = new Date(2018, 0, 31);
+    expect(addMonth(jan31, 1)).toEqual(new Date(2018, 1, 1));
+    expect(addMonth(jan31, 2)).toEqual(new Date(2018, 2, 31));
+    expect(addMonth(jan31, 3)).toEqual(new Date(2018, 3, 1));
+  });
+
+  it('should correctly set the newMonthDate if it exists in the new month', () => {
+    expect(addMonth(JAN_1_2018, 1, 12)).toEqual(new Date(2018, 1, 12));
+    expect(addMonth(new Date(2018, 1, 1), 3, 31)).toEqual(new Date(2018, 4, 31));
+  });
+
+  it('should default back to the first date of the month if the newMonthDate does not exist in the new month', () => {
+    expect(addMonth(JAN_1_2018, 1, 31)).toEqual(new Date(2018, 1, 1));
+    expect(addMonth(new Date(2018, 1, 1), 2, 31)).toEqual(new Date(2018, 3, 1));
+  });
+
+  it('should correctly keep any time when adding new months', () => {
+    const date1 = new Date(2018, 0, 1, 12, 30);
+    const expected1 = new Date(2018, 1, 1, 12, 30);
+    const expected2 = new Date(2020, 6, 1, 12, 30);
+    expect(addMonth(date1, 1)).toEqual(expected1);
+    expect(addMonth(date1, 30)).toEqual(expected2);
+
+    const date2 = new Date(2000, 3, 15, 15, 22, 59);
+    const expected3 = new Date(2000, 4, 15, 15, 22, 59);
+    const expected4 = new Date(2002, 9, 15, 15, 22, 59);
+    expect(addMonth(date2, 1)).toEqual(expected3);
+    expect(addMonth(date2, 30)).toEqual(expected4);
+  });
+});
diff --git a/src/js/utils/dates/__tests__/addYear.js b/src/js/utils/dates/__tests__/addYear.js
new file mode 100644
index 0000000000..51e6e2cfef
--- /dev/null
+++ b/src/js/utils/dates/__tests__/addYear.js
@@ -0,0 +1,44 @@
+/* eslint-env jest */
+import addYear from '../addYear';
+
+const JAN_1_2018 = new Date(2018, 0, 1);
+const JUNE_15_2018 = new Date(2018, 5, 15);
+
+describe('addYear', () => {
+  it('should return null for invalid dates', () => {
+    expect(addYear(undefined, 0)).toBe(null);
+    expect(addYear(null, 0)).toBe(null);
+    expect(addYear('12', 0)).toBe(null);
+    expect(addYear(0, 0)).toBe(null);
+    expect(addYear(new Date('23--33'), 0)).toBe(null);
+  });
+
+  it('should return a new date even if the amount is 0', () => {
+    const result1 = addYear(JAN_1_2018, 0);
+    const result2 = addYear(JUNE_15_2018, 0);
+    expect(result1).toEqual(JAN_1_2018);
+    expect(result1).not.toBe(JAN_1_2018);
+    expect(result2).toEqual(JUNE_15_2018);
+    expect(result2).not.toBe(JUNE_15_2018);
+  });
+
+  it('should correctly add days that are positive', () => {
+    expect(addYear(JAN_1_2018, 1)).toEqual(new Date(2019, 0, 1));
+    expect(addYear(JAN_1_2018, 100)).toEqual(new Date(2118, 0, 1));
+    expect(addYear(JAN_1_2018, 1000)).toEqual(new Date(3018, 0, 1));
+
+    expect(addYear(JUNE_15_2018, 1)).toEqual(new Date(2019, 5, 15));
+    expect(addYear(JUNE_15_2018, 100)).toEqual(new Date(2118, 5, 15));
+    expect(addYear(JUNE_15_2018, 1000)).toEqual(new Date(3018, 5, 15));
+  });
+
+  it('should correctly add days that are negative', () => {
+    const year18 = new Date(18, 0, 1); // really 1918 at this point.
+    year18.setFullYear(18);
+
+    expect(addYear(JAN_1_2018, -1)).toEqual(new Date(2017, 0, 1));
+    expect(addYear(JAN_1_2018, -100)).toEqual(new Date(1918, 0, 1));
+    expect(addYear(JAN_1_2018, -1000)).toEqual(new Date(1018, 0, 1));
+    expect(addYear(JAN_1_2018, -2000)).toEqual(year18);
+  });
+});
diff --git a/src/js/utils/dates/__tests__/getDaysInMonth.js b/src/js/utils/dates/__tests__/getDaysInMonth.js
new file mode 100644
index 0000000000..3369f82bea
--- /dev/null
+++ b/src/js/utils/dates/__tests__/getDaysInMonth.js
@@ -0,0 +1,21 @@
+/* eslint-env jest */
+import getDaysInMonth from '../getDaysInMonth';
+
+describe('getDaysInMonth', () => {
+  it('should return -1 for invalid dates', () => {
+    expect(getDaysInMonth(undefined)).toBe(-1);
+    expect(getDaysInMonth(null)).toBe(-1);
+    expect(getDaysInMonth(0)).toBe(-1);
+    expect(getDaysInMonth('9324')).toBe(-1);
+    expect(getDaysInMonth(new Date('23--33'))).toBe(-1);
+  });
+
+  it('should return the number of days in a month', () => {
+    expect(getDaysInMonth(new Date(2018, 0, 1))).toBe(31);
+    expect(getDaysInMonth(new Date(2018, 0, 31))).toBe(31);
+    expect(getDaysInMonth(new Date(2018, 2, 1))).toBe(31);
+    expect(getDaysInMonth(new Date(2018, 4, 1))).toBe(31);
+    expect(getDaysInMonth(new Date(2018, 1, 1))).toBe(28);
+    expect(getDaysInMonth(new Date(2020, 1, 1))).toBe(29);
+  });
+});
diff --git a/src/js/utils/dates/__tests__/getLastDay.js b/src/js/utils/dates/__tests__/getLastDay.js
new file mode 100644
index 0000000000..a10c41301d
--- /dev/null
+++ b/src/js/utils/dates/__tests__/getLastDay.js
@@ -0,0 +1,34 @@
+/* eslint-disable max-len */
+/* eslint-env jest */
+import getLastDay from '../getLastDay';
+
+describe('getLastDay', () => {
+  it('should return null for invalid dates', () => {
+    expect(getLastDay(null, 0)).toBe(null);
+    expect(getLastDay(new Date('20--33'), 0)).toBe(null);
+    expect(getLastDay(false, 0)).toBe(null);
+  });
+
+  it('should return the last day in the provided month as a new date even if the provided date is the last day in the month', () => {
+    const feb28 = new Date(2018, 1, 28);
+    const feb29 = new Date(2020, 1, 29);
+    const dec31 = new Date(2018, 11, 31);
+    const result = getLastDay(feb28);
+
+    expect(result).toEqual(feb28);
+    expect(result).not.toBe(feb28);
+
+    expect(getLastDay(new Date(2018, 1, 1))).toEqual(feb28);
+    expect(getLastDay(new Date(2018, 1, 15))).toEqual(feb28);
+    expect(getLastDay(new Date(2018, 1, 28))).toEqual(feb28);
+
+    expect(getLastDay(new Date(2018, 11, 1))).toEqual(dec31);
+    expect(getLastDay(new Date(2018, 11, 15))).toEqual(dec31);
+    expect(getLastDay(new Date(2018, 11, 31))).toEqual(dec31);
+
+    expect(getLastDay(new Date(2020, 1, 1))).toEqual(feb29);
+    expect(getLastDay(new Date(2020, 1, 15))).toEqual(feb29);
+    expect(getLastDay(new Date(2020, 1, 28))).toEqual(feb29);
+    expect(getLastDay(new Date(2020, 1, 29))).toEqual(feb29);
+  });
+});
diff --git a/src/js/utils/dates/__tests__/getWeekNumber.js b/src/js/utils/dates/__tests__/getWeekNumber.js
new file mode 100644
index 0000000000..f8fd975838
--- /dev/null
+++ b/src/js/utils/dates/__tests__/getWeekNumber.js
@@ -0,0 +1,23 @@
+/* eslint-env jest */
+import getWeekNumber from '../getWeekNumber';
+
+describe('getWeekNumber', () => {
+  it('should return -1 for invalid dates', () => {
+    expect(getWeekNumber(undefined)).toBe(-1);
+    expect(getWeekNumber(null)).toBe(-1);
+    expect(getWeekNumber('')).toBe(-1);
+    expect(getWeekNumber(0)).toBe(-1);
+    expect(getWeekNumber(new Date('23---332'))).toBe(-1);
+  });
+
+  it('should correctly return the week number for a provided date', () => {
+    expect(getWeekNumber(new Date(2018, 0, 1))).toBe(1);
+    expect(getWeekNumber(new Date(2018, 0, 7))).toBe(2);
+    expect(getWeekNumber(new Date(2018, 3, 1))).toBe(1);
+    expect(getWeekNumber(new Date(2018, 3, 8))).toBe(2);
+    expect(getWeekNumber(new Date(2018, 3, 28))).toBe(4);
+    expect(getWeekNumber(new Date(2020, 1, 22))).toBe(4);
+    expect(getWeekNumber(new Date(2020, 1, 28))).toBe(5);
+    expect(getWeekNumber(new Date(2020, 1, 29))).toBe(5);
+  });
+});
diff --git a/src/js/utils/dates/__tests__/isFirstWeek.js b/src/js/utils/dates/__tests__/isFirstWeek.js
new file mode 100644
index 0000000000..167027a2d1
--- /dev/null
+++ b/src/js/utils/dates/__tests__/isFirstWeek.js
@@ -0,0 +1,21 @@
+/* eslint-env jest */
+import isLastWeek from '../isLastWeek';
+
+describe('isLastWeek', () => {
+  it('should return false for invalid dates', () => {
+    expect(isLastWeek(undefined)).toBe(false);
+    expect(isLastWeek(null)).toBe(false);
+    expect(isLastWeek('')).toBe(false);
+    expect(isLastWeek(0)).toBe(false);
+    expect(isLastWeek(new Date('23--43'))).toBe(false);
+  });
+
+  it.only('should correctly figure out if the date is within the first week', () => {
+    expect(isLastWeek(new Date(2018, 0, 1))).toBe(false);
+    expect(isLastWeek(new Date(2018, 0, 6))).toBe(false);
+    expect(isLastWeek(new Date(2018, 0, 7))).toBe(false);
+    expect(isLastWeek(new Date(2018, 0, 31))).toBe(true);
+    expect(isLastWeek(new Date(2018, 1, 1))).toBe(false);
+    expect(isLastWeek(new Date(2018, 1, 28))).toBe(true);
+  });
+});
diff --git a/src/js/utils/dates/__tests__/isLastWeek.js b/src/js/utils/dates/__tests__/isLastWeek.js
new file mode 100644
index 0000000000..bd2127f073
--- /dev/null
+++ b/src/js/utils/dates/__tests__/isLastWeek.js
@@ -0,0 +1,21 @@
+/* eslint-env jest */
+import isFirstWeek from '../isFirstWeek';
+
+describe('isFirstWeek', () => {
+  it('should return false for invalid dates', () => {
+    expect(isFirstWeek(undefined)).toBe(false);
+    expect(isFirstWeek(null)).toBe(false);
+    expect(isFirstWeek('')).toBe(false);
+    expect(isFirstWeek(0)).toBe(false);
+    expect(isFirstWeek(new Date('23--43'))).toBe(false);
+  });
+
+  it('should correctly figure out if the date is within the first week', () => {
+    expect(isFirstWeek(new Date(2018, 0, 1))).toBe(true);
+    expect(isFirstWeek(new Date(2018, 0, 6))).toBe(true);
+    expect(isFirstWeek(new Date(2018, 0, 7))).toBe(false);
+    expect(isFirstWeek(new Date(2018, 0, 31))).toBe(false);
+    expect(isFirstWeek(new Date(2018, 1, 1))).toBe(true);
+    expect(isFirstWeek(new Date(2018, 1, 28))).toBe(false);
+  });
+});
diff --git a/src/js/utils/DateUtils/__tests__/isMonthBefore.js b/src/js/utils/dates/__tests__/isMonthBefore.js
similarity index 67%
rename from src/js/utils/DateUtils/__tests__/isMonthBefore.js
rename to src/js/utils/dates/__tests__/isMonthBefore.js
index 7f92eec9a1..3751578e4c 100644
--- a/src/js/utils/DateUtils/__tests__/isMonthBefore.js
+++ b/src/js/utils/dates/__tests__/isMonthBefore.js
@@ -3,22 +3,22 @@ import isMonthBefore from '../isMonthBefore';
 
 const march14 = new Date(2016, 2, 14, 15, 22, 18, 450);
 describe('isMonthBefore', () => {
-  it('returns true if a month is before another month', () => {
+  it('should return true if a month is before another month', () => {
     const february28 = new Date(2016, 1, 28);
     expect(isMonthBefore(march14, february28)).toBe(true);
   });
 
-  it('returns false if a month is after another month', () => {
+  it('should return false if a month is after another month', () => {
     const april1 = new Date(2016, 3, 1);
     expect(isMonthBefore(march14, april1)).toBe(false);
   });
 
-  it('returns true if they are the same month', () => {
+  it('should return false if they are the same month', () => {
     const march28 = new Date(2016, 2, 28);
     expect(isMonthBefore(march14, march28)).toBe(true);
   });
 
-  it('returns false if the month is before but in a later year', () => {
+  it('should return false if the month is before but in a later year', () => {
     const february28 = new Date(2017, 1, 28);
     expect(isMonthBefore(march14, february28)).toBe(false);
   });
diff --git a/src/js/utils/dates/__tests__/isSameDay.js b/src/js/utils/dates/__tests__/isSameDay.js
new file mode 100644
index 0000000000..53920660f7
--- /dev/null
+++ b/src/js/utils/dates/__tests__/isSameDay.js
@@ -0,0 +1,45 @@
+/* eslint-env jest */
+import isSameDay from '../isSameDay';
+
+describe('isSameDay', () => {
+  it('should return false by default if both values are null-ish', () => {
+    expect(isSameDay(null, null)).toBe(false);
+    expect(isSameDay(undefined, undefined)).toBe(false);
+  });
+
+  it('should return the third param if both values are null', () => {
+    expect(isSameDay(null, null, false)).toBe(false);
+    expect(isSameDay(null, null, true)).toBe(true);
+    expect(isSameDay(null, null, null)).toBe(null);
+  });
+
+  it('should return false if one of the dates is not a valid date', () => {
+    const d = new Date();
+    const invalid1 = 0;
+    const invalid2 = '';
+    const invalid3 = null;
+    const invalid4 = undefined;
+    const invalid5 = new Date('20--23');
+    expect(isSameDay(d, invalid1)).toBe(false);
+    expect(isSameDay(d, invalid2)).toBe(false);
+    expect(isSameDay(d, invalid3)).toBe(false);
+    expect(isSameDay(d, invalid4)).toBe(false);
+    expect(isSameDay(d, invalid5)).toBe(false);
+    expect(isSameDay(invalid1, d)).toBe(false);
+    expect(isSameDay(invalid2, d)).toBe(false);
+    expect(isSameDay(invalid3, d)).toBe(false);
+    expect(isSameDay(invalid4, d)).toBe(false);
+    expect(isSameDay(invalid5, d)).toBe(false);
+  });
+
+  it('should return true if the dates are part of the same year, month, and day', () => {
+    expect(isSameDay(new Date(2018, 0, 1), new Date(2018, 0, 1))).toBe(true);
+    expect(isSameDay(new Date(2018, 0, 1), new Date(2018, 0, 1, 8))).toBe(true);
+    expect(isSameDay(new Date(2018, 0, 1), new Date(2018, 0, 1, 1))).toBe(true);
+    expect(isSameDay(new Date(2018, 0, 1), new Date(2018, 0, 1, 12))).toBe(true);
+
+    expect(isSameDay(new Date(2018, 0, 1), new Date(2018, 0, 2))).toBe(false);
+    expect(isSameDay(new Date(2018, 0, 1), new Date(2019, 0, 2))).toBe(false);
+    expect(isSameDay(new Date(2018, 0, 1), new Date(2014, 0, 1))).toBe(false);
+  });
+});
diff --git a/src/js/utils/dates/__tests__/isSameMonth.js b/src/js/utils/dates/__tests__/isSameMonth.js
new file mode 100644
index 0000000000..42d3c96ea2
--- /dev/null
+++ b/src/js/utils/dates/__tests__/isSameMonth.js
@@ -0,0 +1,52 @@
+/* eslint-env jest */
+import isSameMonth from '../isSameMonth';
+
+describe('isSameMonth', () => {
+  it('should return false by default if both values are null-ish', () => {
+    expect(isSameMonth(null, null)).toBe(false);
+    expect(isSameMonth(undefined, undefined)).toBe(false);
+  });
+
+  it('should return the third param if both values are null', () => {
+    expect(isSameMonth(null, null, false)).toBe(false);
+    expect(isSameMonth(null, null, true)).toBe(true);
+    expect(isSameMonth(null, null, null)).toBe(null);
+  });
+
+  it('should return false if one of the dates is not a valid date', () => {
+    const d = new Date();
+    const invalid1 = 0;
+    const invalid2 = '';
+    const invalid3 = null;
+    const invalid4 = undefined;
+    const invalid5 = new Date('20--23');
+    expect(isSameMonth(d, invalid1)).toBe(false);
+    expect(isSameMonth(d, invalid2)).toBe(false);
+    expect(isSameMonth(d, invalid3)).toBe(false);
+    expect(isSameMonth(d, invalid4)).toBe(false);
+    expect(isSameMonth(d, invalid5)).toBe(false);
+    expect(isSameMonth(invalid1, d)).toBe(false);
+    expect(isSameMonth(invalid2, d)).toBe(false);
+    expect(isSameMonth(invalid3, d)).toBe(false);
+    expect(isSameMonth(invalid4, d)).toBe(false);
+    expect(isSameMonth(invalid5, d)).toBe(false);
+  });
+
+  it('should return true if the dates are part of the same year and month', () => {
+    expect(isSameMonth(new Date(2018, 0, 1), new Date(2018, 0, 1))).toBe(true);
+    expect(isSameMonth(new Date(2018, 0, 1), new Date(2018, 0, 15))).toBe(true);
+    expect(isSameMonth(new Date(2018, 0, 1), new Date(2018, 0, 31))).toBe(true);
+    expect(isSameMonth(new Date(2018, 0, 15), new Date(2018, 0, 1))).toBe(true);
+    expect(isSameMonth(new Date(2018, 0, 31), new Date(2018, 0, 15))).toBe(true);
+    expect(isSameMonth(new Date(2018, 4, 1), new Date(2018, 4, 1))).toBe(true);
+    expect(isSameMonth(new Date(2018, 4, 1), new Date(2018, 4, 15))).toBe(true);
+    expect(isSameMonth(new Date(2018, 4, 1), new Date(2018, 4, 31))).toBe(true);
+    expect(isSameMonth(new Date(2018, 4, 15), new Date(2018, 4, 1))).toBe(true);
+    expect(isSameMonth(new Date(2018, 4, 31), new Date(2018, 4, 15))).toBe(true);
+
+    expect(isSameMonth(new Date(2018, 0, 1), new Date(2014, 0, 1))).toBe(false);
+    expect(isSameMonth(new Date(2018, 0, 1), new Date(2017, 0, 1))).toBe(false);
+    expect(isSameMonth(new Date(2018, 0, 1), new Date(2018, 1, 1))).toBe(false);
+    expect(isSameMonth(new Date(2018, 0, 1), new Date(2018, 2, 15))).toBe(false);
+  });
+});
diff --git a/src/js/utils/dates/__tests__/isSameYear.js b/src/js/utils/dates/__tests__/isSameYear.js
new file mode 100644
index 0000000000..4e07502ea4
--- /dev/null
+++ b/src/js/utils/dates/__tests__/isSameYear.js
@@ -0,0 +1,42 @@
+/* eslint-env jest */
+import isSameYear from '../isSameYear';
+
+describe('isSameYear', () => {
+  it('should return false by default if both values are null-ish', () => {
+    expect(isSameYear(null, null)).toBe(false);
+    expect(isSameYear(undefined, undefined)).toBe(false);
+  });
+
+  it('should return the third param if both values are null', () => {
+    expect(isSameYear(null, null, false)).toBe(false);
+    expect(isSameYear(null, null, true)).toBe(true);
+    expect(isSameYear(null, null, null)).toBe(null);
+  });
+
+  it('should return false if one of the dates is not a valid date', () => {
+    const d = new Date();
+    const invalid1 = 0;
+    const invalid2 = '';
+    const invalid3 = null;
+    const invalid4 = undefined;
+    const invalid5 = new Date('20--23');
+    expect(isSameYear(d, invalid1)).toBe(false);
+    expect(isSameYear(d, invalid2)).toBe(false);
+    expect(isSameYear(d, invalid3)).toBe(false);
+    expect(isSameYear(d, invalid4)).toBe(false);
+    expect(isSameYear(d, invalid5)).toBe(false);
+    expect(isSameYear(invalid1, d)).toBe(false);
+    expect(isSameYear(invalid2, d)).toBe(false);
+    expect(isSameYear(invalid3, d)).toBe(false);
+    expect(isSameYear(invalid4, d)).toBe(false);
+    expect(isSameYear(invalid5, d)).toBe(false);
+  });
+
+  it('should return true if the dates are part of the same year', () => {
+    expect(isSameYear(new Date(2018, 0, 1), new Date(2018, 5, 15))).toBe(true);
+    expect(isSameYear(new Date(2018, 5, 15), new Date(2018, 0, 1))).toBe(true);
+
+    expect(isSameYear(new Date(2017, 5, 15), new Date(2018, 0, 1))).toBe(false);
+    expect(isSameYear(new Date(2017, 11, 31), new Date(2018, 0, 1))).toBe(false);
+  });
+});
diff --git a/src/js/utils/dates/__tests__/isValidDate.js b/src/js/utils/dates/__tests__/isValidDate.js
new file mode 100644
index 0000000000..82e4e07660
--- /dev/null
+++ b/src/js/utils/dates/__tests__/isValidDate.js
@@ -0,0 +1,22 @@
+/* eslint-env jest */
+import isValidDate from '../isValidDate';
+
+describe('isValidDate', () => {
+  it('should return true for valid dates', () => {
+    expect(isValidDate(new Date())).toBe(true);
+    expect(isValidDate(new Date(2017, 0, 1))).toBe(true);
+    expect(isValidDate(new Date(0))).toBe(true);
+  });
+
+  it('should return false for invalid dates', () => {
+    expect(isValidDate(undefined)).toBe(false);
+    expect(isValidDate(null)).toBe(false);
+    expect(isValidDate(0)).toBe(false);
+    expect(isValidDate('')).toBe(false);
+    expect(isValidDate('12321321')).toBe(false);
+    expect(isValidDate(12321321)).toBe(false);
+    expect(isValidDate(new Date('20'))).toBe(false);
+    expect(isValidDate(new Date('20--23'))).toBe(false);
+    expect(isValidDate(new Date('2018-01-01T0322'))).toBe(false);
+  });
+});
diff --git a/src/js/utils/dates/__tests__/stripTime.js b/src/js/utils/dates/__tests__/stripTime.js
new file mode 100644
index 0000000000..e95809f70d
--- /dev/null
+++ b/src/js/utils/dates/__tests__/stripTime.js
@@ -0,0 +1,53 @@
+/* eslint-env jest */
+import stripTime from '../stripTime';
+
+describe('stripTime', () => {
+  it('should return null for invalid dates', () => {
+    expect(stripTime(undefined)).toBe(null);
+    expect(stripTime(null)).toBe(null);
+    expect(stripTime('')).toBe(null);
+    expect(stripTime(0)).toBe(null);
+    expect(stripTime(new Date('23--32'))).toBe(null);
+  });
+
+  it('should return a date without any time', () => {
+    expect(stripTime(new Date(2018, 0, 1, 22))).toEqual(new Date(2018, 0, 1));
+    expect(stripTime(new Date(2018, 0, 1, 22, 3))).toEqual(new Date(2018, 0, 1));
+    expect(stripTime(new Date(2018, 0, 1, 0, 3))).toEqual(new Date(2018, 0, 1));
+    expect(stripTime(new Date(2018, 0, 1, 0, 0))).toEqual(new Date(2018, 0, 1));
+    expect(stripTime(new Date(2018, 0, 1))).toEqual(new Date(2018, 0, 1));
+
+    // for sanity
+    const d = new Date(2018, 0, 1, 22, 30, 45, 899);
+    const result = stripTime(d);
+    expect(d.getHours()).toBe(22);
+    expect(d.getMinutes()).toBe(30);
+    expect(d.getSeconds()).toBe(45);
+    expect(d.getMilliseconds()).toBe(899);
+    expect(result.getHours()).toBe(0);
+    expect(result.getMinutes()).toBe(0);
+    expect(result.getSeconds()).toBe(0);
+    expect(result.getMilliseconds()).toBe(0);
+  });
+
+  it('should return a new date even if the original date didn\'t have time set', () => {
+    const date1 = new Date(2018, 0, 1);
+    const date2 = new Date(2000, 11, 24);
+    const date3 = new Date(1950, 4, 10);
+    const date4 = new Date(2113, 9, 29);
+
+    const result1 = stripTime(date1);
+    const result2 = stripTime(date2);
+    const result3 = stripTime(date3);
+    const result4 = stripTime(date4);
+
+    expect(result1).toEqual(date1);
+    expect(result1).not.toBe(date1);
+    expect(result2).toEqual(date2);
+    expect(result2).not.toBe(date2);
+    expect(result3).toEqual(date3);
+    expect(result3).not.toBe(date3);
+    expect(result4).toEqual(date4);
+    expect(result4).not.toBe(date4);
+  });
+});
diff --git a/src/js/utils/dates/__tests__/toDayOfWeek.js b/src/js/utils/dates/__tests__/toDayOfWeek.js
new file mode 100644
index 0000000000..52619044d4
--- /dev/null
+++ b/src/js/utils/dates/__tests__/toDayOfWeek.js
@@ -0,0 +1,30 @@
+/* eslint-env jest */
+import toDayOfWeek from '../toDayOfWeek';
+
+describe('toDayOfWeek', () => {
+  it('should return null for invalid dates', () => {
+    expect(toDayOfWeek(undefined)).toBe(null);
+    expect(toDayOfWeek(null)).toBe(null);
+    expect(toDayOfWeek('')).toBe(null);
+    expect(toDayOfWeek(0)).toBe(null);
+    expect(toDayOfWeek(new Date('23--23'))).toBe(null);
+  });
+
+  it('should return a new date at the specified day of week', () => {
+    expect(toDayOfWeek(new Date(2018, 0, 1), 0)).toEqual(new Date(2017, 11, 31));
+    expect(toDayOfWeek(new Date(2018, 0, 1), 1)).toEqual(new Date(2018, 0, 1));
+    expect(toDayOfWeek(new Date(2018, 0, 1), 2)).toEqual(new Date(2018, 0, 2));
+    expect(toDayOfWeek(new Date(2018, 0, 1), 3)).toEqual(new Date(2018, 0, 3));
+    expect(toDayOfWeek(new Date(2018, 0, 1), 4)).toEqual(new Date(2018, 0, 4));
+    expect(toDayOfWeek(new Date(2018, 0, 1), 5)).toEqual(new Date(2018, 0, 5));
+    expect(toDayOfWeek(new Date(2018, 0, 1), 6)).toEqual(new Date(2018, 0, 6));
+
+    expect(toDayOfWeek(new Date(2018, 0, 31), 0)).toEqual(new Date(2018, 0, 28));
+    expect(toDayOfWeek(new Date(2018, 0, 31), 1)).toEqual(new Date(2018, 0, 29));
+    expect(toDayOfWeek(new Date(2018, 0, 31), 2)).toEqual(new Date(2018, 0, 30));
+    expect(toDayOfWeek(new Date(2018, 0, 31), 3)).toEqual(new Date(2018, 0, 31));
+    expect(toDayOfWeek(new Date(2018, 0, 31), 4)).toEqual(new Date(2018, 1, 1));
+    expect(toDayOfWeek(new Date(2018, 0, 31), 5)).toEqual(new Date(2018, 1, 2));
+    expect(toDayOfWeek(new Date(2018, 0, 31), 6)).toEqual(new Date(2018, 1, 3));
+  });
+});