Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: handle issues with one day off #192

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module.exports = {
rootDir: 'src',
coverageDirectory: '<rootDir>/../coverage',
testEnvironment: 'jsdom'
};
25 changes: 12 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,32 +43,31 @@
"prop-types": "^15.6.2"
},
"devDependencies": {
"@babel/cli": "^7.1.2",
"@babel/core": "^7.1.2",
"@babel/plugin-external-helpers": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/preset-env": "^7.1.0",
"@babel/preset-react": "^7.0.0",
"@babel/cli": "^7.19.3",
"@babel/core": "^7.19.6",
"@babel/plugin-external-helpers": "^7.18.6",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/preset-env": "^7.19.4",
"@babel/preset-react": "^7.18.6",
"@testing-library/react": "^12.1.5",
"@testing-library/user-event": "^14.4.3",
"autoprefixer": "^9.1.3",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.0.2",
"babel-jest": "^24.0.0",
"babel-eslint": "^10.1.0",
"babel-jest": "^29.2.2",
"cross-env": "^5.2.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.3.1",
"eslint": "^5.16.0",
"eslint-config-airbnb": "^17.1.1",
"eslint-config-prettier": "^6.0.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.14.3",
"jest": "^24.0.0",
"jest": "^29.2.2",
"jest-environment-jsdom": "^29.2.2",
"npm-run-all": "^4.1.3",
"postcss-cli": "^6.0.0",
"prettier": "^1.14.2",
"react": "^16.4.2",
"react-dom": "^16.4.2",
"react-test-renderer": "^16.4.2",
"rimraf": "^2.0.0",
"rollup": "^1.6.0",
"rollup-plugin-babel": "4",
Expand Down
21 changes: 20 additions & 1 deletion src/helpers.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
// returns a new date shifted a certain number of days (can be negative)
import { MILLISECONDS_IN_ONE_DAY } from './constants';

export function shiftDate(date, numDays) {
const newDate = new Date(date);
newDate.setDate(newDate.getDate() + numDays);
return newDate;
}

export function getBeginningTimeForDate(date) {
export function startOfDay(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
}
export function endOfDay(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59);
}

// obj can be a parseable string, a millisecond timestamp, or a Date object
export function convertToDate(obj) {
Expand All @@ -25,3 +30,17 @@ export function getRange(count) {
}
return arr;
}

export function getDateDifferenceInDays(startDate, endDate) {
const timeDiff = endOfDay(convertToDate(endDate)) - startOfDay(convertToDate(startDate));
return Math.round(timeDiff / MILLISECONDS_IN_ONE_DAY);
}

export function getISODate(date) {
const clone = new Date(date);
clone.setHours(12);
return clone
.toISOString()
.split('T')
.shift();
}
29 changes: 26 additions & 3 deletions src/helpers.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {
convertToDate,
dateNDaysAgo,
getBeginningTimeForDate,
startOfDay,
getRange,
shiftDate,
getDateDifferenceInDays,
} from './helpers';

describe('shiftDate', () => {
Expand Down Expand Up @@ -64,12 +65,12 @@ describe('shiftDate', () => {
});
});

describe('getBeginningTimeForDate', () => {
describe('startOfDay', () => {
it('gets midnight (in the local timezone) on the date passed in', () => {
const inputDate = new Date(2017, 11, 25, 21, 30, 59, 750);
const expectedDate = new Date(2017, 11, 25, 0, 0, 0, 0);

expect(getBeginningTimeForDate(inputDate).getTime()).toBe(expectedDate.getTime());
expect(startOfDay(inputDate).getTime()).toBe(expectedDate.getTime());
});
});

Expand Down Expand Up @@ -172,3 +173,25 @@ describe('getRange', () => {
expect(getRange(5)).toEqual([0, 1, 2, 3, 4]);
});
});

describe('getDateDifferenceInDays', () => {
it('returns the number of days between 2 dates of the same month', () => {
expect(getDateDifferenceInDays('2022-10-01', '2022-10-31')).toEqual(31);
});

it('returns the number of days between 2 dates with a month with 30 days', () => {
expect(getDateDifferenceInDays('2022-11-01', '2022-11-30')).toEqual(30);
});

it('returns the number of days between 2 dates with a month with 29 days', () => {
expect(getDateDifferenceInDays('2024-02-01', '2024-02-29')).toEqual(29);
});

it('returns the number of days between 2 dates with a month with 28 days', () => {
expect(getDateDifferenceInDays('2022-02-01', '2022-02-28')).toEqual(28);
});

it('returns the number of days between 2 dates with multiple months', () => {
expect(getDateDifferenceInDays('2022-12-31', '2023-01-03')).toEqual(4);
});
});
38 changes: 15 additions & 23 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import memoizeOne from 'memoize-one';
import { DAYS_IN_WEEK, MILLISECONDS_IN_ONE_DAY, DAY_LABELS, MONTH_LABELS } from './constants';
import { DAY_LABELS, DAYS_IN_WEEK, MILLISECONDS_IN_ONE_DAY, MONTH_LABELS } from './constants';
import {
dateNDaysAgo,
shiftDate,
getBeginningTimeForDate,
convertToDate,
dateNDaysAgo,
endOfDay,
getDateDifferenceInDays,
getRange,
shiftDate,
startOfDay,
} from './helpers';

const SQUARE_SIZE = 10;
const MONTH_LABEL_GUTTER_SIZE = 4;
const CSS_PSEDUO_NAMESPACE = 'react-calendar-heatmap-';

class CalendarHeatmap extends React.Component {
getDateDifferenceInDays() {
const { startDate, numDays } = this.props;
if (numDays) {
// eslint-disable-next-line no-console
console.warn(
'numDays is a deprecated prop. It will be removed in the next release. Consider using the startDate prop instead.',
);
return numDays;
}
const timeDiff = this.getEndDate() - convertToDate(startDate);
return Math.ceil(timeDiff / MILLISECONDS_IN_ONE_DAY);
}

getSquareSizeWithGutter() {
return SQUARE_SIZE + this.props.gutterSize;
}
Expand All @@ -53,11 +42,11 @@ class CalendarHeatmap extends React.Component {
}

getStartDate() {
return shiftDate(this.getEndDate(), -this.getDateDifferenceInDays() + 1); // +1 because endDate is inclusive
return startOfDay(convertToDate(this.props.startDate));
}

getEndDate() {
return getBeginningTimeForDate(convertToDate(this.props.endDate));
return endOfDay(convertToDate(this.props.endDate));
}

getStartDateWithEmptyDays() {
Expand All @@ -74,7 +63,10 @@ class CalendarHeatmap extends React.Component {

getWeekCount() {
const numDaysRoundedToWeek =
this.getDateDifferenceInDays() + this.getNumEmptyDaysAtStart() + this.getNumEmptyDaysAtEnd();
getDateDifferenceInDays(this.props.startDate, this.props.endDate) +
this.getNumEmptyDaysAtStart() +
this.getNumEmptyDaysAtEnd();

return Math.ceil(numDaysRoundedToWeek / DAYS_IN_WEEK);
}

Expand Down Expand Up @@ -231,7 +223,9 @@ class CalendarHeatmap extends React.Component {
renderSquare(dayIndex, index) {
const indexOutOfRange =
index < this.getNumEmptyDaysAtStart() ||
index >= this.getNumEmptyDaysAtStart() + this.getDateDifferenceInDays();
index >=
this.getNumEmptyDaysAtStart() +
getDateDifferenceInDays(this.props.startDate, this.props.endDate);
if (indexOutOfRange && !this.props.showOutOfRangeDays) {
return null;
}
Expand Down Expand Up @@ -345,7 +339,6 @@ CalendarHeatmap.propTypes = {
.isRequired,
}).isRequired,
).isRequired, // array of objects with date and arbitrary metadata
numDays: PropTypes.number, // number of days back from endDate to show
startDate: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.instanceOf(Date)]), // start of date range
endDate: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.instanceOf(Date)]), // end of date range
gutterSize: PropTypes.number, // size of space between squares
Expand All @@ -365,7 +358,6 @@ CalendarHeatmap.propTypes = {
};

CalendarHeatmap.defaultProps = {
numDays: null,
startDate: dateNDaysAgo(200),
endDate: new Date(),
gutterSize: 1,
Expand Down
Loading