]> source.dussan.org Git - sonarqube.git/commitdiff
Use date parsing function from date-fns instead of new Date()
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Tue, 22 Aug 2017 15:07:33 +0000 (17:07 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 25 Aug 2017 09:05:36 +0000 (11:05 +0200)
65 files changed:
server/sonar-web/package.json
server/sonar-web/src/main/js/app/components/LocalizationContainer.tsx
server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx
server/sonar-web/src/main/js/apps/account/projects/__tests__/ProjectCard-test.js
server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.js
server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.tsx
server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.tsx
server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.js
server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/LeakPeriodLegend-test.js.snap
server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.js
server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.js
server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js
server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.js
server/sonar-web/src/main/js/apps/overview/components/__tests__/Timeline-test.js
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/LeakPeriodLegend-test.js.snap
server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltips-test.js
server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js
server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.js
server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityDateInput.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphHistory-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsHistory-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltips-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAnalysesList-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityDateInput-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityPageHeader-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityAnalysesList-test.js.snap
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityDateInput-test.js.snap
server/sonar-web/src/main/js/apps/projectActivity/utils.js
server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.js
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.js
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.js.snap
server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.tsx
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.tsx
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.tsx
server/sonar-web/src/main/js/apps/quality-profiles/utils.ts
server/sonar-web/src/main/js/components/intl/DateFormatter.tsx
server/sonar-web/src/main/js/components/intl/DateFromNow.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/intl/DateTimeFormatter.tsx
server/sonar-web/src/main/js/components/intl/DateTooltipFormatter.tsx
server/sonar-web/src/main/js/components/intl/TimeFormatter.tsx
server/sonar-web/src/main/js/components/intl/TimeTooltipFormatter.tsx
server/sonar-web/src/main/js/components/issue/components/IssueChangelog.js
server/sonar-web/src/main/js/components/issue/components/IssueCommentLine.js
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueChangelog-test.js.snap
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueCommentLine-test.js.snap
server/sonar-web/src/main/js/components/widgets/barchart.js
server/sonar-web/src/main/js/helpers/__tests__/dates-test.ts
server/sonar-web/src/main/js/helpers/__tests__/query-test.js
server/sonar-web/src/main/js/helpers/dates.ts
server/sonar-web/src/main/js/helpers/handlebars/d.js
server/sonar-web/src/main/js/helpers/handlebars/dt.js
server/sonar-web/src/main/js/helpers/handlebars/fromNow.js
server/sonar-web/src/main/js/helpers/l10n.ts
server/sonar-web/src/main/js/helpers/periods.js
server/sonar-web/src/main/js/helpers/query.js
server/sonar-web/src/main/js/helpers/testUtils.ts
server/sonar-web/yarn.lock

index d95aba437bc7d1b97fde392709cc595396f40479..679b0c8538aea721e96f5684190006ad7da2d6c2 100644 (file)
@@ -17,6 +17,7 @@
     "d3-scale": "1.0.5",
     "d3-selection": "1.0.5",
     "d3-shape": "1.0.6",
+    "date-fns": "1.28.5",
     "escape-html": "1.0.3",
     "handlebars": "2.0.0",
     "history": "3.3.0",
@@ -44,6 +45,7 @@
   },
   "devDependencies": {
     "@types/classnames": "2.2.0",
+    "@types/date-fns": "2.6.0",
     "@types/enzyme": "2.8.6",
     "@types/escape-html": "0.0.19",
     "@types/jest": "20.0.7",
   ],
   "jest": {
     "coverageDirectory": "<rootDir>/target/coverage",
-    "coveragePathIgnorePatterns": [
-      "<rootDir>/node_modules",
-      "<rootDir>/tests"
-    ],
-    "moduleFileExtensions": [
-      "ts",
-      "tsx",
-      "js",
-      "json"
-    ],
+    "coveragePathIgnorePatterns": ["<rootDir>/node_modules", "<rootDir>/tests"],
+    "moduleFileExtensions": ["ts", "tsx", "js", "json"],
     "moduleNameMapper": {
       "^.+\\.(hbs|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/config/jest/FileStub.js",
       "^.+\\.css$": "<rootDir>/config/jest/CSSStub.js"
       "<rootDir>/config/polyfills.js",
       "<rootDir>/config/jest/SetupTestEnvironment.js"
     ],
-    "snapshotSerializers": [
-      "enzyme-to-json/serializer"
-    ],
+    "snapshotSerializers": ["enzyme-to-json/serializer"],
     "testPathIgnorePatterns": [
       "<rootDir>/node_modules",
       "<rootDir>/src/main/webapp",
index 89e68eee61103eaa286eb90071f70366d80b7c88..4391b8d8379e35bf4e9d8e50d61cb2c4402a6d01 100644 (file)
@@ -46,12 +46,14 @@ export default class LocalizationContainer extends React.PureComponent<Props, St
   }
 
   bundleLoaded = (lang: string) => {
-    import('react-intl/locale-data/' + (lang || DEFAULT_LANGUAGE)).then(
-      i => this.updateLang(lang, i),
-      () => {
-        import('react-intl/locale-data/en').then(i => this.updateLang(lang, i));
-      }
-    );
+    const langToLoad = lang || DEFAULT_LANGUAGE;
+
+    // No need to load english bundle, it's coming wiht react-intl
+    if (langToLoad !== 'en') {
+      import('react-intl/locale-data/' + langToLoad).then(i => this.updateLang(langToLoad, i));
+    } else {
+      this.setState({ loading: false, lang: langToLoad });
+    }
   };
 
   updateLang = (lang: string, intlBundle: Locale[]) => {
index cd5f6d39feab22eef71b85d97a646cdbbb7a16e4..5ce7e174de7ae800d5dd520939796a3b062a0159 100644 (file)
@@ -19,9 +19,9 @@
  */
 import * as React from 'react';
 import { sortBy } from 'lodash';
-import { FormattedRelative } from 'react-intl';
 import { Link } from 'react-router';
 import { Project } from './types';
+import DateFromNow from '../../../components/intl/DateFromNow';
 import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
 import Level from '../../../components/ui/Level';
 import Tooltip from '../../../components/controls/Tooltip';
@@ -43,12 +43,12 @@ export default function ProjectCard({ project }: Props) {
               overlay={<DateTimeFormatter date={project.lastAnalysisDate} />}
               placement="right">
               <div className="account-project-analysis">
-                <FormattedRelative value={project.lastAnalysisDate}>
-                  {(relativeDate: string) =>
+                <DateFromNow date={project.lastAnalysisDate}>
+                  {(fromNow: string) =>
                     <span>
-                      {translateWithParameters('my_account.projects.analyzed_x', relativeDate)}
+                      {translateWithParameters('my_account.projects.analyzed_x', fromNow)}
                     </span>}
-                </FormattedRelative>
+                </DateFromNow>
               </div>
             </Tooltip>
           : <div className="account-project-analysis">
index aa597b1ce8c536c4f9b31662374cc0016e63c273..d7a80d12599bf1d47c1f27870087ee4ee5c117b0 100644 (file)
@@ -49,7 +49,7 @@ it('should not render optional fields', () => {
 it('should render analysis date', () => {
   const project = { ...BASE, lastAnalysisDate: '2016-05-17' };
   const output = shallow(<ProjectCard project={project} />);
-  expect(output.find('.account-project-analysis FormattedRelative')).toHaveLength(1);
+  expect(output.find('.account-project-analysis DateFromNow')).toHaveLength(1);
 });
 
 it('should not render analysis date', () => {
index 8fe7a69b02820afadba40690f9a3315c347a8be5..de5c77abebd50e0da4e9b19389dc1c3effd510ba 100644 (file)
@@ -19,7 +19,7 @@
  */
 import $ from 'jquery';
 import React, { Component } from 'react';
-import { toShortNotSoISOString, isValidDate } from '../../../helpers/dates';
+import { isValidDate, parseDate, toShortNotSoISOString } from '../../../helpers/dates';
 
 export default class DateFilter extends Component {
   componentDidMount() {
@@ -45,8 +45,8 @@ export default class DateFilter extends Component {
 
   handleChange() {
     const date = {};
-    const minDate = new Date(this.refs.minDate.value);
-    const maxDate = new Date(this.refs.maxDate.value);
+    const minDate = parseDate(this.refs.minDate.value);
+    const maxDate = parseDate(this.refs.maxDate.value);
 
     if (isValidDate(minDate)) {
       date.minSubmittedAt = toShortNotSoISOString(minDate);
index d03a3e6228997d0b163d44ccb31802a2ea2ae97d..525d8bbc4204ea529b610a95c7d814bc23b09419 100644 (file)
@@ -19,7 +19,7 @@
  */
 import * as React from 'react';
 import TimeFormatter from '../../../components/intl/TimeFormatter';
-import { differenceInDays, isValidDate } from '../../../helpers/dates';
+import { differenceInDays, isValidDate, parseDate } from '../../../helpers/dates';
 
 interface Props {
   date: string;
@@ -27,8 +27,8 @@ interface Props {
 }
 
 export default function TaskDate({ date, baseDate }: Props) {
-  const parsedDate = new Date(date);
-  const parsedBaseDate = new Date(baseDate);
+  const parsedDate = parseDate(date);
+  const parsedBaseDate = parseDate(baseDate);
   const diff =
     date && baseDate && isValidDate(parsedDate) && isValidDate(parsedBaseDate)
       ? differenceInDays(parsedDate, parsedBaseDate)
index ed6a79cef9c62ea572fabb1dba13657b1cec312a..c2d8911358b59af98d39a520f8f127eb266f3a23 100644 (file)
@@ -19,7 +19,7 @@
  */
 import * as React from 'react';
 import DateFormatter from '../../../components/intl/DateFormatter';
-import { isSameDay } from '../../../helpers/dates';
+import { isSameDay, parseDate } from '../../../helpers/dates';
 import { ITask } from '../types';
 
 interface Props {
@@ -29,7 +29,7 @@ interface Props {
 
 export default function TaskDay({ task, prevTask }: Props) {
   const shouldDisplay =
-    !prevTask || !isSameDay(new Date(task.submittedAt), new Date(prevTask.submittedAt));
+    !prevTask || !isSameDay(parseDate(task.submittedAt), parseDate(prevTask.submittedAt));
 
   return (
     <td className="thin nowrap text-right">
index e8b41e80e364aefcdf63024500d9bdce0446f7ac..8ca27ea3eb9aacb4688c3c0a7fadb6ff1df76614 100644 (file)
@@ -20,7 +20,7 @@
 // @flow
 import React from 'react';
 import classNames from 'classnames';
-import { FormattedRelative } from 'react-intl';
+import DateFromNow from '../../../components/intl/DateFromNow';
 import DateFormatter from '../../../components/intl/DateFormatter';
 import Tooltip from '../../../components/controls/Tooltip';
 import { getPeriodLabel, getPeriodDate } from '../../../helpers/periods';
@@ -56,7 +56,7 @@ export default function LeakPeriodLegend({ className, component, period } /*: Pr
   const date = getPeriodDate(period);
   const tooltip = (
     <div>
-      <FormattedRelative value={date} />
+      <DateFromNow date={date} />
       {', '}
       <DateFormatter date={date} long={true} />
     </div>
index cf03a171d55fc1ed7c5078848d3ee0fe9bf52a0f..c5dedfe59b67cec2343c17e61292b845e305808b 100644 (file)
@@ -34,6 +34,7 @@ import { complementary } from '../config/complementary';
 import { enhanceComponent, isFileType, isViewType } from '../utils';
 import { getProjectUrl } from '../../../helpers/urls';
 import { isDiffMetric } from '../../../helpers/measures';
+import { parseDate } from '../../../helpers/dates';
 /*:: import type { Component, ComponentEnhanced, Paging, Period } from '../types'; */
 /*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */
 /*:: import type { Metric } from '../../../store/metrics/actions'; */
@@ -216,13 +217,13 @@ export default class MeasureContent extends React.PureComponent {
   renderCode() {
     const { component, leakPeriod } = this.props;
     const leakPeriodDate =
-      isDiffMetric(this.props.metric.key) && leakPeriod != null ? new Date(leakPeriod.date) : null;
+      isDiffMetric(this.props.metric.key) && leakPeriod != null ? parseDate(leakPeriod.date) : null;
 
     let filterLine;
     if (leakPeriodDate != null) {
       filterLine = line => {
         if (line.scmDate) {
-          const scmDate = new Date(line.scmDate);
+          const scmDate = parseDate(line.scmDate);
           return scmDate >= leakPeriodDate;
         } else {
           return false;
index b82411aaf929556c7b9dd0d7d6ca2f719c3d5fed..6040a382d6c2d8fe513c5d0e6f2810cef5e91c82 100644 (file)
@@ -4,9 +4,8 @@ exports[`should render correctly 1`] = `
 <Tooltip
   overlay={
     <div>
-      <FormattedRelative
-        updateInterval={10000}
-        value={2017-05-16T11:50:02.000Z}
+      <DateFromNow
+        date={2017-05-16T11:50:02.000Z}
       />
       , 
       <DateFormatter
index 8a409a34ac8fa31b5084f15cb7dc27a11567820b..6170138afa2688fca51edc2bb389835ff20a7eea 100644 (file)
 // @flow
 import React from 'react';
 import { max } from 'lodash';
-import { FormattedRelative, intlShape } from 'react-intl';
-import { formatterOption, longFormatterOption } from '../../../components/intl/DateFormatter';
+import { intlShape } from 'react-intl';
+import DateFromNow from '../../../components/intl/DateFromNow';
+import { longFormatterOption } from '../../../components/intl/DateFormatter';
 import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
 import FacetBox from '../../../components/facet/FacetBox';
 import FacetHeader from '../../../components/facet/FacetHeader';
 import FacetItem from '../../../components/facet/FacetItem';
 import { BarChart } from '../../../components/charts/bar-chart';
 import DateInput from '../../../components/controls/DateInput';
-import { isSameDay, toShortNotSoISOString } from '../../../helpers/dates';
+import { isSameDay, parseDate, toShortNotSoISOString } from '../../../helpers/dates';
 import { translate } from '../../../helpers/l10n';
 import { formatMeasure } from '../../../helpers/measures';
 /*:: import type { Component } from '../utils'; */
@@ -105,7 +106,7 @@ export default class CreationDateFacet extends React.PureComponent {
       createdAt: undefined,
       createdInLast: undefined,
       sinceLeakPeriod: undefined,
-      [property]: toShortNotSoISOString(new Date(value))
+      [property]: toShortNotSoISOString(parseDate(value))
     });
   };
 
@@ -134,12 +135,12 @@ export default class CreationDateFacet extends React.PureComponent {
     const { formatDate } = this.context.intl;
     const beforeDate = createdBefore ? createdBefore : undefined;
     const data = periods.map((start, index) => {
-      const startDate = new Date(start);
+      const startDate = parseDate(start);
       let nextStartDate = index < periods.length - 1 ? periods[index + 1] : beforeDate;
       let endDate;
       if (nextStartDate) {
-        nextStartDate = new Date(nextStartDate);
-        endDate = new Date(nextStartDate);
+        nextStartDate = parseDate(nextStartDate);
+        endDate = parseDate(nextStartDate);
         endDate.setDate(endDate.getDate() - 1);
       }
 
@@ -186,7 +187,7 @@ export default class CreationDateFacet extends React.PureComponent {
         <DateTimeFormatter date={this.props.createdAt} />
         <br />
         <span className="note">
-          <FormattedRelative value={this.props.createdAt} />
+          <DateFromNow date={this.props.createdAt} />
         </span>
       </div>
     );
@@ -194,20 +195,19 @@ export default class CreationDateFacet extends React.PureComponent {
 
   renderPeriodSelectors() {
     const { createdAfter, createdBefore } = this.props;
-    const { formatDate } = this.context.intl;
     return (
       <div className="search-navigator-date-facet-selection">
         <DateInput
           className="search-navigator-date-facet-selection-dropdown-left"
           onChange={this.handlePeriodChangeAfter}
           placeholder={translate('from')}
-          value={createdAfter ? formatDate(createdAfter, formatterOption) : undefined}
+          value={createdAfter ? toShortNotSoISOString(createdAfter) : undefined}
         />
         <DateInput
           className="search-navigator-date-facet-selection-dropdown-right"
           onChange={this.handlePeriodChangeBefore}
           placeholder={translate('to')}
-          value={createdBefore ? formatDate(createdBefore, formatterOption) : undefined}
+          value={createdBefore ? toShortNotSoISOString(createdBefore) : undefined}
         />
       </div>
     );
index f932aeb8c8e6e1346f4b3dccd92014589668d051..89181eecc938e156afd350978b445039b68aee6f 100644 (file)
@@ -19,7 +19,7 @@
  */
 // @flow
 import React from 'react';
-import { FormattedRelative } from 'react-intl';
+import DateFromNow from '../../../components/intl/DateFromNow';
 import DateFormatter from '../../../components/intl/DateFormatter';
 import Tooltip from '../../../components/controls/Tooltip';
 import { getPeriodDate, getPeriodLabel } from '../../../helpers/periods';
@@ -101,7 +101,7 @@ export default function LeakPeriodLegend({ period } /*: { period: Period } */) {
       <div className="overview-legend">
         {translateWithParameters('overview.leak_period_x', leakPeriodLabel)}
         <br />
-        <FormattedRelative value={leakPeriodDate}>
+        <DateFromNow date={leakPeriodDate}>
           {fromNow =>
             <span className="note">
               {translateWithParameters(
@@ -109,7 +109,7 @@ export default function LeakPeriodLegend({ period } /*: { period: Period } */) {
                 fromNow
               )}
             </span>}
-        </FormattedRelative>
+        </DateFromNow>
       </div>
     </Tooltip>
   );
index 800a62d4ccd0a4a122dcbcd494e4ec6ee30799b7..ac67fbae1940bd069338f24709be9123ac1d5d16 100644 (file)
@@ -30,6 +30,7 @@ import Meta from '../meta/Meta';
 import throwGlobalError from '../../../app/utils/throwGlobalError';
 import { getMeasuresAndMeta } from '../../../api/measures';
 import { getAllTimeMachineData } from '../../../api/time-machine';
+import { parseDate } from '../../../helpers/dates';
 import { enhanceMeasuresWithMetrics } from '../../../helpers/measures';
 import { getLeakPeriod } from '../../../helpers/periods';
 import { getCustomGraph, getGraph } from '../../../helpers/storage';
@@ -123,7 +124,7 @@ export default class OverviewApp extends React.PureComponent {
         const history /*: History */ = {};
         r.measures.forEach(measure => {
           const measureHistory = measure.history.map(analysis => ({
-            date: new Date(analysis.date),
+            date: parseDate(analysis.date),
             value: analysis.value
           }));
           history[measure.metric] = measureHistory;
index b0179494ae03e41b34790a88f1f246e69b0245a9..39a24fcadb102963f2e6ddeeee632ea267e03363 100644 (file)
@@ -37,9 +37,7 @@ describe('check note', () => {
       mode: 'date',
       parameter: '2013-01-01'
     };
-    expect(
-      shallow(<LeakPeriodLegend period={period} />).find('FormattedRelative')
-    ).toMatchSnapshot();
+    expect(shallow(<LeakPeriodLegend period={period} />).find('DateFromNow')).toMatchSnapshot();
   });
 
   it('version', () => {
@@ -48,9 +46,7 @@ describe('check note', () => {
       mode: 'version',
       parameter: '0.1'
     };
-    expect(
-      shallow(<LeakPeriodLegend period={period} />).find('FormattedRelative')
-    ).toMatchSnapshot();
+    expect(shallow(<LeakPeriodLegend period={period} />).find('DateFromNow')).toMatchSnapshot();
   });
 
   it('previous_version', () => {
@@ -58,7 +54,7 @@ describe('check note', () => {
       date: '2013-09-22T00:00:00+0200',
       mode: 'previous_version'
     };
-    expect(shallow(<LeakPeriodLegend period={period} />).find('FormattedRelative')).toHaveLength(1);
+    expect(shallow(<LeakPeriodLegend period={period} />).find('DateFromNow')).toHaveLength(1);
   });
 
   it('previous_analysis', () => {
@@ -66,6 +62,6 @@ describe('check note', () => {
       date: '2013-09-22T00:00:00+0200',
       mode: 'previous_analysis'
     };
-    expect(shallow(<LeakPeriodLegend period={period} />).find('FormattedRelative')).toHaveLength(1);
+    expect(shallow(<LeakPeriodLegend period={period} />).find('DateFromNow')).toHaveLength(1);
   });
 });
index 186f707fc3477712140758a9e78b06a91bfb6e2d..c07d56a869e50604ef748d58d8cdfde7e107da15 100644 (file)
 import React from 'react';
 import { shallow } from 'enzyme';
 import Timeline from '../Timeline';
+import { parseDate } from '../../../../helpers/dates';
 
-const range = new Date('2017-05-01T00:00:00.000Z');
+const range = parseDate('2017-05-01T00:00:00.000Z');
 const history = [
-  { date: new Date('2017-04-08T00:00:00.000Z'), value: '29.6' },
-  { date: new Date('2017-04-09T00:00:00.000Z'), value: '170.8' },
-  { date: new Date('2017-05-08T00:00:00.000Z'), value: '360' },
-  { date: new Date('2017-05-09T00:00:00.000Z'), value: '39' }
+  { date: parseDate('2017-04-08T00:00:00.000Z'), value: '29.6' },
+  { date: parseDate('2017-04-09T00:00:00.000Z'), value: '170.8' },
+  { date: parseDate('2017-05-08T00:00:00.000Z'), value: '360' },
+  { date: parseDate('2017-05-09T00:00:00.000Z'), value: '39' }
 ];
 
 it('should render correctly with an "after" range', () => {
@@ -38,7 +39,7 @@ it('should render correctly with a "before" range', () => {
 });
 
 it('should have a correct domain with strings or numbers', () => {
-  const date = new Date('2017-05-08T00:00:00.000Z');
+  const date = parseDate('2017-05-08T00:00:00.000Z');
   const wrapper = shallow(<Timeline after={range} history={history} />);
   expect(wrapper.find('LineChart').props().domain).toEqual([0, 360]);
 
index 44caddb1324595c5ccda0bde76cff11da09b58a5..b38a2d79967f87a122d64ae6219ee10e5e1bb716 100644 (file)
@@ -9,15 +9,13 @@ exports[`check note 10 days 1`] = `
 `;
 
 exports[`check note date 1`] = `
-<FormattedRelative
-  updateInterval={10000}
-  value={2013-09-21T22:00:00.000Z}
+<DateFromNow
+  date={2013-09-21T22:00:00.000Z}
 />
 `;
 
 exports[`check note version 1`] = `
-<FormattedRelative
-  updateInterval={10000}
-  value={2013-09-21T22:00:00.000Z}
+<DateFromNow
+  date={2013-09-21T22:00:00.000Z}
 />
 `;
index 2953cb69037a378f0495a44a1069c91c83349d33..1b8d08d2ef166fdfbf31f2ff3794114637427c0b 100644 (file)
@@ -21,6 +21,7 @@ import React from 'react';
 import { shallow } from 'enzyme';
 import PreviewGraphTooltips from '../PreviewGraphTooltips';
 import { DEFAULT_GRAPH } from '../../../projectActivity/utils';
+import { parseDate } from '../../../../helpers/dates';
 
 const SERIES_ISSUES = [
   {
@@ -78,7 +79,7 @@ const DEFAULT_PROPS = {
   graph: DEFAULT_GRAPH,
   graphWidth: 150,
   metrics: METRICS,
-  selectedDate: new Date('2011-10-01T22:01:00.000Z'),
+  selectedDate: parseDate('2011-10-01T22:01:00.000Z'),
   series: SERIES_ISSUES,
   tooltipIdx: 0,
   tooltipPos: 25
@@ -90,7 +91,7 @@ it('should render correctly', () => {
       <PreviewGraphTooltips
         {...DEFAULT_PROPS}
         graph="random"
-        selectedDate={new Date('2011-10-25T10:27:41.000Z')}
+        selectedDate={parseDate('2011-10-25T10:27:41.000Z')}
         tooltipIdx={1}
       />
     )
index 2eab616e1957113316a7d3eb17fb4a888fbc7c4f..26a751d62190279067d5c488bc94ec29ba2964ad 100644 (file)
@@ -19,8 +19,8 @@
  */
 import React from 'react';
 import { Link } from 'react-router';
-import { FormattedRelative } from 'react-intl';
 import Tooltip from '../../../components/controls/Tooltip';
+import DateFromNow from '../../../components/intl/DateFromNow';
 import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
 import enhance from './enhance';
 import { getMetricName } from '../helpers/metrics';
@@ -66,12 +66,12 @@ class CodeSmells extends React.PureComponent {
       return null;
     }
     return (
-      <FormattedRelative value={this.props.historyStartDate}>
+      <DateFromNow date={this.props.historyStartDate}>
         {fromNow =>
           <span className="overview-domain-timeline-date">
             {translateWithParameters('overview.started_x', fromNow)}
           </span>}
-      </FormattedRelative>
+      </DateFromNow>
     );
   }
 
index 6914d110f1e92aecfed0c4b4d599c51780409209..4e8432ce9d9343e61fd334123354e42d2d6e4aec 100644 (file)
  */
 // @flow
 import * as actions from '../actions';
+import { parseDate } from '../../../helpers/dates';
 
 const ANALYSES = [
   {
     key: 'A1',
-    date: new Date('2016-10-27T16:33:50+0200'),
+    date: parseDate('2016-10-27T16:33:50+0200'),
     events: [
       {
         key: 'E1',
@@ -34,12 +35,12 @@ const ANALYSES = [
   },
   {
     key: 'A2',
-    date: new Date('2016-10-27T12:21:15+0200'),
+    date: parseDate('2016-10-27T12:21:15+0200'),
     events: []
   },
   {
     key: 'A3',
-    date: new Date('2016-10-26T12:17:29+0200'),
+    date: parseDate('2016-10-26T12:17:29+0200'),
     events: [
       {
         key: 'E2',
index 455c679d2d7c404969e87551b45d076048c80602..d60213ed43e4e0d1c102a1469fce3aca858a7802 100644 (file)
@@ -24,18 +24,18 @@ import * as dates from '../../../helpers/dates';
 const ANALYSES = [
   {
     key: 'AVyMjlK1HjR_PLDzRbB9',
-    date: new Date('2017-06-09T13:06:10.000Z'),
+    date: dates.parseDate('2017-06-09T13:06:10.000Z'),
     events: [{ key: 'AVyM9oI1HjR_PLDzRciU', category: 'VERSION', name: '1.1-SNAPSHOT' }]
   },
-  { key: 'AVyM9n3cHjR_PLDzRciT', date: new Date('2017-06-09T11:12:27.000Z'), events: [] },
+  { key: 'AVyM9n3cHjR_PLDzRciT', date: dates.parseDate('2017-06-09T11:12:27.000Z'), events: [] },
   {
     key: 'AVyMjlK1HjR_PLDzRbB9',
-    date: new Date('2017-06-09T11:12:27.000Z'),
+    date: dates.parseDate('2017-06-09T11:12:27.000Z'),
     events: [{ key: 'AVyM9oI1HjR_PLDzRciU', category: 'VERSION', name: '1.1' }]
   },
   {
     key: 'AVxZtCpH7841nF4RNEMI',
-    date: new Date('2017-05-18T14:13:07.000Z'),
+    date: dates.parseDate('2017-05-18T14:13:07.000Z'),
     events: [
       {
         key: 'AVxZtC-N7841nF4RNEMJ',
@@ -44,10 +44,10 @@ const ANALYSES = [
       }
     ]
   },
-  { key: 'AVwaa1qkpbBde8B6UhYI', date: new Date('2017-05-18T07:17:32.000Z'), events: [] },
+  { key: 'AVwaa1qkpbBde8B6UhYI', date: dates.parseDate('2017-05-18T07:17:32.000Z'), events: [] },
   {
     key: 'AVwQF7kwl-nNFgFWOJ3V',
-    date: new Date('2017-05-16T07:09:59.000Z'),
+    date: dates.parseDate('2017-05-16T07:09:59.000Z'),
     events: [
       { key: 'AVyM9oI1HjR_PLDzRciU', category: 'VERSION', name: '1.0' },
       {
@@ -57,22 +57,22 @@ const ANALYSES = [
       }
     ]
   },
-  { key: 'AVvtGF3IY6vCuQNDdwxI', date: new Date('2017-05-09T12:03:59.000Z'), events: [] }
+  { key: 'AVvtGF3IY6vCuQNDdwxI', date: dates.parseDate('2017-05-09T12:03:59.000Z'), events: [] }
 ];
 
 const HISTORY = [
   {
     metric: 'lines_to_cover',
     history: [
-      { date: new Date('2017-04-27T08:21:32.000Z'), value: '100' },
-      { date: new Date('2017-04-30T23:06:24.000Z'), value: '100' }
+      { date: dates.parseDate('2017-04-27T08:21:32.000Z'), value: '100' },
+      { date: dates.parseDate('2017-04-30T23:06:24.000Z'), value: '100' }
     ]
   },
   {
     metric: 'uncovered_lines',
     history: [
-      { date: new Date('2017-04-27T08:21:32.000Z'), value: '12' },
-      { date: new Date('2017-04-30T23:06:24.000Z'), value: '50' }
+      { date: dates.parseDate('2017-04-27T08:21:32.000Z'), value: '12' },
+      { date: dates.parseDate('2017-04-30T23:06:24.000Z'), value: '50' }
     ]
   }
 ];
@@ -84,7 +84,7 @@ const METRICS = [
 
 const QUERY = {
   category: '',
-  from: new Date('2017-04-27T08:21:32.000Z'),
+  from: dates.parseDate('2017-04-27T08:21:32.000Z'),
   graph: utils.DEFAULT_GRAPH,
   project: 'foo',
   to: undefined,
@@ -112,7 +112,6 @@ describe('getAnalysesByVersionByDay', () => {
     startDay.setUTCHours(0, 0, 0, 0);
     return startDay;
   });
-
   it('should correctly map analysis by versions and by days', () => {
     expect(
       utils.getAnalysesByVersionByDay(ANALYSES, {
@@ -138,8 +137,8 @@ describe('getAnalysesByVersionByDay', () => {
         customMetrics: [],
         graph: utils.DEFAULT_GRAPH,
         project: 'foo',
-        to: new Date('2017-06-09T11:12:27.000Z'),
-        from: new Date('2017-05-18T14:13:07.000Z')
+        to: dates.parseDate('2017-06-09T11:12:27.000Z'),
+        from: dates.parseDate('2017-05-18T14:13:07.000Z')
       })
     ).toMatchSnapshot();
   });
@@ -147,10 +146,26 @@ describe('getAnalysesByVersionByDay', () => {
     expect(
       utils.getAnalysesByVersionByDay(
         [
-          { key: 'AVyMjlK1HjR_PLDzRbB9', date: new Date('2017-06-09T13:06:10.000Z'), events: [] },
-          { key: 'AVyM9n3cHjR_PLDzRciT', date: new Date('2017-06-09T11:12:27.000Z'), events: [] },
-          { key: 'AVyMjlK1HjR_PLDzRbB9', date: new Date('2017-06-09T11:12:27.000Z'), events: [] },
-          { key: 'AVxZtCpH7841nF4RNEMI', date: new Date('2017-05-18T14:13:07.000Z'), events: [] }
+          {
+            key: 'AVyMjlK1HjR_PLDzRbB9',
+            date: dates.parseDate('2017-06-09T13:06:10.000Z'),
+            events: []
+          },
+          {
+            key: 'AVyM9n3cHjR_PLDzRciT',
+            date: dates.parseDate('2017-06-09T11:12:27.000Z'),
+            events: []
+          },
+          {
+            key: 'AVyMjlK1HjR_PLDzRbB9',
+            date: dates.parseDate('2017-06-09T11:12:27.000Z'),
+            events: []
+          },
+          {
+            key: 'AVxZtCpH7841nF4RNEMI',
+            date: dates.parseDate('2017-05-18T14:13:07.000Z'),
+            events: []
+          }
         ],
         {
           category: '',
@@ -253,8 +268,8 @@ describe('hasHistoryData', () => {
           name: 'foo',
           type: 'INT',
           data: [
-            { x: new Date('2017-04-27T08:21:32.000Z'), y: 2 },
-            { x: new Date('2017-04-30T23:06:24.000Z'), y: 2 }
+            { x: dates.parseDate('2017-04-27T08:21:32.000Z'), y: 2 },
+            { x: dates.parseDate('2017-04-30T23:06:24.000Z'), y: 2 }
           ]
         }
       ])
@@ -270,8 +285,8 @@ describe('hasHistoryData', () => {
           name: 'bar',
           type: 'INT',
           data: [
-            { x: new Date('2017-04-27T08:21:32.000Z'), y: 2 },
-            { x: new Date('2017-04-30T23:06:24.000Z'), y: 2 }
+            { x: dates.parseDate('2017-04-27T08:21:32.000Z'), y: 2 },
+            { x: dates.parseDate('2017-04-30T23:06:24.000Z'), y: 2 }
           ]
         }
       ])
@@ -281,7 +296,7 @@ describe('hasHistoryData', () => {
         {
           name: 'bar',
           type: 'INT',
-          data: [{ x: new Date('2017-04-27T08:21:32.000Z'), y: 2 }]
+          data: [{ x: dates.parseDate('2017-04-27T08:21:32.000Z'), y: 2 }]
         }
       ])
     ).toBeFalsy();
index 044b1462323b1af002dd90cb76d7ded559e07d42..de15fb7c664e3ec15cf3579e5357e1922cdfed78 100644 (file)
@@ -23,6 +23,7 @@ import DeferredSpinner from '../../../components/common/DeferredSpinner';
 import GraphHistory from './GraphHistory';
 import { EVENT_TYPES, getSeriesMetricType, hasHistoryData, isCustomGraph } from '../utils';
 import { translate } from '../../../helpers/l10n';
+import { parseDate } from '../../../helpers/dates';
 /*:: import type { Analysis, MeasureHistory } from '../types'; */
 /*:: import type { Serie } from '../../../components/charts/AdvancedTimeline'; */
 
@@ -86,7 +87,7 @@ export default class GraphsHistory extends React.PureComponent {
       return acc.concat({
         className: event.category,
         name: event.name,
-        date: new Date(analysis.date)
+        date: parseDate(analysis.date)
       });
     }, []);
     return sortBy(filteredEvents, 'date');
index 7b94b01483c58cb21a903175857ea818a928c0cb..9afecfd778f24f228efb391c459fa72af7e1e8df 100644 (file)
@@ -24,6 +24,7 @@ import { throttle } from 'lodash';
 import ProjectActivityAnalysis from './ProjectActivityAnalysis';
 import DateFormatter from '../../../components/intl/DateFormatter';
 import { translate } from '../../../helpers/l10n';
+import { toShortNotSoISOString } from '../../../helpers/dates';
 import {
   activityQueryChanged,
   getAnalysesByVersionByDay,
@@ -190,7 +191,10 @@ export default class ProjectActivityAnalysesList extends React.PureComponent {
                 </div>}
               <ul className="project-activity-days-list">
                 {days.map(day =>
-                  <li key={day} className="project-activity-day">
+                  <li
+                    key={day}
+                    className="project-activity-day"
+                    data-day={toShortNotSoISOString(Number(day))}>
                     <div className="project-activity-date">
                       <DateFormatter date={Number(day)} long={true} />
                     </div>
index 2de698d4b5281c7c030f74103d41fd189c12f01b..e0b70d68e3852a8590471ea8292a429d27fc0194 100644 (file)
@@ -23,6 +23,7 @@ import Helmet from 'react-helmet';
 import ProjectActivityPageHeader from './ProjectActivityPageHeader';
 import ProjectActivityAnalysesList from './ProjectActivityAnalysesList';
 import ProjectActivityGraphs from './ProjectActivityGraphs';
+import { parseDate } from '../../../helpers/dates';
 import { translate } from '../../../helpers/l10n';
 import './projectActivity.css';
 /*:: import type { Analysis, MeasureHistory, Metric, Query } from '../types'; */
@@ -88,7 +89,7 @@ export default function ProjectActivityApp(props /*: Props */) {
         <div className="project-activity-layout-page-main">
           <ProjectActivityGraphs
             analyses={analyses}
-            leakPeriodDate={new Date(props.project.leakPeriodDate)}
+            leakPeriodDate={parseDate(props.project.leakPeriodDate)}
             loading={props.graphLoading}
             measuresHistory={measuresHistory}
             metrics={props.metrics}
index 172e0d35bcf75665b4522686a862359e25805d19..1d3c9845ecd2ba31018d6a97bc234b81bc592ce8 100644 (file)
@@ -28,6 +28,7 @@ import { getAllTimeMachineData } from '../../../api/time-machine';
 import { getMetrics } from '../../../api/metrics';
 import * as api from '../../../api/projectActivity';
 import * as actions from '../actions';
+import { parseDate } from '../../../helpers/dates';
 import { getCustomGraph, getGraph } from '../../../helpers/storage';
 import {
   customMetricsChanged,
@@ -172,7 +173,7 @@ class ProjectActivityAppContainer extends React.PureComponent {
     return api
       .getProjectActivity({ ...parameters, ...additional })
       .then(({ analyses, paging }) => ({
-        analyses: analyses.map(analysis => ({ ...analysis, date: new Date(analysis.date) })),
+        analyses: analyses.map(analysis => ({ ...analysis, date: parseDate(analysis.date) })),
         paging
       }));
   };
@@ -186,7 +187,7 @@ class ProjectActivityAppContainer extends React.PureComponent {
         measures.map(measure => ({
           metric: measure.metric,
           history: measure.history.map(analysis => ({
-            date: new Date(analysis.date),
+            date: parseDate(analysis.date),
             value: analysis.value
           }))
         })),
index f6f85c29c9b180a29bdbb15c9fd4e4d78daa989d..edd3ca20dc7bd2fb6d3ec7dbb3e5ec0cdcebf5d3 100644 (file)
  */
 // @flow
 import React from 'react';
-import { intlShape } from 'react-intl';
 import DateInput from '../../../components/controls/DateInput';
-import { formatterOption } from '../../../components/intl/DateFormatter';
 import { parseAsDate } from '../../../helpers/query';
 import { translate } from '../../../helpers/l10n';
+import { toShortNotSoISOString } from '../../../helpers/dates';
 /*:: import type { RawQuery } from '../../../helpers/query'; */
 
 /*::
@@ -37,18 +36,13 @@ type Props = {
 export default class ProjectActivityDateInput extends React.PureComponent {
   /*:: props: Props; */
 
-  static contextTypes = {
-    intl: intlShape
-  };
-
   handleFromDateChange = (from /*: string */) => this.props.onChange({ from: parseAsDate(from) });
 
   handleToDateChange = (to /*: string */) => this.props.onChange({ to: parseAsDate(to) });
 
   handleResetClick = () => this.props.onChange({ from: null, to: null });
 
-  formatDate = (date /*: ?Date */) =>
-    date ? this.context.intl.formatDate(date, formatterOption) : undefined;
+  formatDate = (date /*: ?Date */) => (date ? toShortNotSoISOString(date) : undefined);
 
   render() {
     return (
index 57cf8c7087e7b55059bb7d299044e943a0452c7d..466d141907d9246af7658582911d18c3761b3f65 100644 (file)
@@ -21,15 +21,16 @@ import React from 'react';
 import { shallow } from 'enzyme';
 import GraphHistory from '../GraphHistory';
 import { DEFAULT_GRAPH } from '../../utils';
+import { parseDate } from '../../../../helpers/dates';
 
 const SERIES = [
   {
     name: 'bugs',
     translatedName: 'metric.bugs.name',
     data: [
-      { x: new Date('2016-10-27T16:33:50+0200'), y: 5 },
-      { x: new Date('2016-10-27T12:21:15+0200'), y: 16 },
-      { x: new Date('2016-10-26T12:17:29+0200'), y: 12 }
+      { x: parseDate('2016-10-27T16:33:50+0200'), y: 5 },
+      { x: parseDate('2016-10-27T12:21:15+0200'), y: 16 },
+      { x: parseDate('2016-10-26T12:17:29+0200'), y: 12 }
     ]
   }
 ];
index 5a62d59c1fe993bbfbef2907b465ad333319f7e0..099d18fdfffb2bfc6731f4fbafdaa5410dac26d2 100644 (file)
@@ -21,11 +21,12 @@ import React from 'react';
 import { shallow } from 'enzyme';
 import GraphsHistory from '../GraphsHistory';
 import { DEFAULT_GRAPH } from '../../utils';
+import { parseDate } from '../../../../helpers/dates';
 
 const ANALYSES = [
   {
     key: 'A1',
-    date: new Date('2016-10-27T16:33:50+0200'),
+    date: parseDate('2016-10-27T16:33:50+0200'),
     events: [
       {
         key: 'E1',
@@ -36,12 +37,12 @@ const ANALYSES = [
   },
   {
     key: 'A2',
-    date: new Date('2016-10-27T12:21:15+0200'),
+    date: parseDate('2016-10-27T12:21:15+0200'),
     events: []
   },
   {
     key: 'A3',
-    date: new Date('2016-10-26T12:17:29+0200'),
+    date: parseDate('2016-10-26T12:17:29+0200'),
     events: [
       {
         key: 'E2',
@@ -62,9 +63,9 @@ const SERIES = [
     name: 'bugs',
     translatedName: 'metric.bugs.name',
     data: [
-      { x: new Date('2016-10-27T16:33:50+0200'), y: 5 },
-      { x: new Date('2016-10-27T12:21:15+0200'), y: 16 },
-      { x: new Date('2016-10-26T12:17:29+0200'), y: 12 }
+      { x: parseDate('2016-10-27T16:33:50+0200'), y: 5 },
+      { x: parseDate('2016-10-27T12:21:15+0200'), y: 16 },
+      { x: parseDate('2016-10-26T12:17:29+0200'), y: 12 }
     ]
   }
 ];
@@ -117,7 +118,7 @@ it('should show that there is no history data', () => {
           {
             name: 'bugs',
             translatedName: 'metric.bugs.name',
-            data: [{ x: new Date('2016-10-27T16:33:50+0200'), y: undefined }]
+            data: [{ x: parseDate('2016-10-27T16:33:50+0200'), y: undefined }]
           }
         ]}
       />
index 34f50c3911aeb4a958490f3b7e5d6f3e61a9e31e..d67c36db1b1c8f779b4add68d45ff71d3c736c1d 100644 (file)
@@ -21,6 +21,7 @@ import React from 'react';
 import { shallow } from 'enzyme';
 import GraphsTooltips from '../GraphsTooltips';
 import { DEFAULT_GRAPH } from '../../utils';
+import { parseDate } from '../../../../helpers/dates';
 
 const SERIES_ISSUES = [
   {
@@ -72,7 +73,7 @@ const DEFAULT_PROPS = {
   graph: DEFAULT_GRAPH,
   graphWidth: 500,
   measuresHistory: [],
-  selectedDate: new Date('2011-10-01T22:01:00.000Z'),
+  selectedDate: parseDate('2011-10-01T22:01:00.000Z'),
   series: SERIES_ISSUES,
   tooltipIdx: 0,
   tooltipPos: 666
@@ -88,7 +89,7 @@ it('should render correctly for random graphs', () => {
       <GraphsTooltips
         {...DEFAULT_PROPS}
         graph="random"
-        selectedDate={new Date('2011-10-25T10:27:41.000Z')}
+        selectedDate={parseDate('2011-10-25T10:27:41.000Z')}
         tooltipIdx={1}
       />
     )
index 6a0c61741f2cd7949b501753b10871f912a2f9f5..216d9d7e727e02de83eabe1e394c4253045c3611 100644 (file)
 import React from 'react';
 import { shallow } from 'enzyme';
 import ProjectActivityAnalysesList from '../ProjectActivityAnalysesList';
-import * as dates from '../../../../helpers/dates';
 import { DEFAULT_GRAPH } from '../../utils';
+import * as dates from '../../../../helpers/dates';
 
 const ANALYSES = [
   {
     key: 'A1',
-    date: new Date('2016-10-27T16:33:50+0000'),
+    date: dates.parseDate('2016-10-27T16:33:50+0000'),
     events: [
       {
         key: 'E1',
@@ -37,12 +37,12 @@ const ANALYSES = [
   },
   {
     key: 'A2',
-    date: new Date('2016-10-27T12:21:15+0000'),
+    date: dates.parseDate('2016-10-27T12:21:15+0000'),
     events: []
   },
   {
     key: 'A3',
-    date: new Date('2016-10-26T12:17:29+0000'),
+    date: dates.parseDate('2016-10-26T12:17:29+0000'),
     events: [
       {
         key: 'E2',
@@ -58,7 +58,7 @@ const ANALYSES = [
   },
   {
     key: 'A4',
-    date: new Date('2016-10-24T16:33:50+0000'),
+    date: dates.parseDate('2016-10-24T16:33:50+0000'),
     events: [
       {
         key: 'E1',
@@ -92,6 +92,8 @@ dates.startOfDay = jest.fn(date => {
   return startDay;
 });
 
+dates.toShortNotSoISOString = date => 'ISO.' + date;
+
 it('should render correctly', () => {
   expect(shallow(<ProjectActivityAnalysesList {...DEFAULT_PROPS} />)).toMatchSnapshot();
 });
@@ -107,8 +109,8 @@ it('should correctly filter analyses by date range', () => {
   wrapper.setProps({
     query: {
       ...DEFAULT_PROPS.query,
-      from: new Date('2016-10-27T16:33:50+0000'),
-      to: new Date('2016-10-27T16:33:50+0000')
+      from: dates.parseDate('2016-10-27T16:33:50+0000'),
+      to: dates.parseDate('2016-10-27T16:33:50+0000')
     }
   });
   expect(wrapper).toMatchSnapshot();
index 1d3d5cec2901a4a76946f9174292b22c82af3d5b..2dcf1dd4d613c1bfbbabd00b6490553f8c27e7fe 100644 (file)
@@ -21,11 +21,12 @@ import React from 'react';
 import { shallow } from 'enzyme';
 import ProjectActivityApp from '../ProjectActivityApp';
 import { DEFAULT_GRAPH } from '../../utils';
+import { parseDate } from '../../../../helpers/dates';
 
 const ANALYSES = [
   {
     key: 'A1',
-    date: new Date('2016-10-27T16:33:50+0200'),
+    date: parseDate('2016-10-27T16:33:50+0200'),
     events: [
       {
         key: 'E1',
@@ -36,12 +37,12 @@ const ANALYSES = [
   },
   {
     key: 'A2',
-    date: new Date('2016-10-27T12:21:15+0200'),
+    date: parseDate('2016-10-27T12:21:15+0200'),
     events: []
   },
   {
     key: 'A3',
-    date: new Date('2016-10-26T12:17:29+0200'),
+    date: parseDate('2016-10-26T12:17:29+0200'),
     events: [
       {
         key: 'E2',
@@ -76,8 +77,8 @@ const DEFAULT_PROPS = {
     {
       metric: 'code_smells',
       history: [
-        { date: new Date('Fri Mar 04 2016 10:40:12 GMT+0100 (CET)'), value: '1749' },
-        { date: new Date('Fri Mar 04 2016 18:40:16 GMT+0100 (CET)'), value: '2286' }
+        { date: parseDate('Fri Mar 04 2016 10:40:12 GMT+0100 (CET)'), value: '1749' },
+        { date: parseDate('Fri Mar 04 2016 18:40:16 GMT+0100 (CET)'), value: '2286' }
       ]
     }
   ],
index 6b93eaae2966e9331a2a14a5e295e809d097954d..a0930873a3063b6076eab4b233d2db45b5f6daa4 100644 (file)
 import React from 'react';
 import { shallowWithIntl } from '../../../../helpers/testUtils';
 import ProjectActivityDateInput from '../ProjectActivityDateInput';
+import { parseDate } from '../../../../helpers/dates';
 
 it('should render correctly the date inputs', () => {
   expect(
     shallowWithIntl(
       <ProjectActivityDateInput
-        from={new Date('2016-10-27T12:21:15+0000')}
-        to={new Date('2016-12-27T12:21:15+0000')}
+        from={parseDate('2016-10-27T12:21:15+0000')}
+        to={parseDate('2016-12-27T12:21:15+0000')}
         onChange={() => {}}
       />
     )
index 64c9f8b420b7bfffff5285d1b0582ef73f3c8dc3..ffc96031708adc2a47fd34058aff87f63b218ee8 100644 (file)
@@ -21,6 +21,7 @@ import React from 'react';
 import { shallow } from 'enzyme';
 import ProjectActivityGraphs from '../ProjectActivityGraphs';
 import { DEFAULT_GRAPH } from '../../utils';
+import { parseDate } from '../../../../helpers/dates';
 
 const ANALYSES = [
   {
@@ -67,9 +68,9 @@ const DEFAULT_PROPS = {
     {
       metric: 'code_smells',
       history: [
-        { date: new Date('2016-10-26T12:17:29+0200'), value: '2286' },
-        { date: new Date('2016-10-27T12:21:15+0200'), value: '1749' },
-        { date: new Date('2016-10-27T16:33:50+0200'), value: '500' }
+        { date: parseDate('2016-10-26T12:17:29+0200'), value: '2286' },
+        { date: parseDate('2016-10-27T12:21:15+0200'), value: '1749' },
+        { date: parseDate('2016-10-27T16:33:50+0200'), value: '500' }
       ]
     }
   ],
index f6faa2a1efabca7fcfcf8b03bbfec8c0fa9823bc..4c02f5d15e6a9c00d5b820a1a85170aa21a94cca 100644 (file)
 import React from 'react';
 import { shallow } from 'enzyme';
 import ProjectActivityPageHeader from '../ProjectActivityPageHeader';
+import { parseDate } from '../../../../helpers/dates';
 
 it('should render correctly the list of series', () => {
   expect(
     shallow(
       <ProjectActivityPageHeader
         category=""
-        from={new Date('2016-10-27T12:21:15+0200')}
+        from={parseDate('2016-10-27T12:21:15+0200')}
         project={{}}
         updateQuery={() => {}}
       />
index 00938d611a838d1b4acef20a69db9d5e0b6b5138..904f0c3a49d48241063c079f6a73c30328099a38 100644 (file)
@@ -25,6 +25,7 @@ exports[`should correctly filter analyses by category 1`] = `
     >
       <li
         className="project-activity-day"
+        data-day="ISO.1477267200000"
       >
         <div
           className="project-activity-date"
@@ -94,6 +95,7 @@ exports[`should correctly filter analyses by date range 1`] = `
     >
       <li
         className="project-activity-day"
+        data-day="ISO.1477526400000"
       >
         <div
           className="project-activity-date"
@@ -163,6 +165,7 @@ exports[`should render correctly 1`] = `
     >
       <li
         className="project-activity-day"
+        data-day="ISO.1477526400000"
       >
         <div
           className="project-activity-date"
@@ -238,6 +241,7 @@ exports[`should render correctly 1`] = `
     >
       <li
         className="project-activity-day"
+        data-day="ISO.1477440000000"
       >
         <div
           className="project-activity-date"
@@ -284,6 +288,7 @@ exports[`should render correctly 1`] = `
       </li>
       <li
         className="project-activity-day"
+        data-day="ISO.1477267200000"
       >
         <div
           className="project-activity-date"
index 95d05668feff5f8564be36bb7c9c3afa9c705389..bc15b05c60e54b331acd5304b57248758fdc7357 100644 (file)
@@ -8,7 +8,7 @@ exports[`should render correctly the date inputs 1`] = `
     name="from"
     onChange={[Function]}
     placeholder="from"
-    value="10/27/2016"
+    value="2016-10-27"
   />
   —
   <DateInput
@@ -17,7 +17,7 @@ exports[`should render correctly the date inputs 1`] = `
     name="to"
     onChange={[Function]}
     placeholder="to"
-    value="12/27/2016"
+    value="2016-12-27"
   />
   <button
     className="spacer-left"
index 03eb0510b61d1d9cd75886d69e4b966be069dc45..c825610ce4e950320948ba495dcdab9497364935 100644 (file)
@@ -28,7 +28,7 @@ import {
   serializeDate,
   serializeString
 } from '../../helpers/query';
-import { startOfDay } from '../../helpers/dates';
+import { parseDate, startOfDay } from '../../helpers/dates';
 import { getLocalizedMetricName, translate } from '../../helpers/l10n';
 /*:: import type { Analysis, MeasureHistory, Metric, Query } from './types'; */
 /*:: import type { RawQuery } from '../../helpers/query'; */
@@ -157,7 +157,7 @@ export function getAnalysesByVersionByDay(analyses /*: Array<Analysis> */, query
       acc.push(currentVersion);
     }
 
-    const day = startOfDay(new Date(analysis.date)).getTime().toString();
+    const day = startOfDay(parseDate(analysis.date)).getTime().toString();
 
     let matchFilters = true;
     if (query.category || query.from || query.to) {
index 695136bb6c679edc5884dfdb87aed76d0b117387..5e33a335fd94f3246e5cbf846ba876db45801029 100644 (file)
@@ -21,7 +21,7 @@
 import React from 'react';
 import classNames from 'classnames';
 import { Link } from 'react-router';
-import { FormattedRelative } from 'react-intl';
+import DateFromNow from '../../../components/intl/DateFromNow';
 import DateTimeFormatter from '../../../components/intl/DateTimeFormatter.tsx';
 import ProjectCardQualityGate from './ProjectCardQualityGate';
 import ProjectCardLeakMeasures from './ProjectCardLeakMeasures';
@@ -91,12 +91,12 @@ export default function ProjectCardLeak({ measures, organization, project } /*:
           hasLeakPeriodStart &&
           <div className="project-card-dates note text-right pull-right">
             {hasLeakPeriodStart &&
-              <FormattedRelative value={project.leakPeriodDate}>
+              <DateFromNow date={project.leakPeriodDate}>
                 {fromNow =>
                   <span className="project-card-leak-date pull-right">
                     {translateWithParameters('projects.leak_period_x', fromNow)}
                   </span>}
-              </FormattedRelative>}
+              </DateFromNow>}
             {isProjectAnalyzed &&
               <DateTimeFormatter date={project.analysisDate}>
                 {formattedDate =>
index 2ac25e044ab094ca6848d304867e57fb9feb50a1..8ada7b201740a0640ff7d4789a1ca42631cd4756 100644 (file)
@@ -38,8 +38,8 @@ const MEASURES = {
 it('should display analysis date and leak start date', () => {
   const card = shallow(<ProjectCardLeak type="leak" measures={MEASURES} project={PROJECT} />);
   expect(card.find('.project-card-dates').exists()).toBeTruthy();
-  expect(card.find('.project-card-dates').find('FormattedRelative').getNodes()).toHaveLength(1);
-  expect(card.find('.project-card-dates').find('DateTimeFormatter').getNodes()).toHaveLength(1);
+  expect(card.find('.project-card-dates').find('DateFromNow')).toHaveLength(1);
+  expect(card.find('.project-card-dates').find('DateTimeFormatter')).toHaveLength(1);
 });
 
 it('should not display analysis date or leak start date', () => {
index 591ed49e1c8f97b595c2cc8d33b9fb909594b823..91275b640483f47694c70d71017a536123954935 100644 (file)
@@ -35,9 +35,8 @@ exports[`should display the leak measures and quality gate 1`] = `
     <div
       className="project-card-dates note text-right pull-right"
     >
-      <FormattedRelative
-        updateInterval={10000}
-        value="2016-12-01"
+      <DateFromNow
+        date="2016-12-01"
       />
       <DateTimeFormatter
         date="2017-01-01"
index 1e94f724259824804ca8827dc96f84555f4c3a2e..9fef642d8e7750f0738ea928f5454b4b168fac33 100644 (file)
@@ -23,7 +23,7 @@ import ChangesList from './ChangesList';
 import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
 import { translate } from '../../../helpers/l10n';
 import { getRulesUrl } from '../../../helpers/urls';
-import { differenceInSeconds } from '../../../helpers/dates';
+import { differenceInSeconds, parseDate } from '../../../helpers/dates';
 import { ProfileChangelogEvent } from '../types';
 
 interface Props {
@@ -37,7 +37,7 @@ export default function Changelog(props: Props) {
   const rows = props.events.map((event, index) => {
     const prev = index > 0 ? props.events[index - 1] : null;
     const isSameDate =
-      prev != null && differenceInSeconds(new Date(prev.date), new Date(event.date)) < 10;
+      prev != null && differenceInSeconds(parseDate(prev.date), parseDate(event.date)) < 10;
     const isBulkChange =
       prev != null &&
       isSameDate &&
index e59479fccf3791540002a3f3dfa90b8490fab3e5..9c91e098135180c5b4443a541be8dace51ae14db 100644 (file)
@@ -19,6 +19,7 @@
  */
 import * as React from 'react';
 import DateInput from '../../../components/controls/DateInput';
+import { toShortNotSoISOString } from '../../../helpers/dates';
 import { translate } from '../../../helpers/l10n';
 
 interface Props {
@@ -36,19 +37,21 @@ export default class ChangelogSearch extends React.PureComponent<Props> {
     this.props.onReset();
   }
 
+  formatDate = (date?: string) => (date ? toShortNotSoISOString(date) : undefined);
+
   render() {
     return (
       <div className="display-inline-block" id="quality-profile-changelog-form">
         <DateInput
           name="since"
-          value={this.props.fromDate}
+          value={this.formatDate(this.props.fromDate)}
           placeholder="From"
           onChange={this.props.onFromDateChange}
         />
         {' — '}
         <DateInput
           name="to"
-          value={this.props.toDate}
+          value={this.formatDate(this.props.toDate)}
           placeholder="To"
           onChange={this.props.onToDateChange}
         />
index 3841c70ee0fa4c874b9c99c6ba1ec60e011417ff..01c08688af96efc5f87857431de8fd584a90b66d 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { FormattedRelative } from 'react-intl';
+import DateFromNow from '../../../components/intl/DateFromNow';
 import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
 import Tooltip from '../../../components/controls/Tooltip';
 import { translate } from '../../../helpers/l10n';
@@ -31,7 +31,7 @@ export default function ProfileDate({ date }: Props) {
   return date
     ? <Tooltip overlay={<DateTimeFormatter date={date} />}>
         <span>
-          <FormattedRelative value={date} />
+          <DateFromNow date={date} />
         </span>
       </Tooltip>
     : <span>
index d058aa01acdc1dc30d556da706e6af7312c1bb59..6d5c9db9d9fe7d327588a62c8cfd4529c40b2827 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { sortBy } from 'lodash';
-import { differenceInYears, isValidDate } from '../../helpers/dates';
+import { differenceInYears, isValidDate, parseDate } from '../../helpers/dates';
 import { Profile } from './types';
 
 export function sortProfiles(profiles: Profile[]) {
@@ -67,7 +67,7 @@ export function createFakeProfile(overrides?: any) {
 
 export function isStagnant(profile: Profile): boolean {
   if (profile.userUpdatedAt) {
-    const updateDate = new Date(profile.userUpdatedAt);
+    const updateDate = parseDate(profile.userUpdatedAt);
     if (isValidDate(updateDate)) {
       return differenceInYears(new Date(), updateDate) >= 1;
     }
index 0c7894895878cdffa33ff2b5ef27f18daeb4d75f..91670a3c89506597831879a7914bd7a1b8d3396f 100644 (file)
@@ -19,6 +19,7 @@
  */
 import * as React from 'react';
 import { DateSource, FormattedDate } from 'react-intl';
+import { parseDate } from '../../helpers/dates';
 
 interface Props {
   children?: (formattedDate: string) => React.ReactNode;
@@ -34,7 +35,7 @@ export default function DateFormatter({ children, date, long }: Props) {
   return (
     <FormattedDate
       children={children}
-      value={date}
+      value={parseDate(date)}
       {...(long ? longFormatterOption : formatterOption)}
     />
   );
diff --git a/server/sonar-web/src/main/js/components/intl/DateFromNow.tsx b/server/sonar-web/src/main/js/components/intl/DateFromNow.tsx
new file mode 100644 (file)
index 0000000..ce2adea
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { DateSource, FormattedRelative } from 'react-intl';
+import { parseDate } from '../../helpers/dates';
+
+interface Props {
+  children?: (formattedDate: string) => React.ReactNode;
+  date: DateSource;
+}
+
+export default function DateFromNow({ children, date }: Props) {
+  return <FormattedRelative children={children} value={parseDate(date)} />;
+}
index 560270c23e6e3e8f77aea3c7772cb67e7d457880..9d9c1bcd03c0b92d0be639472184c863cb2f4f09 100644 (file)
@@ -19,6 +19,7 @@
  */
 import * as React from 'react';
 import { DateSource, FormattedDate } from 'react-intl';
+import { parseDate } from '../../helpers/dates';
 
 interface Props {
   children?: (formattedDate: string) => React.ReactNode;
@@ -34,5 +35,5 @@ export const formatterOption = {
 };
 
 export default function DateTimeFormatter({ children, date }: Props) {
-  return <FormattedDate children={children} value={date} {...formatterOption} />;
+  return <FormattedDate children={children} value={parseDate(date)} {...formatterOption} />;
 }
index f8c7e90233ac2720b34182d2541181f56453f3f7..8f9aaa32191bc891e5ea765a6965d989bf496e65 100644 (file)
@@ -21,6 +21,7 @@ import * as React from 'react';
 import DateFormatter from './DateFormatter';
 import DateTimeFormatter from './DateTimeFormatter';
 import Tooltip from '../controls/Tooltip';
+import { parseDate } from '../../helpers/dates';
 
 interface Props {
   className?: string;
@@ -29,14 +30,15 @@ interface Props {
 }
 
 export default function DateTooltipFormatter({ className, date, placement }: Props) {
+  const parsedDate = parseDate(date);
   return (
-    <DateFormatter date={date} long={true}>
+    <DateFormatter date={parsedDate} long={true}>
       {formattedDate =>
         <Tooltip
-          overlay={<DateTimeFormatter date={date} />}
+          overlay={<DateTimeFormatter date={parsedDate} />}
           placement={placement}
           mouseEnterDelay={0.5}>
-          <time className={className} dateTime={new Date(date as Date).toISOString()}>
+          <time className={className} dateTime={parsedDate.toISOString()}>
             {formattedDate}
           </time>
         </Tooltip>}
index 9823aa8a9ce1a007a2e8bc75446e1e30b3179b35..efa15fc38a0104935b06a3f5b0fb42b4d28b726f 100644 (file)
@@ -19,6 +19,7 @@
  */
 import * as React from 'react';
 import { DateSource, FormattedTime } from 'react-intl';
+import { parseDate } from '../../helpers/dates';
 
 interface Props {
   children?: (formattedDate: string) => React.ReactNode;
@@ -34,7 +35,7 @@ export default function TimeFormatter({ children, date, long }: Props) {
   return (
     <FormattedTime
       children={children}
-      value={date}
+      value={parseDate(date)}
       {...(long ? longFormatterOption : formatterOption)}
     />
   );
index 4ee3edc783eb3288cc648097145d87f60f51817e..857ed31e2f933219a01321912a62faa66bde7344 100644 (file)
@@ -20,6 +20,7 @@
 import * as React from 'react';
 import TimeFormatter from './TimeFormatter';
 import Tooltip from '../controls/Tooltip';
+import { parseDate } from '../../helpers/dates';
 
 interface Props {
   className?: string;
@@ -28,14 +29,15 @@ interface Props {
 }
 
 export default function TimeTooltipFormatter({ className, date, placement }: Props) {
+  const parsedDate = parseDate(date);
   return (
-    <TimeFormatter date={date} long={false}>
+    <TimeFormatter date={parsedDate} long={false}>
       {formattedTime =>
         <Tooltip
-          overlay={<TimeFormatter date={date} long={true} />}
+          overlay={<TimeFormatter date={parsedDate} long={true} />}
           placement={placement}
           mouseEnterDelay={0.5}>
-          <time className={className} dateTime={new Date(date as Date).toISOString()}>
+          <time className={className} dateTime={parsedDate.toISOString()}>
             {formattedTime}
           </time>
         </Tooltip>}
index 404e395479ae0be8c3518fc1f341df03e8a7e6d6..5baedc3cd610b70e36aeb596f6fbfc71546342af 100644 (file)
@@ -19,9 +19,9 @@
  */
 // @flow
 import React from 'react';
-import { FormattedRelative } from 'react-intl';
 import BubblePopupHelper from '../../../components/common/BubblePopupHelper';
 import ChangelogPopup from '../popups/ChangelogPopup';
+import DateFromNow from '../../../components/intl/DateFromNow';
 import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
 import Tooltip from '../../../components/controls/Tooltip';
 /*:: import type { Issue } from '../types'; */
@@ -63,7 +63,7 @@ export default class IssueChangelog extends React.PureComponent {
             className="button-link issue-action issue-action-with-options js-issue-show-changelog"
             onClick={this.handleClick}>
             <span className="issue-meta-label">
-              <FormattedRelative value={this.props.creationDate} />
+              <DateFromNow date={this.props.creationDate} />
             </span>
             <i className="icon-dropdown little-spacer-left" />
           </button>
index 51865ad5850b29c2825a798c81e2737f380750fd..aa6eeb9013fa4115337b130ef27b617a4b33d284 100644 (file)
  */
 // @flow
 import React from 'react';
-import { FormattedRelative } from 'react-intl';
 import Avatar from '../../../components/ui/Avatar';
 import BubblePopupHelper from '../../../components/common/BubblePopupHelper';
 import CommentDeletePopup from '../popups/CommentDeletePopup';
 import CommentPopup from '../popups/CommentPopup';
+import DateFromNow from '../../../components/intl/DateFromNow';
 /*:: import type { IssueComment } from '../types'; */
 
 /*::
@@ -98,7 +98,7 @@ export default class IssueCommentLine extends React.PureComponent {
           tabIndex={0}
         />
         <div className="issue-comment-age">
-          <FormattedRelative value={comment.createdAt} />
+          <DateFromNow date={comment.createdAt} />
         </div>
         <div className="issue-comment-actions">
           {comment.updatable &&
index 531d5bf7ab29a8ac5c921924c685091b9fe7b585..30f81a378f26f286eff6eb72ce7ce6fd6d034f34 100644 (file)
@@ -43,9 +43,8 @@ exports[`should open the popup when the button is clicked 2`] = `
       <span
         className="issue-meta-label"
       >
-        <FormattedRelative
-          updateInterval={10000}
-          value="2017-03-01T09:36:01+0100"
+        <DateFromNow
+          date="2017-03-01T09:36:01+0100"
         />
       </span>
       <i
@@ -90,9 +89,8 @@ exports[`should render correctly 1`] = `
       <span
         className="issue-meta-label"
       >
-        <FormattedRelative
-          updateInterval={10000}
-          value="2017-03-01T09:36:01+0100"
+        <DateFromNow
+          date="2017-03-01T09:36:01+0100"
         />
       </span>
       <i
index a3b7b63d015656aed346648e05c3a16363ffe441..d31a9e8eba2ceed67a4a69493200b6bea31d87a7 100644 (file)
@@ -42,9 +42,8 @@ exports[`should open the right popups when the buttons are clicked 3`] = `
   <div
     className="issue-comment-age"
   >
-    <FormattedRelative
-      updateInterval={10000}
-      value="2017-03-01T09:36:01+0100"
+    <DateFromNow
+      date="2017-03-01T09:36:01+0100"
     />
   </div>
   <div
@@ -141,9 +140,8 @@ exports[`should render correctly a comment that is not updatable 1`] = `
   <div
     className="issue-comment-age"
   >
-    <FormattedRelative
-      updateInterval={10000}
-      value="2017-03-01T09:36:01+0100"
+    <DateFromNow
+      date="2017-03-01T09:36:01+0100"
     />
   </div>
   <div
@@ -182,9 +180,8 @@ exports[`should render correctly a comment that is updatable 1`] = `
   <div
     className="issue-comment-age"
   >
-    <FormattedRelative
-      updateInterval={10000}
-      value="2017-03-01T09:36:01+0100"
+    <DateFromNow
+      date="2017-03-01T09:36:01+0100"
     />
   </div>
   <div
index a75ee83fe44b7e55dd31fda1fdb5c070412c6cc3..dadb5788eac73d3d41217fec898e86e6d5650a49 100644 (file)
@@ -21,7 +21,7 @@ import $ from 'jquery';
 import { max } from 'd3-array';
 import { select } from 'd3-selection';
 import { scaleLinear, scaleBand } from 'd3-scale';
-import { isSameDay, toNotSoISOString } from '../../helpers/dates';
+import { isSameDay, parseDate, toNotSoISOString } from '../../helpers/dates';
 
 function trans(left, top) {
   return `translate(${left}, ${top})`;
@@ -51,7 +51,7 @@ $.fn.barchart = function(data) {
     const options = { ...defaults(), ...$(this).data() };
     Object.assign(options, {
       width: options.width || $(this).width(),
-      endDate: options.endDate ? new Date(options.endDate) : null
+      endDate: options.endDate ? parseDate(options.endDate) : null
     });
 
     const container = select(this);
@@ -91,9 +91,9 @@ $.fn.barchart = function(data) {
         .attr('width', barWidth)
         .attr('height', d => Math.floor(yScale(d.count)))
         .style('cursor', 'pointer')
-        .attr('data-period-start', d => toNotSoISOString(new Date(d.val)))
+        .attr('data-period-start', d => toNotSoISOString(d.val))
         .attr('data-period-end', (d, i) => {
-          const ending = i < data.length - 1 ? new Date(data[i + 1].val) : options.endDate;
+          const ending = i < data.length - 1 ? data[i + 1].val : options.endDate;
           if (ending) {
             return toNotSoISOString(ending);
           } else {
@@ -101,10 +101,10 @@ $.fn.barchart = function(data) {
           }
         })
         .attr('title', (d, i) => {
-          const beginning = new Date(d.val);
+          const beginning = parseDate(d.val);
           let ending = options.endDate;
           if (i < data.length - 1) {
-            ending = new Date(data[i + 1].val);
+            ending = parseDate(data[i + 1].val);
             ending.setDate(ending.getDate() - 1);
           }
           if (ending) {
index 455886d15a8813b42c8fce7a00c0e3fa2609441f..bbdb35139d0ec3a37b2c0b99e644bd049ff10956 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as dates from '../dates';
+import { parseDate } from '../../helpers/dates';
 
-const recentDate = new Date('2017-08-16T12:00:00.000Z');
-const recentDate2 = new Date('2016-12-16T12:00:00.000Z');
-const oldDate = new Date('2014-01-12T12:00:00.000Z');
+const recentDate = parseDate('2017-08-16T12:00:00.000Z');
+const recentDate2 = parseDate('2016-12-16T12:00:00.000Z');
+const oldDate = parseDate('2014-01-12T12:00:00.000Z');
 
 it('toShortNotSoISOString', () => {
   expect(dates.toShortNotSoISOString(recentDate)).toBe('2017-08-16');
@@ -39,14 +40,14 @@ it('startOfDay', () => {
 it('isValidDate', () => {
   expect(dates.isValidDate(recentDate)).toBeTruthy();
   expect(dates.isValidDate(new Date())).toBeTruthy();
-  expect(dates.isValidDate(new Date('foo'))).toBeFalsy();
+  expect(dates.isValidDate(parseDate('foo'))).toBeFalsy();
 });
 
 it('isSameDay', () => {
-  expect(dates.isSameDay(recentDate, new Date(recentDate))).toBeTruthy();
+  expect(dates.isSameDay(recentDate, parseDate(recentDate))).toBeTruthy();
   expect(dates.isSameDay(recentDate, recentDate2)).toBeFalsy();
   expect(dates.isSameDay(recentDate, oldDate)).toBeFalsy();
-  expect(dates.isSameDay(recentDate, new Date('2016-08-16T12:00:00.000Z'))).toBeFalsy();
+  expect(dates.isSameDay(recentDate, parseDate('2016-08-16T12:00:00.000Z'))).toBeFalsy();
 });
 
 it('differenceInYears', () => {
@@ -56,14 +57,14 @@ it('differenceInYears', () => {
 });
 
 it('differenceInDays', () => {
-  expect(dates.differenceInDays(recentDate, new Date('2017-08-01T12:00:00.000Z'))).toBe(15);
-  expect(dates.differenceInDays(recentDate, new Date('2017-08-15T23:00:00.000Z'))).toBe(0);
+  expect(dates.differenceInDays(recentDate, parseDate('2017-08-01T12:00:00.000Z'))).toBe(15);
+  expect(dates.differenceInDays(recentDate, parseDate('2017-08-15T23:00:00.000Z'))).toBe(0);
   expect(dates.differenceInDays(recentDate, recentDate2)).toBe(243);
   expect(dates.differenceInDays(recentDate, oldDate)).toBe(1312);
 });
 
 it('differenceInSeconds', () => {
-  expect(dates.differenceInSeconds(recentDate, new Date('2017-08-16T10:00:00.000Z'))).toBe(7200);
-  expect(dates.differenceInSeconds(recentDate, new Date('2017-08-16T12:00:00.500Z'))).toBe(0);
+  expect(dates.differenceInSeconds(recentDate, parseDate('2017-08-16T10:00:00.000Z'))).toBe(7200);
+  expect(dates.differenceInSeconds(recentDate, parseDate('2017-08-16T12:00:00.500Z'))).toBe(0);
   expect(dates.differenceInSeconds(recentDate, oldDate)).toBe(113356800);
 });
index 11d7b289cae844dd62aead3ac2f768d6478bdd65..9c2c8c24b3716d7ddee7fc4f04f346f740c53ea7 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as query from '../query';
+import { parseDate } from '../dates';
 
 describe('queriesEqual', () => {
   it('should correctly test equality of two queries', () => {
@@ -78,7 +79,7 @@ describe('parseAsDate', () => {
 });
 
 describe('serializeDate', () => {
-  const date = new Date('2016-06-20T13:09:48.256Z');
+  const date = parseDate('2016-06-20T13:09:48.256Z');
   it('should serialize string correctly', () => {
     expect(query.serializeDate(date)).toBe('2016-06-20T13:09:48+0000');
     expect(query.serializeDate('')).toBeUndefined();
index 5bbb50b3bff4697722ec9e4c7b7cc88fa0366b44..3bba40df67eb175896d1cc568544dc04f988c5ab 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-
-const MILLISECONDS_IN_MINUTE = 60 * 1000;
-const MILLISECONDS_IN_DAY = MILLISECONDS_IN_MINUTE * 60 * 24;
+import {
+  differenceInDays as _differenceInDays,
+  differenceInSeconds as _differenceInSeconds,
+  differenceInYears as _differenceInYears,
+  isSameDay as _isSameDay,
+  parse,
+  startOfDay as _startOfDay
+} from 'date-fns';
 
 function pad(number: number) {
   if (number < 10) {
@@ -28,31 +33,24 @@ function pad(number: number) {
   return number;
 }
 
-function compareDateAsc(dateLeft: Date, dateRight: Date): number {
-  var timeLeft = dateLeft.getTime();
-  var timeRight = dateRight.getTime();
+type ParsableDate = string | number | Date;
 
-  if (timeLeft < timeRight) {
-    return -1;
-  } else if (timeLeft > timeRight) {
-    return 1;
-  } else {
-    return 0;
-  }
+export function parseDate(rawDate: ParsableDate): Date {
+  return parse(rawDate);
 }
 
-export function toShortNotSoISOString(date: Date): string {
+export function toShortNotSoISOString(rawDate: ParsableDate): string {
+  const date = parseDate(rawDate);
   return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate());
 }
 
-export function toNotSoISOString(date: Date): string {
+export function toNotSoISOString(rawDate: ParsableDate): string {
+  const date = parseDate(rawDate);
   return date.toISOString().replace(/\..+Z$/, '+0000');
 }
 
 export function startOfDay(date: Date): Date {
-  const startDay = new Date(date);
-  startDay.setHours(0, 0, 0, 0);
-  return startDay;
+  return _startOfDay(date);
 }
 
 export function isValidDate(date: Date): boolean {
@@ -60,31 +58,17 @@ export function isValidDate(date: Date): boolean {
 }
 
 export function isSameDay(dateLeft: Date, dateRight: Date): boolean {
-  const startDateLeft = startOfDay(dateLeft);
-  const startDateRight = startOfDay(dateRight);
-  return startDateLeft.getTime() === startDateRight.getTime();
+  return _isSameDay(dateLeft, dateRight);
 }
 
 export function differenceInYears(dateLeft: Date, dateRight: Date): number {
-  const sign = compareDateAsc(dateLeft, dateRight);
-  const diff = Math.abs(dateLeft.getFullYear() - dateRight.getFullYear());
-  const tmpLeftDate = new Date(dateLeft);
-  tmpLeftDate.setFullYear(dateLeft.getFullYear() - sign * diff);
-  const isLastYearNotFull = compareDateAsc(tmpLeftDate, dateRight) === -sign;
-  return sign * (diff - (isLastYearNotFull ? 1 : 0));
+  return _differenceInYears(dateLeft, dateRight);
 }
 
 export function differenceInDays(dateLeft: Date, dateRight: Date): number {
-  const startDateLeft = startOfDay(dateLeft);
-  const startDateRight = startOfDay(dateRight);
-  const timestampLeft =
-    startDateLeft.getTime() - startDateLeft.getTimezoneOffset() * MILLISECONDS_IN_MINUTE;
-  const timestampRight =
-    startDateRight.getTime() - startDateRight.getTimezoneOffset() * MILLISECONDS_IN_MINUTE;
-  return Math.round((timestampLeft - timestampRight) / MILLISECONDS_IN_DAY);
+  return _differenceInDays(dateLeft, dateRight);
 }
 
 export function differenceInSeconds(dateLeft: Date, dateRight: Date): number {
-  const diff = (dateLeft.getTime() - dateRight.getTime()) / 1000;
-  return diff > 0 ? Math.floor(diff) : Math.ceil(diff);
+  return _differenceInSeconds(dateLeft, dateRight);
 }
index ef43101b3325c169f07a56fdd3f0c69b8a34ccc7..fc61cb21129e2d708381c6b513b540d59d608de8 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+const { parseDate } = require('../../helpers/dates');
+const { DEFAULT_LANGUAGE } = require('../../helpers/l10n');
+
 module.exports = function(date) {
-  return new Intl.DateTimeFormat(localStorage.getItem('l10n.locale') || 'en', {
+  return new Intl.DateTimeFormat(localStorage.getItem('l10n.locale') || DEFAULT_LANGUAGE, {
     year: 'numeric',
     month: 'long',
     day: 'numeric'
-  }).format(new Date(date));
+  }).format(parseDate(date));
 };
index 3af77ae1d6c8ab024655347338c3dc87bf74be68..2334f217f8847e9218b049f85f96bc5e5af07aa7 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+const { parseDate } = require('../../helpers/dates');
+const { DEFAULT_LANGUAGE } = require('../../helpers/l10n');
+
 module.exports = function(date) {
-  return new Intl.DateTimeFormat(localStorage.getItem('l10n.locale') || 'en', {
+  return new Intl.DateTimeFormat(localStorage.getItem('l10n.locale') || DEFAULT_LANGUAGE, {
     year: 'numeric',
     month: 'long',
     day: 'numeric',
     hour: 'numeric',
     minute: 'numeric'
-  }).format(new Date(date));
+  }).format(parseDate(date));
 };
index ea25726d79f2a5492219a7d6d2853ad70af98c89..c17513de21853458821b765229e24201352e0861 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 const IntlRelativeFormat = require('intl-relativeformat');
+const { DEFAULT_LANGUAGE } = require('../../helpers/l10n');
 
 module.exports = function(date) {
-  return new IntlRelativeFormat(localStorage.getItem('l10n.locale') || 'en').format(date);
+  return new IntlRelativeFormat(localStorage.getItem('l10n.locale') || DEFAULT_LANGUAGE).format(
+    date
+  );
 };
index 57f519490741966534ba5ddfb99b2adfbc9b1163..5a1e4bcb190bf1c97ec0b7fe9564df32aac37b23 100644 (file)
@@ -113,7 +113,7 @@ export function requestMessages(): Promise<string> {
         // do nothing
       }
       resetBundle(messages);
-      return effectiveLocale || browserLocale || DEFAULT_LANGUAGE;
+      return effectiveLocale;
     },
     ({ response }) => {
       if (response && response.status === 304) {
index 4c5ac1c876d720a2e3cc12870559510e5796d1f0..d458a1931ebd50cace901ce3ae43025484a83cb3 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { translate, translateWithParameters } from './l10n';
+import { parseDate } from './dates';
 
 export function getPeriod(periods, index) {
   if (!Array.isArray(periods)) {
@@ -50,7 +51,7 @@ export function getPeriodDate(period) {
     return null;
   }
 
-  return new Date(period.date);
+  return parseDate(period.date);
 }
 
 export function getLeakPeriodLabel(periods) {
index a87eefe5e3f5d5d43b8f7f2ce9876da3abb21900..400390e65b88c4734c965849ecdcd43db81a7259 100644 (file)
@@ -19,7 +19,7 @@
  */
 // @flow
 import { isNil, omitBy } from 'lodash';
-import { isValidDate, toNotSoISOString } from './dates';
+import { isValidDate, parseDate, toNotSoISOString } from './dates';
 
 /*::
 export type RawQuery = { [string]: any };
@@ -66,7 +66,7 @@ export function parseAsBoolean(
 
 export function parseAsDate(value /*: ?string */) /*: Date | void */ {
   if (value) {
-    const date = new Date(value);
+    const date = parseDate(value);
     if (isValidDate(date)) {
       return date;
     }
index a093176935548ed04a774cabb64c312bacbcfef7..02e2ce1c37c10c7759feb62e0b3df0f1ad558d39 100644 (file)
@@ -17,7 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { shallow, ShallowWrapper } from 'enzyme';
+import { shallow, ShallowRendererProps, ShallowWrapper } from 'enzyme';
 import { IntlProvider } from 'react-intl';
 
 export const mockEvent = {
@@ -71,8 +71,9 @@ export function doAsync(fn: Function): Promise<void> {
   });
 }
 
+// Create the IntlProvider to retrieve context for wrapping around.
 const intlProvider = new IntlProvider({ locale: 'en' }, {});
 const { intl } = intlProvider.getChildContext();
-export function shallowWithIntl(node, options = {}) {
+export function shallowWithIntl(node: React.ReactElement<any>, options: ShallowRendererProps = {}) {
   return shallow(node, { ...options, context: { intl, ...options.context } });
 }
index fc6cfa7afabe3840ddae391a5ef8cb86fad635bc..3ee0524870e29a2bdb266b34299f4a24ca053444 100644 (file)
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.0.tgz#f2312039e780bdf89d7d4102a26ec11de5ec58aa"
 
+"@types/date-fns@2.6.0":
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/@types/date-fns/-/date-fns-2.6.0.tgz#b062ca46562002909be0c63a6467ed173136acc1"
+  dependencies:
+    date-fns "*"
+
 "@types/enzyme@2.8.6":
   version "2.8.6"
   resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-2.8.6.tgz#b508da0860b4fcda2ab851d600e5bfaf695775dd"
@@ -1975,6 +1981,10 @@ dashdash@^1.12.0:
   dependencies:
     assert-plus "^1.0.0"
 
+date-fns@*, date-fns@1.28.5:
+  version "1.28.5"
+  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.28.5.tgz#257cfc45d322df45ef5658665967ee841cd73faf"
+
 date-now@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"