Skip to content

Instantly share code, notes, and snippets.

@PanJarda
Created March 20, 2025 22:10
Show Gist options
  • Select an option

  • Save PanJarda/e10b296692c09a38e6b44a33d62f29ec to your computer and use it in GitHub Desktop.

Select an option

Save PanJarda/e10b296692c09a38e6b44a33d62f29ec to your computer and use it in GitHub Desktop.
(function (Number) {
'use strict';
Number.prototype.mul = function(num) {
return this.valueOf() * num;
};
Number.prototype.div = function(num) {
return this.valueOf() / num;
};
Number.prototype.floor = function() {
return Math.floor(this.valueOf());
};
Number.prototype.ceil = function() {
return Math.ceil(this.valueOf());
};
Number.prototype.plus = function(num) {
return this.valueOf() + num;
};
Number.prototype.minus = function(num) {
return this.valueOf() - num;
};
Number.prototype.inv = function() {
return this.valueOf().mul(-1);
};
Number.prototype.mod = function(num) {
if (this.valueOf() < 0) {
return num + (this.valueOf() % num);
} else {
return this.valueOf() % num;
}
};
Number.prototype.eq = function(num) {
return this.valueOf() === num;
};
Number.prototype.lt = function(num) {
return this.valueOf() < num;
};
Number.prototype.gt = function(num) {
return this.valueOf() > num;
};
Number.prototype.lte = function(num) {
return this.valueOf() <= num;
};
Number.prototype.gte = function(num) {
return this.valueOf() >= num;
};
Number.prototype.pow = function(num) {
return Math.pow(this.valueOf(), num);
};
Number.prototype.to = function(num) {
return new Range(this.valueOf(), num);
};
})(Number);
(function (Boolean) {
'use strict';
Boolean.prototype.and = function(pred) {
return this.valueOf() && pred;
};
Boolean.prototype.or = function(pred) {
return this.valueOf() || pred;
};
Boolean.prototype.not = function(pred) {
return !this.valueOf();
};
})(Boolean);
var Range = (function () {
'use strict';
/******************************************************************
* @constructor
* @param {*} from
* @param {*} to
*/
function Range(from, to) {
this.from = from;
this._by = 1;
this.to = to;
}
Range.prototype.toArray = function() {
return this.map(function(i) { return i });
};
Range.prototype.map = function(callback, self) {
var to = this.to;
var res = [];
for (var i = this.from; i <= to; i += this._by) {
res.push(callback.call(self, i));
}
return res;
};
Range.prototype.fill = function(value) {
return this.map(function(x) {
return value;
});
};
Range.prototype.by = function(num) {
this._by = num;
return this;
};
Range.prototype.isBetween = function(num) {
return (this.from <= num) && (num <= this.to);
};
return Range;
})();
var GregorianCalendar = (function () {
'use strict';
/******************************************************************
* @constructor
* @param {number} num
*/
function Year(num) {
this._num = num;
}
Year.fromDaysInEra = function(days) {
var d4 = (365).mul(4).plus(1),
d100 = d4.mul(25).minus(1),
d400 = d100.mul(4).plus(1);
var m400 = days.mod(d400);
var m100 = m400.mod(d100);
var m4 = m100.mod(d4);
var n400 = days.div(d400).floor();
var n100 = m400.div(d100).floor();
var n4 = m100.div(d4).floor();
var n1 = m4.div(365).floor();
return new Year(
n400.mul(400)
.plus(n100.mul(100))
.plus(n4.mul(4))
.plus(n1)
);
};
Year.prototype.leapYearsPassed = function() {
var year = this._num;
return year.div(4).floor()
.minus(year.div(100).floor())
.plus(year.div(400).floor());
};
Year.prototype.daysPassedInEra = function() {
return this._num.mul(365).plus(this.leapYearsPassed());
};
Year.prototype.firstDay = function() {
return new Day(new Month(this, 1), 1);
};
Year.prototype.lastDay = function() {
return new Day(new Month(this, 12), this.isLeap() ? 366 : 365)
};
Year.prototype.next = function() {
return new this.constructor(this._num.plus(1));
};
Year.prototype.prev = function() {
return new this.constructor(this._num.minus(1));
};
Year.prototype.plus = function(num) {
return new this.constructor(this._num.plus(num));
};
Year.prototype.minus = function(num) {
return new this.constructor(this._num.minus(num));
};
Year.prototype.isLeap = function() {
var y = this._num;
return !!((0).pow(y.mod(4))
.minus((0).pow(y.mod(100)))
.plus((0).pow(y.mod(400))));
};
Year.prototype.print = function(template) {
return template.with('yyyy', this._num);
};
Year.prototype.num = function() {
return this._num;
};
Year.prototype.eq = function(year) {
return this._num.eq(year.num());
};
Year.prototype.gt = function(year) {
return this._num.gt(year.num());
};
Year.prototype.lt = function(year) {
return this._num.lt(year.num());
};
Year.prototype.gte = function(year) {
return this._num.gte(year.num());
};
Year.prototype.lte = function(year) {
return this._num.lte(year.num());
};
Year.prototype.month = function(i) {
return new Month(this, i);
};
Year.prototype.months = function() {
return (1).to(12).map(this.month, this);
};
/******************************************************************
* @constructor
* @param {Year} year
* @param {number} num
*/
function Month(year, num) {
this._year = year;
this._num = num;
}
Month.prototype._monthLengths = [
[31,28,31,30,31,30,31,31,30,31,30,31],
[31,29,31,30,31,30,31,31,30,31,30,31]
];
Month.prototype._daysPassed = [
[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334],
[0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]
];
Month.fromDaysInEra = function(days) {
var year = Year.fromDaysInEra(days);
var daysInYear = days.minus(year.daysPassedInEra());
var month = Month.prototype._daysPassed[year.isLeap() ? 1 : 0].findIndex(function(m) {
return daysInYear <= m;
});
return new Month(year, month);
};
Month.prototype.year = function() {
return this._year;
};
Month.prototype.length = function() {
return this._monthLengths[this._year.isLeap() ? 1 : 0][this._num - 1];
};
Month.prototype.next = function() {
if (this._num.eq(12)) {
return new this.constructor(this._year.next(), 1);
} else {
return new this.constructor(this._year, this._num.plus(1));
}
};
Month.prototype.prev = function() {
if (this._num.eq(1)) {
return new this.constructor(this._year.prev(), 12);
} else {
return new this.constructor(this._year, this._num.minus(1));
}
};
Month.prototype.plus = function(num) {
var years = this._num.minus(1).plus(num).div(12).floor();
var month = this._num.minus(1).plus(num).mod(12).plus(1);
return new this.constructor(this._year.plus(years), month);
};
Month.prototype.minus = function(num) {
return this.plus(num.inv());
};
Month.prototype.print = function(template) {
return this._year.print(template)
.with('MM', this._num);
};
Month.prototype.firstDay = function() {
return new Day(this, 1);
};
Month.prototype.lastDay = function() {
return new Day(this, this.length());
};
Month.prototype.day = function(n) {
return new Day(this, n);
};
Month.prototype.num = function() {
return this._num;
};
Month.prototype.daysPassedInYear = function() {
return this._daysPassed[this._year.isLeap() ? 1 : 0][this._num - 1];
};
Month.prototype.daysPassedInEra = function() {
return this._year.daysPassedInEra().plus(this.daysPassedInYear());
};
Month.prototype.eq = function(month) {
return this._num.eq(month.num())
.and(this._year.eq(month.year()));
};
Month.prototype.days = function() {
return (1).to(this.length()).map(this.day, this);
};
Month.prototype.gt = function(month) {
return this._year.gt(month.year())
.or(this._year.eq(month.year())
.and(this._num.gt(month.num())));
};
Month.prototype.gte = function(month) {
return this.gt(month).or(this.eq(month));
};
Month.prototype.lt = function(month) {
return this._year.lt(month.year())
.or(this._year.eq(month.year())
.and(this._num.lt(month.num())));
};
Month.prototype.lte = function(month) {
return this.lt(month).or(this.eq(month));
};
Month.prototype.toNative = function() {
return new Date(this._year.num(), this._num);
};
/******************************************************************
* @constructor
* @param {Month} month
* @param {number} num
*/
function Day(month, num) {
if (num > month.length()) {
throw 'Invalid date';
}
this._month = month;
this._num = num;
}
Day.fromDaysInEra = function(days) {
var month = Month.fromDaysInEra(days);
var day = days.minus(month.daysPassedInEra());
return new Day(month, day);
};
Day.today = function() {
var date = new Date();
return new Day(
new Month(
new Year(date.getFullYear()),
date.getMonth() + 1), date.getDate()
);
};
Day.prototype.month = function() {
return this._month;
};
Day.prototype.year = function() {
return this._month.year();
};
Day.prototype.plus = function(n) {
return Day.fromDaysInEra(this.daysPassedInEra().plus(n));
};
Day.prototype.minus = function(n) {
return this.plus(n.inv());
};
Day.prototype.next = function() {
if (this.eq(this._month.lastDay())) {
return new this.constructor(this._month.next(), 1);
} else {
return new this.constructor(this._month, this._num.plus(1));
}
};
Day.prototype.prev = function() {
if (this.eq(this._month.firstDay())) {
var prevMonth = this._month.prev();
return new this.constructor(prevMonth, prevMonth.length());
} else {
return new this.constructor(this._month, this._num.minus(1));
}
};
Day.prototype.print = function(template) {
return this._month.print(template)
.with('dd', this._num);
};
Day.prototype.dayOfWeek = function() {
return new DayOfWeek(this.daysPassedInEra().mod(7).minus(1));
};
Day.prototype.daysPassedInYear = function() {
return this._month.daysPassedInYear().plus(this._num);
};
Day.prototype.daysPassedInEra = function() {
return this._month.daysPassedInEra().plus(this._num);
};
Day.prototype.num = function() {
return this._num;
};
Day.prototype.eq = function(day) {
return this._num.eq(day.num())
.and(this._month.eq(day.month()));
};
Day.prototype.gt = function(day) {
return this._month.gt(day.month())
.or(this._month.eq(day.month())
.and(this._num.gt(day.num())));
};
Day.prototype.lt = function(day) {
return this._month.lt(day.month())
.or(this._month.eq(day.month())
.and(this._num.lt(day.num())));
};
Day.prototype.gte = function(day) {
return this.gt(day).or(this.eq(day));
};
Day.prototype.lte = function(day) {
return this.lt(day).or(this.eq(day));
};
Day.prototype.toNative = function() {
return new Date(this.year().num(), this._month.num(), this._num);
};
/******************************************************************
* @constructor
*/
function DayOfWeek(num) {
this._num = num;
}
DayOfWeek.Sunday = new DayOfWeek(0);
DayOfWeek.Monday = new DayOfWeek(1);
DayOfWeek.Tuesday = new DayOfWeek(2);
DayOfWeek.Wednesday = new DayOfWeek(3);
DayOfWeek.Thursday = new DayOfWeek(4);
DayOfWeek.Friday = new DayOfWeek(5);
DayOfWeek.Saturday = new DayOfWeek(6);
DayOfWeek.prototype.eq = function(dow) {
return this._num.eq(dow.num());
};
DayOfWeek.prototype.num = function() {
return this._num;
};
DayOfWeek.prototype.next = function() {
return new this.constructor(this._num.plus(1).mod(7));
};
DayOfWeek.prototype.prev = function() {
return new this.constructor(this._num.minus(1).mod(7));
};
DayOfWeek.prototype.plus = function(n) {
return new this.constructor(this._num.plus(n).mod(7));
};
DayOfWeek.prototype.minus = function(n) {
return new this.constructor(this._num.minus(n).mod(7));
};
DayOfWeek.prototype.print = function(template) {
return template.with('w', this._num);
};
return {
Year: Year,
Month: Month,
Day: Day,
DayOfWeek: DayOfWeek
};
})();
var Datepicker = (function(h, Component, Day, DayOfWeek) {
'use strict';
/******************************************************************
* @constructor
* @param {*} props
*/
function Datepicker(props) {
Component.call(this, props);
var today = Day.today();
this.today = today;
this.state = {
month: today.month(),
start: null,
selecting: 'start',
end: null,
hovered: null
};
this.nextMonth = this.nextMonth.bind(this);
this.prevMonth = this.prevMonth.bind(this);
this.selectDay = this.selectDay.bind(this);
this.selectStartOrEnd = this.selectStartOrEnd.bind(this);
this.reset = this.reset.bind(this);
this.hoverDay = this.hoverDay.bind(this);
}
Datepicker.prototype = Object.create(Component.prototype);
Datepicker.prototype.nextMonth = function() {
this.setState({
month: this.state.month.next()
});
};
Datepicker.prototype.prevMonth = function() {
this.setState({
month: this.state.month.prev()
});
};
Datepicker.prototype.selectDay = function(day) {
var selecting = this.state.selecting;
var start = this.state.start;
var end = this.state.end;
if (selecting === 'start') {
this.setState({
start: day,
end: end ? day.gt(end) ? null : end : null,
selecting: 'end'
});
} else {
if (start && day.lt(start)) {
this.setState({
start: day,
selecting: 'end'
});
} else {
this.setState({
end: day
});
}
}
};
Datepicker.prototype.hoverDay = function(day) {
var start = this.state.start;
if (start && day.gt(start)) {
this.setState({
hovered: day
});
} else if (this.state.hovered) {
this.setState({
hovered: null
});
}
};
Datepicker.prototype.selectStartOrEnd = function(startOrEnd) {
this.setState({
selecting: startOrEnd
});
};
Datepicker.prototype.reset = function() {
this.setState({
start: null,
end: null,
selecting: 'start',
hovered:null,
month: this.today.month()
});
}
Datepicker.prototype.render = function() {
var state = this.state;
return (
h(this.props.view, {
today: this.today,
start: state.start,
end: state.end,
hovered: state.hovered,
hoverDay: this.hoverDay,
month: state.month,
selecting: state.selecting,
selectStartOrEnd: this.selectStartOrEnd,
nextMonth: this.nextMonth,
prevMonth: this.prevMonth,
selectDay: this.selectDay,
firstDayOfWeek: 'firstDayOfWeek' in this.props ? this.props.firstDayOfWeek : DayOfWeek.Sunday,
reset: this.reset
})
)
};
return Datepicker;
})(preact.h, preact.Component, GregorianCalendar.Day, GregorianCalendar.DayOfWeek);
/* DatepickerView =================================================*/
var DatepickerView = (function(h, Component) {
'use strict';
/******************************************************************
* @constructor
*/
function Template() {}
Template.prototype.with = function(key, num) {
this[key] = num;
return this;
};
Template.prototype.toString = function() {
var res = '';
if ('w' in this) {
res += ['Ne', 'Po', 'Út', 'St', 'Čt', 'Pá', 'So'][this['w']];
}
if ('dd' in this) {
res += this['dd'] + '. ';
}
if ('MM' in this) {
res += [
'Leden',
'Únor',
'Březen',
'Duben',
'Květen',
'Červen',
'Červenec',
'Srpen',
'Září',
'Říjen',
'Listopad',
'Prosinec'
][this['MM'] - 1];
}
if ('yyyy' in this) {
res += ' ' + this['yyyy'];
}
return res;
};
/******************************************************************
* @constructor
* @param {*} props
*/
function DatepickerView(props) {
Component.call(this, props);
this.hover = this.hover.bind(this);
}
DatepickerView.prototype = Object.create(Component.prototype);
DatepickerView.prototype.hover = function(day) {
var props = this.props;
var self = this;
if (this.timeout) {
return;
}
this.timeout = setTimeout(function() {
if (!(props.hovered && props.hovered.eq(day))) {
props.hoverDay(day);
}
self.timeout = null;
}, 5);
};
DatepickerView.prototype.renderDay = function(day) {
var props = this.props;
var start = props.start;
var end = props.end;
var today = props.today;
var hovered = props.hovered;
var hover = this.hover;
if (!day) {
return h('td', {className: 'Datepicker-day'});
}
if (day.lt(today)) {
return h('td', {className: 'Datepicker-day is-disabled'}, day.num());
}
if (start && day.eq(start)) {
return h('td', {className: 'Datepicker-day is-selected is-start'},
h('span', {className: 'Datepicker-day-circle is-selected'}, day.num()));
}
if (end && day.eq(end)) {
return h('td', {className: 'Datepicker-day is-selected is-end'},
h('span', {className: 'Datepicker-day-circle is-selected'}, day.num()));
}
if (start && end && day.gt(start) && day.lt(end)) {
return h('td', {className: 'Datepicker-day is-inRange',
onMouseDown: function() { props.selectDay(day) }}, day.num())
}
if (start && hovered && !end && day.gt(start) && day.lt(hovered)) {
return h('td', {className: 'Datepicker-day is-inRange',
onMouseDown: function() { props.selectDay(day) },
onMouseOver: function() {hover(day)}}, day.num())
}
if (start && hovered && !end && day.eq(hovered)) {
return h('td', {className: 'Datepicker-day is-end',
onMouseDown: function() { props.selectDay(day) },
onMouseOver: function() {hover(day)}}, day.num())
}
if (start && !end) {
return h('td', {
className: 'Datepicker-day',
onMouseDown: function() {props.selectDay(day)},
onMouseOver: function() {hover(day)}
},
day.num()
);
}
return h('td', {
className: 'Datepicker-day',
onMouseDown: function() {props.selectDay(day)}
},
h('span', {className: 'Datepicker-day-circle' }, day.num())
);
};
DatepickerView.prototype.renderWeek = function(week) {
return (
h('tr', null,
week.map(this.renderDay, this)
)
);
};
DatepickerView.prototype.renderMonth = function(month, weeks) {
var firstDayOfWeek = this.props.firstDayOfWeek;
return (
h('table', {className: 'Datepicker-month'},
h('caption', null, month.print(new Template).toString()),
h('thead', null,
h('tr', null, (0).to(6).map(function(i) {
return h('th', null, firstDayOfWeek.plus(i).print(new Template).toString());
}))
),
h('tbody', null,
weeks.map(this.renderWeek, this)
)
)
);
};
// TODO rozpadnout do komponent
DatepickerView.prototype.render = function() {
var props = this.props;
var month = this.props.month;
var month2 = month.next();
var pad = month.firstDay().dayOfWeek().num().minus(props.firstDayOfWeek.num()).mod(7);
var days = (1).to(pad).fill(0)
.concat(month.days());
var weeks = [
days.slice(0, 7),
days.slice(7, 14),
days.slice(14, 21),
days.slice(21, 28),
days.slice(28, 35),
days.slice(35, 42)
];
var pad2 = month2.firstDay().dayOfWeek().num(props.firstDayOfWeek.num()).minus(1).mod(7);
var days2 = (1).to(pad2).fill(0)
.concat(month2.days());
var weeks2 = [
days2.slice(0, 7),
days2.slice(7, 14),
days2.slice(14, 21),
days2.slice(21, 28),
days2.slice(28, 35),
days2.slice(35, 42)
];
return (
h('div', {className: 'Datepicker'},
h('div', null,
h('button', {onClick: props.prevMonth}, ' < '),
h('input', {
id: 'selectStart',
type: 'radio',
value: 'start',
checked: props.selecting === 'start',
onChange: function(e) {
props.selectStartOrEnd(e.target.value)
}
}),
h('label', {htmlFor: 'selectStart'}, (props.start ? props.start.print(new Template).toString() : '')),
' - ',
h('input', {
id: 'selectEnd',
type: 'radio',
value: 'end',
checked: props.selecting === 'end',
onChange: function(e) {
props.selectStartOrEnd(e.target.value)
}
}),
h('label', {htmlFor: 'selectEnd'}, (props.end ? props.end.print(new Template).toString() : '')),
h('button', {onClick: props.reset}, 'reset'),
h('button', {onClick: props.nextMonth}, ' > ')
),
h('hr'),
this.renderMonth(month, weeks),
this.renderMonth(month2, weeks2)
)
);
};
return DatepickerView;
})(preact.h, preact.Component);
/* App ============================================================*/
(function(window, h, Datepicker, DatepickerView, DayOfWeek) {
'use strict';
window.addEventListener('DOMContentLoaded', function() {
preact.render(
h(Datepicker, {
view: DatepickerView,
firstDayOfWeek: DayOfWeek.Monday
}),
document.getElementById('app')
)
});
})(window, preact.h, Datepicker, DatepickerView, GregorianCalendar.DayOfWeek);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment