├── .npmignore
├── examples
├── react
│ ├── build
│ │ └── .gitignore
│ ├── .gitignore
│ ├── app.jsx
│ ├── index.html
│ ├── components
│ │ ├── Week.jsx
│ │ ├── Day.jsx
│ │ └── Calendar.jsx
│ └── package.json
└── vue
│ ├── build
│ └── .gitignore
│ ├── .gitignore
│ ├── index.html
│ ├── components
│ ├── week.js
│ ├── day.js
│ └── calendar.js
│ ├── package.json
│ └── app.js
├── .gitignore
├── .travis.yml
├── test
├── .eslintrc
├── formatting.js
├── related-months.js
├── create.js
└── calendar.js
├── .codeclimate.yml
├── CHANGELOG.md
├── src
├── index.js
├── enums.js
├── validate.js
└── month.js
├── .editorconfig
├── .eslintrc
├── LICENSE.md
├── package.json
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | .idea
2 | examples
3 |
--------------------------------------------------------------------------------
/examples/react/build/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/examples/vue/build/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /dist
2 | node_modules
3 | npm-debug.log
4 |
--------------------------------------------------------------------------------
/examples/vue/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 |
--------------------------------------------------------------------------------
/examples/react/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 'stable'
4 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | engines:
2 | eslint:
3 | enabled: true
4 | ratings:
5 | paths:
6 | - src/**/*
7 | exclude_paths:
8 | - lib/**/*
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All Notable changes to `calendar-months` will be documented in this file
4 |
5 | ## 1.0.1
6 | - Initial release
7 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { days, months } from './enums';
2 | import Month from './month';
3 |
4 | export default Month;
5 |
6 | export {
7 | days,
8 | Month,
9 | months,
10 | };
11 |
--------------------------------------------------------------------------------
/examples/react/app.jsx:
--------------------------------------------------------------------------------
1 | import Calendar from './components/Calendar';
2 | import Month from 'calendar-months';
3 | import React from 'react';
4 | import { render } from 'react-dom';
5 |
6 | render(
7 | ,
8 | document.getElementById('calendar')
9 | );
10 |
--------------------------------------------------------------------------------
/examples/vue/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Calendar Months Vue
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/examples/react/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Calendar Months React
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/examples/vue/components/week.js:
--------------------------------------------------------------------------------
1 | import Day from './day';
2 |
3 | export default {
4 |
5 | props: ['days', 'month'],
6 |
7 | template: `
8 |
9 | |
10 |
11 | |
12 |
13 | `,
14 |
15 | components: {
16 | Day,
17 | },
18 |
19 | };
20 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; This file is for unifying the coding style for different editors and IDEs.
2 | ; More information at http://editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | charset = utf-8
8 | indent_size = 4
9 | indent_style = space
10 | end_of_line = lf
11 | insert_final_newline = true
12 | trim_trailing_whitespace = true
13 |
14 | [{package.json,*.scss}]
15 | indent_size = 2
16 |
17 | [*.md]
18 | trim_trailing_whitespace = false
19 |
--------------------------------------------------------------------------------
/examples/react/components/Week.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Day from './Day';
3 |
4 | class Week extends Component {
5 |
6 | render() {
7 | return (
8 |
9 | {this.props.days.map((day, i) =>
10 |
11 | )}
12 |
13 | );
14 | }
15 | }
16 |
17 | export default Week;
18 |
--------------------------------------------------------------------------------
/examples/vue/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "lint": "eslint .",
4 | "build": "browserify app.js -o build/app.js -t [ babelify --presets [ es2015 ] ]"
5 | },
6 | "dependencies": {
7 | "calendar-months": "^1.0.1",
8 | "moment": "^2.15.1",
9 | "vue": "^1.0.0"
10 | },
11 | "devDependencies": {
12 | "babel-preset-es2015": "^6.9.0",
13 | "babelify": "^7.3.0",
14 | "browserify": "^13.0.1",
15 | "eslint": "^2.9.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/enums.js:
--------------------------------------------------------------------------------
1 | const months = {
2 | JANUARY: 0,
3 | FEBRUARY: 1,
4 | MARCH: 2,
5 | APRIL: 3,
6 | MAY: 4,
7 | JUNE: 5,
8 | JULY: 6,
9 | AUGUST: 7,
10 | SEPTEMBER: 8,
11 | OCTOBER: 9,
12 | NOVEMBER: 10,
13 | DECEMBER: 11,
14 | };
15 |
16 | const days = {
17 | SUNDAY: 0,
18 | MONDAY: 1,
19 | TUESDAY: 2,
20 | WEDNESDAY: 3,
21 | THURSDAY: 4,
22 | FRIDAY: 5,
23 | SATURDAY: 6,
24 | };
25 |
26 | export { months, days };
27 |
--------------------------------------------------------------------------------
/examples/vue/app.js:
--------------------------------------------------------------------------------
1 | import Calendar from './components/calendar';
2 | import Month from 'calendar-months';
3 | import Vue from 'vue';
4 |
5 | new Vue({
6 |
7 | el: '#calendar',
8 |
9 | template: `
10 |
11 |
12 |
13 | `,
14 |
15 | components: {
16 | Calendar,
17 | },
18 |
19 | data() {
20 | return {
21 | month: Month.now(),
22 | };
23 | },
24 |
25 | });
26 |
--------------------------------------------------------------------------------
/examples/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "lint": "eslint .",
4 | "build": "browserify app.jsx -o build/app.js --extension=.jsx -t [ babelify --presets [ es2015 react ] ]"
5 | },
6 | "dependencies": {
7 | "calendar-months": "^1.0.1",
8 | "moment": "^2.15.1",
9 | "react": "^15.1.0",
10 | "react-dom": "^15.1.0"
11 | },
12 | "devDependencies": {
13 | "babel-preset-es2015": "^6.9.0",
14 | "babel-preset-react": "^6.5.0",
15 | "babelify": "^7.3.0",
16 | "browserify": "^13.0.1",
17 | "eslint": "^2.9.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "comma-dangle": [2, "always-multiline"],
4 | "indent": [2, 4],
5 | "linebreak-style": [2, "unix"],
6 | "quotes": [2, "single"],
7 | "semi": [2, "always"],
8 | "sort-imports": ["error", { "ignoreCase": true }],
9 | "space-before-function-paren": ["error", { "anonymous": "always", "named": "never" }],
10 | "strict": [2, "never"]
11 | },
12 | "parserOptions": {
13 | "ecmaVersion": 6,
14 | "sourceType": "module"
15 | },
16 | "env": {
17 | "es6": true,
18 | "browser": true
19 | },
20 | "extends": "eslint:recommended"
21 | }
22 |
--------------------------------------------------------------------------------
/examples/vue/components/day.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 |
3 | export default {
4 |
5 | props: ['date', 'month'],
6 |
7 | template: `
8 |
9 | {{ date.format('DD') }}
10 |
11 | `,
12 |
13 | computed: {
14 | color() {
15 | if (!this.isInCurrentMonth()) {
16 | return 'gray';
17 | }
18 |
19 | if (this.isToday()) {
20 | return 'red';
21 | }
22 |
23 | return '';
24 | },
25 | },
26 |
27 | methods: {
28 | isToday() {
29 | return moment().isSame(this.date, 'day');
30 | },
31 | isInCurrentMonth() {
32 | return this.month.containsDay(this.date);
33 | },
34 | },
35 |
36 | };
37 |
--------------------------------------------------------------------------------
/examples/react/components/Day.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import moment from 'moment';
3 |
4 | class Day extends Component {
5 |
6 | isToday() {
7 | return moment().isSame(this.props.date, 'day');
8 | }
9 |
10 | isInCurrentMonth() {
11 | return this.props.month.containsDay(this.props.date);
12 | }
13 |
14 | get color() {
15 | if (!this.isInCurrentMonth()) {
16 | return 'gray';
17 | }
18 |
19 | if (this.isToday()) {
20 | return 'red';
21 | }
22 |
23 | return '';
24 | }
25 |
26 | render() {
27 | return (
28 |
29 |
30 | {this.props.date.format('DD')}
31 |
32 | |
33 | );
34 | }
35 | }
36 |
37 | export default Day;
38 |
--------------------------------------------------------------------------------
/examples/vue/components/calendar.js:
--------------------------------------------------------------------------------
1 | import { days } from 'calendar-months';
2 | import Week from './week';
3 |
4 | export default {
5 |
6 | props: ['month'],
7 |
8 | template: `
9 |
10 |
{{ month.format('MMMM YYYY') }}
11 |
12 |
13 |
14 | | Mon |
15 | Tue |
16 | Wed |
17 | Thu |
18 | Fri |
19 | Sat |
20 | Sun |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | `,
29 |
30 | components: {
31 | Week,
32 | },
33 |
34 | computed: {
35 | weeks() {
36 | return this.month.calendarWeeks(days.MONDAY);
37 | },
38 | },
39 |
40 | };
41 |
--------------------------------------------------------------------------------
/examples/react/components/Calendar.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { days } from 'calendar-months';
3 | import Week from './Week';
4 |
5 | class Calendar extends Component {
6 |
7 | render() {
8 | return (
9 |
10 |
{this.props.month.format('MMMM YYYY')}
11 |
12 |
13 |
14 | | Mon |
15 | Tue |
16 | Wed |
17 | Thu |
18 | Fri |
19 | Sat |
20 | Sun |
21 |
22 |
23 |
24 | {this.props.month.calendarWeeks(days.MONDAY).map((week, i) =>
25 |
26 | )}
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
34 | export default Calendar;
35 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Spatie bvba
4 |
5 | > Permission is hereby granted, free of charge, to any person obtaining a copy
6 | > of this software and associated documentation files (the "Software"), to deal
7 | > in the Software without restriction, including without limitation the rights
8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | > copies of the Software, and to permit persons to whom the Software is
10 | > furnished to do so, subject to the following conditions:
11 | >
12 | > The above copyright notice and this permission notice shall be included in
13 | > all copies or substantial portions of the Software.
14 | >
15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | > THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/validate.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 |
3 | /**
4 | * Determine whether the object is a string.
5 | *
6 | * @param {mixed} object
7 | *
8 | * @return {bool}
9 | */
10 | const isString = (object) => typeof object === 'string';
11 |
12 | /**
13 | * Determine whether the object is an instance of Moment.
14 | *
15 | * @param {mixed} object
16 | *
17 | * @return {bool}
18 | */
19 | const isMoment = (object) => moment.isMoment(object);
20 |
21 | /**
22 | * Determine whether the object is an instance of Date (JavaScript standard library).
23 | *
24 | * @param {mixed} object
25 | *
26 | * @return {bool}
27 | */
28 | const isDate = (object) => object instanceof Date;
29 |
30 | /**
31 | * Determine whether a number is a valid month number (0-11).
32 | *
33 | * @param {number} number
34 | *
35 | * @return {bool}
36 | */
37 | const isValidMonthNumber = (number) => (!isNaN(number) && number >= 0 && number <= 11);
38 |
39 | /**
40 | * Determine whether a number is a valid year number.
41 | *
42 | * @param {number} number
43 | *
44 | * @return {bool}
45 | */
46 | const isValidYearNumber = (number) => !isNaN(number);
47 |
48 | export default { isString, isMoment, isDate, isValidMonthNumber, isValidYearNumber };
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "calendar-months",
3 | "version": "1.0.1",
4 | "description": "Month class with specialized functions for generating calendar user interfaces",
5 | "main": "dist/index.js",
6 | "jsnext:main": "src/index.js",
7 | "scripts": {
8 | "test": "mocha --compilers js:babel-register",
9 | "lint": "eslint src test",
10 | "build": "babel src -d dist",
11 | "prepublish": "npm test && npm run build"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/spatie/calendar-months.git"
16 | },
17 | "keywords": [
18 | "spatie",
19 | "calendar",
20 | "date",
21 | "moment"
22 | ],
23 | "author": "Sebastian De Deyne",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/spatie/calendar-months/issues"
27 | },
28 | "homepage": "https://github.com/spatie/calendar-months",
29 | "devDependencies": {
30 | "babel-cli": "^6.8.0",
31 | "babel-preset-es2015": "^6.3.13",
32 | "babel-register": "^6.4.3",
33 | "chai": "^3.4.1",
34 | "eslint": "^2.9.0",
35 | "mocha": "^2.4.5",
36 | "moment": "^2.10.6"
37 | },
38 | "peerDependencies": {
39 | "moment": "^2.10.6"
40 | },
41 | "babel": {
42 | "presets": [
43 | "es2015"
44 | ]
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/test/formatting.js:
--------------------------------------------------------------------------------
1 | import Month, { months } from '../src/index';
2 | import { assert } from 'chai';
3 |
4 | describe('It can be formatted to', () => {
5 |
6 | it('YYYY-MM by default', () => {
7 |
8 | const cases = [
9 | [ new Month(months.SEPTEMBER, 2015), '2015-09' ],
10 | [ new Month(months.FEBRUARY, 1992), '1992-02' ],
11 | [ new Month(months.DECEMBER, 2019), '2019-12' ],
12 | ];
13 |
14 | cases.forEach(([ month, formatted ]) => {
15 | assert.equal(month.format(), formatted);
16 | });
17 | });
18 |
19 | it('anything your heart desires', () => {
20 |
21 | const cases = [
22 | [ new Month(months.SEPTEMBER, 2015), '2015-09', '15-09', '2015-09-01', '01/09/2015' ],
23 | [ new Month(months.FEBRUARY, 1992), '1992-02', '92-02', '1992-02-01', '01/02/1992' ],
24 | [ new Month(months.DECEMBER, 2019), '2019-12', '19-12', '2019-12-01', '01/12/2019' ],
25 | ];
26 |
27 | cases.forEach(([ month, plain, shortYear, withDay, backwardsWithSlashes ]) => {
28 | assert.equal(month.format('YYYY-MM'), plain);
29 | assert.equal(month.format('YY-MM'), shortYear);
30 | assert.equal(month.format('YYYY-MM-DD'), withDay);
31 | assert.equal(month.format('DD/MM/YYYY'), backwardsWithSlashes);
32 | });
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/test/related-months.js:
--------------------------------------------------------------------------------
1 | import Month, { months } from '../src/index';
2 | import { assert } from 'chai';
3 |
4 | describe('It returns returns a new instance for', () => {
5 |
6 | it('the current month through `thisMonth`', () => {
7 |
8 | const cases = [
9 | [ new Month(months.SEPTEMBER, 2015), '2015-09' ],
10 | [ new Month(months.FEBRUARY, 1992), '1992-02' ],
11 | [ new Month(months.DECEMBER, 2019), '2019-12' ],
12 | ];
13 |
14 | cases.forEach(([ month, result ]) => {
15 | assert.equal(month.thisMonth().format(), result);
16 | });
17 | });
18 |
19 | it('the current month through `clone`', () => {
20 |
21 | const cases = [
22 | [ new Month(months.SEPTEMBER, 2015), '2015-09' ],
23 | [ new Month(months.FEBRUARY, 1992), '1992-02' ],
24 | [ new Month(months.DECEMBER, 2019), '2019-12' ],
25 | ];
26 |
27 | cases.forEach(([ month, result ]) => {
28 | assert.equal(month.clone().format(), result);
29 | });
30 | });
31 |
32 | it('the next month', () => {
33 |
34 | const cases = [
35 | [ new Month(months.SEPTEMBER, 2015), '2015-10' ],
36 | [ new Month(months.FEBRUARY, 1992), '1992-03' ],
37 | [ new Month(months.DECEMBER, 2019), '2020-01' ],
38 | ];
39 |
40 | cases.forEach(([ month, result ]) => {
41 | assert.equal(month.nextMonth().format(), result);
42 | });
43 | });
44 |
45 | it('the last month', () => {
46 |
47 | const cases = [
48 | [ new Month(months.SEPTEMBER, 2015), '2015-08' ],
49 | [ new Month(months.FEBRUARY, 1992), '1992-01' ],
50 | [ new Month(months.DECEMBER, 2019), '2019-11' ],
51 | ];
52 |
53 | cases.forEach(([ month, result ]) => {
54 | assert.equal(month.lastMonth().format(), result);
55 | });
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/test/create.js:
--------------------------------------------------------------------------------
1 | import Month, { months } from '../src/index';
2 | import { assert } from 'chai';
3 | import moment from 'moment';
4 |
5 | describe('It returns a new instance', () => {
6 |
7 | it('through the `new` keyword', () => {
8 |
9 | const cases = [
10 | [ months.SEPTEMBER, 2015 ],
11 | [ months.FEBRUARY, 1992 ],
12 | [ months.DECEMBER, 2019 ],
13 | ];
14 |
15 | cases.forEach(([ monthNumber, yearNumber ]) => {
16 | const month = new Month(monthNumber, yearNumber);
17 |
18 | assert.equal(month.month, monthNumber);
19 | assert.equal(month.year, yearNumber);
20 | });
21 | });
22 |
23 | it('by passing month and year numbers to the `create` method', () => {
24 |
25 | const cases = [
26 | [ months.SEPTEMBER, 2015 ],
27 | [ months.FEBRUARY, 1992 ],
28 | [ months.DECEMBER, 2019 ],
29 | ];
30 |
31 | cases.forEach(([ monthNumber, yearNumber ]) => {
32 | const month = Month.create(monthNumber, yearNumber);
33 |
34 | assert.equal(month.month, monthNumber);
35 | assert.equal(month.year, yearNumber);
36 | });
37 | });
38 |
39 | it('from a `moment` object', () => {
40 |
41 | const cases = [
42 | [ moment('2015-09-17'), months.SEPTEMBER, 2015 ],
43 | [ moment('1992-02-01'), months.FEBRUARY, 1992 ],
44 | [ moment('2019-12-25'), months.DECEMBER, 2019 ],
45 | ];
46 |
47 | cases.forEach(([ moment, monthNumber, yearNumber ]) => {
48 | const month = Month.create(moment);
49 |
50 | assert.equal(month.month, monthNumber);
51 | assert.equal(month.year, yearNumber);
52 | });
53 | });
54 |
55 | it('from a `date` object', () => {
56 |
57 | const cases = [
58 | [ new Date('2015-09-17'), months.SEPTEMBER, 2015 ],
59 | [ new Date('1992-02-01'), months.FEBRUARY, 1992 ],
60 | [ new Date('2019-12-25'), months.DECEMBER, 2019 ],
61 | ];
62 |
63 | cases.forEach(([ date, monthNumber, yearNumber ]) => {
64 | const month = Month.create(date);
65 |
66 | assert.equal(month.month, monthNumber);
67 | assert.equal(month.year, yearNumber);
68 | });
69 | });
70 |
71 | it('from a date string', () => {
72 |
73 | const cases = [
74 | [ '2015-09-17', months.SEPTEMBER, 2015 ],
75 | [ '1992-02-01', months.FEBRUARY, 1992 ],
76 | [ '2019-12-25', months.DECEMBER, 2019 ],
77 | ];
78 |
79 | cases.forEach(([ date, monthNumber, yearNumber ]) => {
80 | const month = Month.create(date);
81 |
82 | assert.equal(month.month, monthNumber);
83 | assert.equal(month.year, yearNumber);
84 | });
85 | });
86 |
87 | it('for the current month', () => {
88 |
89 | const now = new Date();
90 | const thisMonth = Month.thisMonth();
91 |
92 | assert.equal(thisMonth.month, now.getMonth());
93 | assert.equal(thisMonth.year, now.getFullYear());
94 | });
95 | });
96 |
97 | describe('It throws an error when', () => {
98 |
99 | it('an invalid month is provided', () => {
100 |
101 | assert.throw(() => new Month(13, 2015));
102 | assert.throw(() => new Month(12, 2015));
103 | assert.throw(() => new Month(-2, 2015));
104 | assert.throw(() => new Month(null, 2015));
105 | });
106 |
107 | it('an invalid year is provided', () => {
108 |
109 | assert.throw(() => new Month(1, null));
110 | });
111 |
112 | it('an non-supported argument is passed to `create`', () => {
113 |
114 | assert.throw(() => Month.create([1, 2015]));
115 | assert.throw(() => Month.create(() => [1, 2015]));
116 | assert.throw(() => Month.create('january 2015'));
117 | });
118 | });
119 |
--------------------------------------------------------------------------------
/test/calendar.js:
--------------------------------------------------------------------------------
1 | import Month, { days, months } from '../src/index';
2 | import { assert } from 'chai';
3 | import moment from 'moment';
4 |
5 | describe('It returns the first calendar day', () => {
6 |
7 | it('with weeks starting on a Sunday (default)', () => {
8 |
9 | const cases = [
10 | [ Month.create(months.JANUARY, 2015), '2014-12-28' ],
11 | [ Month.create(months.FEBRUARY, 2015), '2015-02-01' ],
12 | [ Month.create(months.JUNE, 2015), '2015-05-31' ],
13 | [ Month.create(months.FEBRUARY, 2016), '2016-01-31' ],
14 | [ Month.create(months.MARCH, 2016), '2016-02-28' ],
15 | ];
16 |
17 | cases.forEach(([ month, result ]) => {
18 | assert.equal(month.firstCalendarDay().format('YYYY-MM-DD'), result);
19 | assert.equal(month.firstCalendarDay(days.SUNDAY).format('YYYY-MM-DD'), result);
20 | });
21 | });
22 |
23 | it('with weeks starting on a a Monday', () => {
24 |
25 | const cases = [
26 | [ Month.create(months.JANUARY, 2015), '2014-12-29' ],
27 | [ Month.create(months.FEBRUARY, 2015), '2015-01-26' ],
28 | [ Month.create(months.JUNE, 2015), '2015-06-01' ],
29 | [ Month.create(months.FEBRUARY, 2016), '2016-02-01' ],
30 | [ Month.create(months.MARCH, 2016), '2016-02-29' ],
31 | ];
32 |
33 | cases.forEach(([ month, result ]) => {
34 | assert.equal(month.firstCalendarDay(days.MONDAY).format('YYYY-MM-DD'), result);
35 | });
36 | });
37 |
38 | it('with weeks starting on a a Wednesday', () => {
39 |
40 | const cases = [
41 | [ Month.create(months.JANUARY, 2015), '2014-12-31' ],
42 | [ Month.create(months.FEBRUARY, 2015), '2015-01-28' ],
43 | [ Month.create(months.JUNE, 2015), '2015-05-27' ],
44 | [ Month.create(months.FEBRUARY, 2016), '2016-01-27' ],
45 | [ Month.create(months.MARCH, 2016), '2016-02-24' ],
46 | ];
47 |
48 | cases.forEach(([ month, result ]) => {
49 | assert.equal(month.firstCalendarDay(days.WEDNESDAY).format('YYYY-MM-DD'), result);
50 | });
51 | });
52 | });
53 |
54 | describe('It generates an array of weeks for a calendar month', () => {
55 |
56 | it('with weeks starting on a Sunday (default)', () => {
57 |
58 | const cases = [
59 | [ Month.create(months.JANUARY, 2015), '2014-12-28', '2015-02-07' ],
60 | [ Month.create(months.FEBRUARY, 2015), '2015-02-01', '2015-03-14' ],
61 | [ Month.create(months.JUNE, 2015), '2015-05-31', '2015-07-11' ],
62 | [ Month.create(months.FEBRUARY, 2016), '2016-01-31', '2016-03-12' ],
63 | [ Month.create(months.MARCH, 2016), '2016-02-28', '2016-04-09' ],
64 | ];
65 |
66 | cases.forEach(([ month, start, end ]) => {
67 | const calendarDays = month.calendarDays(days.SUNDAY);
68 |
69 | assert.lengthOf(calendarDays, 42);
70 | assert.equal(calendarDays[0].format('YYYY-MM-DD'), start);
71 | assert.equal(calendarDays[41].format('YYYY-MM-DD'), end);
72 | });
73 | });
74 |
75 | it('with weeks starting on a Monday', () => {
76 |
77 | const cases = [
78 | [ Month.create(months.JANUARY, 2015), '2014-12-29', '2015-02-08' ],
79 | [ Month.create(months.FEBRUARY, 2015), '2015-01-26', '2015-03-08' ],
80 | [ Month.create(months.JUNE, 2015), '2015-06-01', '2015-07-12' ],
81 | [ Month.create(months.FEBRUARY, 2016), '2016-02-01', '2016-03-13' ],
82 | [ Month.create(months.MARCH, 2016), '2016-02-29', '2016-04-10' ],
83 | ];
84 |
85 | cases.forEach(([ month, start, end ]) => {
86 | const calendarDays = month.calendarDays(days.MONDAY);
87 |
88 | assert.lengthOf(calendarDays, 42);
89 | assert.equal(calendarDays[0].format('YYYY-MM-DD'), start);
90 | assert.equal(calendarDays[41].format('YYYY-MM-DD'), end);
91 | });
92 | });
93 | });
94 |
95 | describe('It generates an array of weeks for a calendar month', () => {
96 |
97 | it('with weeks starting on a Sunday (default)', () => {
98 |
99 | const cases = [
100 | [ Month.create(months.JANUARY, 2015), '2014-12-28', '2015-02-07' ],
101 | [ Month.create(months.FEBRUARY, 2015), '2015-02-01', '2015-03-14' ],
102 | [ Month.create(months.JUNE, 2015), '2015-05-31', '2015-07-11' ],
103 | [ Month.create(months.FEBRUARY, 2016), '2016-01-31', '2016-03-12' ],
104 | [ Month.create(months.MARCH, 2016), '2016-02-28', '2016-04-09' ],
105 | ];
106 |
107 | cases.forEach(([ month, start, end ]) => {
108 | const calendarWeeks = month.calendarWeeks(days.SUNDAY);
109 |
110 | assert.lengthOf(calendarWeeks, 6);
111 | assert.equal(calendarWeeks[0][0].format('YYYY-MM-DD'), start);
112 | assert.equal(calendarWeeks[5][6].format('YYYY-MM-DD'), end);
113 | });
114 | });
115 | });
116 |
117 | describe('It can check whether a month', () => {
118 |
119 | it('contains a day', () => {
120 |
121 | const cases = [
122 | [ new Month(months.SEPTEMBER, 2015), moment('2015-09-12') ],
123 | [ new Month(months.FEBRUARY, 1992), moment('1992-02-01') ],
124 | [ new Month(months.DECEMBER, 2019), moment('2019-12-30') ],
125 | ];
126 |
127 | cases.forEach(([ month, day ]) => {
128 | assert.isTrue(month.containsDay(day));
129 | });
130 | });
131 |
132 | it('doesn\'t contain a day', () => {
133 |
134 | const cases = [
135 | [ new Month(months.SEPTEMBER, 2015), moment('2014-09-12') ],
136 | [ new Month(months.FEBRUARY, 1992), moment('1992-01-31') ],
137 | [ new Month(months.DECEMBER, 2019), moment('2019-11-30') ],
138 | ];
139 |
140 | cases.forEach(([ month, day ]) => {
141 | assert.isFalse(month.containsDay(day));
142 | });
143 | });
144 |
145 | it('is the current month', () => {
146 |
147 | assert.isTrue(Month.thisMonth().isThisMonth());
148 | assert.isFalse(Month.nextMonth().isThisMonth());
149 | assert.isFalse(Month.lastMonth().isThisMonth());
150 | });
151 |
152 | it('is in the future', () => {
153 |
154 | assert.isTrue(Month.nextMonth().isFuture());
155 | assert.isTrue(Month.nextMonth().nextMonth().isFuture());
156 | assert.isFalse(Month.thisMonth().isFuture());
157 | assert.isFalse(Month.lastMonth().isFuture());
158 | assert.isFalse(Month.lastMonth().lastMonth().isFuture());
159 | });
160 |
161 | it('is in the past', () => {
162 |
163 | assert.isTrue(Month.lastMonth().isPast());
164 | assert.isTrue(Month.lastMonth().lastMonth().isPast());
165 | assert.isFalse(Month.thisMonth().isPast());
166 | assert.isFalse(Month.nextMonth().nextMonth().isPast());
167 | });
168 | });
169 |
--------------------------------------------------------------------------------
/src/month.js:
--------------------------------------------------------------------------------
1 | import { days } from './enums';
2 | import moment from 'moment';
3 | import validate from './validate';
4 |
5 | /**
6 | * @class Month
7 | *
8 | * @property {number} month
9 | * @property {number} year
10 | */
11 | export class Month {
12 | /**
13 | * Create a new instance of Month from a month and a year.
14 | *
15 | * @param {number} month - A number from 0 to 11.
16 | * @param {number} year - A valid year number.
17 | *
18 | * @return {Month}
19 | */
20 | constructor(month, year) {
21 |
22 | month = parseInt(month);
23 | year = parseInt(year);
24 |
25 | if (! validate.isValidMonthNumber(month)) {
26 | throw new Error('Argument `month` has to be a number between 0 and 11.');
27 | }
28 |
29 | if (! validate.isValidYearNumber(year)) {
30 | throw new Error('Argument `year` has to be a number.');
31 | }
32 |
33 | this.month = month;
34 | this.year = year;
35 | }
36 |
37 | /**
38 | * Return a fresh instance of the Month object.
39 | *
40 | * @return {Month}
41 | */
42 | clone() {
43 | return new Month(this.month, this.year);
44 | }
45 |
46 | /**
47 | * Create a new instance of Month, this essentially creates a clone of the
48 | * object.
49 | *
50 | * @return {Month}
51 | */
52 | thisMonth() {
53 | return this.clone();
54 | }
55 |
56 | /**
57 | * Create a new instance for the current month.
58 | *
59 | * @return {Month}
60 | */
61 | static now() {
62 | return Month.create(moment());
63 | }
64 |
65 | /**
66 | * Create a new instance for the current month.
67 | *
68 | * @return {Month}
69 | */
70 | static thisMonth() {
71 | return Month.now();
72 | }
73 |
74 | /**
75 | * Return a new Month instance, set one month later than now.
76 | *
77 | * @return {Month}
78 | */
79 | nextMonth() {
80 | return Month.create(this.moment().add(1, 'month'));
81 | }
82 |
83 | /**
84 | * Return a new Month instance, set one month later than now.
85 | *
86 | * @return {Month}
87 | */
88 | static nextMonth() {
89 | return Month.thisMonth().nextMonth();
90 | }
91 |
92 | /**
93 | * Return a new Month instance, set one month earlier than the current
94 | * instance.
95 | *
96 | * @return {Month}
97 | */
98 | lastMonth() {
99 | return Month.create(this.moment().add(-1, 'month'));
100 | }
101 |
102 | /**
103 | * Alias for `lastMonth`.
104 | *
105 | * @return {Month}
106 | */
107 | previousMonth() {
108 | return this.lastMonth();
109 | }
110 |
111 | /**
112 | * Return a new Month instance, set one month earlier than now.
113 | *
114 | * @return {Month}
115 | */
116 | static lastMonth() {
117 | return Month.thisMonth().lastMonth();
118 | }
119 |
120 | /**
121 | * Alias for `static lastMonth`.
122 | *
123 | * @return {Month}
124 | */
125 | static previousMonth() {
126 | return Month.lastMonth();
127 | }
128 |
129 | /**
130 | * Format the month's date. Uses ISO 8601, defaults to YYYY-MM.
131 | *
132 | * @param {string} format - The format you want to return.
133 | *
134 | * @return {number}
135 | */
136 | format(format = 'YYYY-MM') {
137 | return this.moment().format(format);
138 | }
139 |
140 | /**
141 | * Generate a new moment instance set to the first day of this month.
142 | * Since moment.js creates mutable instances, it's very important to always
143 | * return a new one here.
144 | *
145 | * @return {Moment}
146 | */
147 | moment() {
148 | return moment([this.year, this.month, 1]);
149 | }
150 |
151 | /**
152 | * Return a moment instance of the first day of the month.
153 | *
154 | * @return {Moment}
155 | */
156 | firstDay() {
157 | return this.moment();
158 | }
159 |
160 | /**
161 | * Return a moment instance of the first 'calendar day' of the month.
162 | *
163 | * @return {Moment}
164 | */
165 | firstCalendarDay(weekStartsOn = Month.weekStartsOn) {
166 | const firstDay = this.moment();
167 |
168 | while (firstDay.day() !== weekStartsOn) {
169 | firstDay.subtract(1, 'day');
170 | }
171 |
172 | return firstDay;
173 | }
174 |
175 | /**
176 | * Return an array of 'calendar days'. This array contains 42 days,
177 | * starting on a specific day of the week.
178 | *
179 | * @return {Moment[]}
180 | */
181 | calendarDays(weekStartsOn = Month.weekStartsOn) {
182 | const current = this.firstCalendarDay(weekStartsOn);
183 |
184 | const days = [];
185 |
186 | while (days.length < 42) {
187 | days.push(current.clone());
188 | current.add(1, 'day');
189 | }
190 |
191 | return days;
192 | }
193 |
194 | /**
195 | * Return an array of 'calendar weeks'. This array contains 6 weeks, with
196 | * days starting on a specific day of the week.
197 | *
198 | * @return {Moment[][]}
199 | */
200 | calendarWeeks(weekStartsOn = Month.weekStartsOn) {
201 | const weeks = [];
202 |
203 | for (let i = 0; i < 6; i++) {
204 | weeks.push(this.calendarDays(weekStartsOn).slice(i * 7, i * 7 + 7));
205 | }
206 |
207 | return weeks;
208 | }
209 |
210 | /**
211 | * Creates a new instance of Month from various formats.
212 | *
213 | * - {number}, {number}: creates a Month from a month and year number.
214 | * - {string}: creates a Month from a string which is equal to 'YYYY-MM'
215 | * or starts with 'YYYY-MM-'.
216 | * - {Moment}: creates a Month for the month in which the Moment instance resides.
217 | *
218 | * @param {(number|string|Moment)} argument
219 | * @param {?number} year
220 | *
221 | * @return {Month}
222 | */
223 | static create() {
224 | if (arguments.length > 2) {
225 | throw new Error('`Month.create()` can only accept zero, one or two arguments.');
226 | }
227 |
228 | if (arguments.length === 2) {
229 | return new Month(...arguments);
230 | }
231 |
232 | if (arguments.length === 1) {
233 |
234 | const argument = arguments[0];
235 |
236 | if (validate.isString(argument)) {
237 | const dateParts = argument.split('-');
238 |
239 | return new Month(dateParts[1] - 1, dateParts[0]);
240 | }
241 |
242 | if (validate.isMoment(argument)) {
243 | return new Month(argument.month(), argument.year());
244 | }
245 |
246 | if (validate.isDate(argument)) {
247 | return new Month(argument.getMonth(), argument.getFullYear());
248 | }
249 |
250 | throw new Error('Invalid argument specified for `Month.create()`.');
251 | }
252 |
253 | return Month.thisMonth();
254 | }
255 |
256 | /**
257 | * Check whether a day in the form of a Moment instance is in this month.
258 | *
259 | * @param {Moment} day
260 | *
261 | * @return {bool}
262 | */
263 | containsDay(day) {
264 | return (
265 | day.month() === this.month &&
266 | day.year() === this.year
267 | );
268 | }
269 |
270 | /**
271 | * Check whether a day in the form of a Moment instance is not in this month.
272 | *
273 | * @param {Moment} day
274 | *
275 | * @return {bool}
276 | */
277 | doesntContainDay(day) {
278 | return ! this.containsDay(day);
279 | }
280 |
281 | /**
282 | * Check whether the month instance is the current month in time.
283 | *
284 | * @return {bool}
285 | */
286 | isThisMonth() {
287 | return this.containsDay(moment());
288 | }
289 |
290 | /**
291 | * Check whether the month is in the future. Note: the current month will never be in the future.
292 | *
293 | * @return {bool}
294 | */
295 | isFuture() {
296 | return this.moment().diff(Month.now().nextMonth().moment(), 'months') > -1;
297 | }
298 |
299 | /**
300 | * Check whether the month is in the future. Note: the current month will never be in the future.
301 | *
302 | * @return {bool}
303 | */
304 | isPast() {
305 | return this.moment().diff(Month.now().lastMonth().moment(), 'months') < 1;
306 | }
307 | }
308 |
309 | /**
310 | * Default setting for the first day of the week.
311 | *
312 | * @type {number} - A number from 0 to 6, 0 being Sunday.
313 | */
314 | Month.weekStartsOn = days.SUNDAY;
315 |
316 | export default Month;
317 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | [
](https://supportukrainenow.org)
3 |
4 | # calendar-months
5 |
6 | [](https://npmjs.com/package/calendar-months)
7 | [](LICENSE.md)
8 | [](https://travis-ci.org/spatie/calendar-months)
9 | [](https://img.shields.io/codeclimate/github/spatie/calendar-months.svg)
10 |
11 | Provides a `Month` class with specialized functions for generating calendar user interfaces.
12 |
13 | Calendar UI's generally start each month on the same day of the week, and keep months at the same visual height every month. If a month starts on a Wednesday, you'll probably want to hide or gray out the Monday and Tuesday before. This package provides an unopinionated class to help you build these interfaces.
14 |
15 | A calendar month always has exactly 42 days, or 6 weeks.
16 |
17 | ```
18 | |------------------------------------------------|
19 | | December 2015 |
20 | | M | T | W | T | F | S | S |
21 | |------------------------------------------------|
22 | | 30* | 1 | 2 | 3 | 4 | 5 | 6 |
23 | | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
24 | | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
25 | | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
26 | | 28 | 29 | 30 | 31 | 1* | 2* | 3* |
27 | | 4* | 5* | 6* | 7* | 8* | 9* | 10* |
28 | |------------------------------------------------|
29 |
30 | *: Gray out or hide
31 | ```
32 |
33 | ```js
34 | import Month from 'calendar-months';
35 |
36 | const january = new Month('2015-01');
37 |
38 | const days = january.calendarDays();
39 | // => An array of all days that could be visible on the calendar (always 42 days)
40 |
41 | const weeks = january.calendarWeeks();
42 | // => An array of 6 arrays, each containing 7 days
43 | ```
44 |
45 | The `examples` folder in this repository contains examples of React and Vue implementations.
46 |
47 | Spatie is a webdesign agency based in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource).
48 |
49 | ## Support us
50 |
51 | [
](https://spatie.be/github-ad-click/calendar-months)
52 |
53 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us).
54 |
55 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards).
56 |
57 | ## Postcardware
58 |
59 | You're free to use this package (it's [MIT-licensed](LICENSE.md)), but if it makes it to your production environment you are required to send us a postcard from your hometown, mentioning which of our package(s) you are using.
60 |
61 | Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium.
62 |
63 | The best postcards will get published on the open source page on our website.
64 |
65 | ## Install
66 |
67 | You can install the package via npm:
68 |
69 | ```bash
70 | $ npm install calendar-months
71 | ```
72 |
73 | ## Examples
74 |
75 | The `examples` directory in this repository contains Vue and a React example calendars.
76 |
77 | To open an example, navigate to it's directory to run `npm install` and `npm run build`, then open the `index.html` file in your browser.
78 |
79 | ## Usage
80 |
81 | ### Creating Month Instances
82 |
83 | #### From Integers
84 |
85 | ```js
86 | import Month from 'calendar-months';
87 |
88 | new Month(0, 2016);
89 | // => January 2016
90 | ```
91 |
92 | Since javascript uses a 0-based index for months, the package also ships with a set of enums to improve clarity when dealing with month numbers.
93 |
94 | ```js
95 | import Month, { months } from 'calendar-months';
96 |
97 | new Month(months.JANUARY, 2016);
98 | // => January 2016
99 | ```
100 |
101 | The `Month.create` method also accepts integers to create a `Month` object.
102 |
103 | ```js
104 | import Month from 'calendar-months';
105 |
106 | Month.create(0, 2016);
107 | // => January 2016
108 | ```
109 |
110 | #### From Strings
111 |
112 | The `Month.create` method accepts a string in the format of `YYYY-MM[-...]`.
113 |
114 | ```js
115 | import Month from 'calendar-months';
116 |
117 | Month.create('2016-01');
118 | // => January 2016
119 |
120 | Month.create('2016-02-04');
121 | // => February 2016
122 | ```
123 |
124 | #### From a Moment or Date object
125 |
126 | The `Month.create` method accepts `Moment` and Date objects.
127 |
128 | ```js
129 | import moment from 'moment';
130 | import Month from 'calendar-months';
131 |
132 | Month.create(moment());
133 | // => This month
134 |
135 | Month.create(new Date());
136 | // => This month
137 | ```
138 |
139 | #### In a Relative Point in Time
140 |
141 | There are a few factory methods to create `Month` instances for this month, the previous month and the next month.
142 |
143 | ```js
144 | import Month from 'calendar-months';
145 |
146 | Month.create();
147 | Month.now();
148 | Month.thisMonth();
149 | // => This month
150 |
151 | Month.lastMonth();
152 | Month.previousMonth();
153 | // => The previous month
154 |
155 | Month.nextMonth();
156 | // => The next month
157 | ```
158 |
159 | `thisMonth`, `lastMonth`, `previousMonth` and `nextMonth` can also be used on existing `Month` instances. These methods are immutable, and return a new instance of the object.
160 |
161 | ```js
162 | import Month from 'calendar-months';
163 |
164 | const june = Month.create('2016-06');
165 |
166 | const may = june.lastMonth();
167 | const july = june.nextMonth();
168 | ```
169 |
170 | ### Retrieving Days and Weeks
171 |
172 | To retrieve all the weeks in a calendar month, there's a `calendarWeeks` method. `calendarWeeks` returns an array of 6 arrays, each containing 7 days. A day is a moment object with the time set to `00:00:00`.
173 |
174 | ```js
175 | import Month from 'calendar-months';
176 |
177 | const weeksInJune = Month.create('2016-06').calendarWeeks();
178 |
179 | // => [ [ sun, mon, tue, wed, thu, fri, sat ], ... ]
180 | ```
181 |
182 | If you want your calendars weeks to start on a different day, you can pass in a day as the first parameter. By default, a week starts on Sunday. Since javascript uses a 0-based index for days, the package also ships with a set of enums to improve clarity when dealing with day numbers.
183 |
184 | ```js
185 | import Month, { days } from 'calendar-months';
186 |
187 | const weeksInJune = Month.create('2016-06').calendarWeeks(days.MONDAY);
188 |
189 | // => [ [ mon, tue, wed, thu, fri, sat, sun ], ... ]
190 | ```
191 |
192 | If you want to retrieve all days without chunking them by week, there's a `calendarDays` method. This method also optionally accepts a starting day as it's first parameter.
193 |
194 | ```js
195 | import Month, { days } from 'calendar-months';
196 |
197 | const daysInJune = Month.create('2016-06').calendarDays(days.MONDAY);
198 |
199 | // => [ mon, tue, wed, thu, fri, sat, sun, mon, tue, wed, ... ]
200 | ```
201 |
202 | ### Additional Methods
203 |
204 | #### Checking Months
205 |
206 | There are three methods to check the position of a month compared to the current time:
207 |
208 | - `isThisMonth`
209 | - `isFuture`
210 | - `isPast`
211 |
212 | ```js
213 | // Considering it's currently June 2016...
214 |
215 | import Month from 'calendar-months';
216 |
217 | const june = Month.create('2016-06');
218 |
219 | june.isThisMonth(); // => true
220 | june.isPast(); // => false
221 | june.isFuture(); // => false
222 |
223 | const may = Month.create('2016-05');
224 |
225 | may.isPast(); // => true
226 |
227 | const july = Month.create('2016-07');
228 |
229 | july.isFuture(); // => true
230 | ```
231 |
232 | #### Checking Days in a Month
233 |
234 | By providing a `moment` object of a date, you can check if that date is part of the `Month` instance with `containsDay` and `doesntContainDay`.
235 |
236 | ```js
237 | import Month from 'calendar-months';
238 |
239 | const june = Month.create('2016-06');
240 |
241 | june.containsDay(moment('2016-06-23')); // => true
242 | june.containsDay(moment('2016-04-03')); // => false
243 |
244 | june.doesntContainDay(moment('2016-06-23')); // => false
245 | june.doesntContainDay(moment('2016-04-03')); // => true
246 | ```
247 |
248 | ## Change log
249 |
250 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
251 |
252 | ## Testing
253 |
254 | This package is fully tested with `mocha` and `chai`. To run the tests, use the npm script:
255 |
256 | ``` bash
257 | $ npm run test
258 | ```
259 |
260 | ## Contributing
261 |
262 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details.
263 |
264 | ## Security
265 |
266 | If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker.
267 |
268 | ## Credits
269 |
270 | - [Sebastian De Deyne](https://github.com/sebastiandedeyne)
271 | - [All Contributors](../../contributors)
272 |
273 | ## About Spatie
274 | Spatie is a webdesign agency based in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource).
275 |
276 | ## License
277 |
278 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
279 |
--------------------------------------------------------------------------------