This commit is contained in:
Fenris Wolf 2024-09-08 21:31:17 +02:00
commit bd1626f3a6
4 changed files with 951 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/build/
/temp/

914
source/main.ts Normal file
View file

@ -0,0 +1,914 @@
declare var process : any;
type int = number;
/**
*/
function date_object_get_week_of_year(
date : Date
) : int
{
let date_ : Date = new Date(date.getTime());
date_.setHours(0, 0, 0, 0);
// Thursday in current week decides the year.
date_.setDate(date_.getDate() + 3 - (date_.getDay() + 6) % 7);
// January 4 is always in week 1.
let week1 : Date = new Date(date_.getFullYear(), 0, 4);
// Adjust to Thursday in week 1 and count number of weeks from date to week1.
return (
1
+
Math.round(
(
((date_.getTime() - week1.getTime()) / 86400000)
-
3
+
(week1.getDay() + 6) % 7
)
/
7
)
);
}
type type_date = {
year : int;
month : int;
day : int;
};
type type_time = {
hour : int;
minute : int;
second : int;
};
type type_datetime = {
timezone : string;
date : type_date;
time : (
null
|
type_time
);
};
type type_pit = int;
/**
*/
function pit_to_date_object(
pit : type_pit
) : Date
{
return (new Date(pit * 1000));
}
/**
*/
function pit_from_date_object(
date_object : Date
) : type_pit
{
return Math.round(date_object.getTime() / 1000);
}
/**
*/
function pit_now(
) : type_pit
{
return pit_from_date_object(new Date(Date.now()));
}
/**
*/
function pit_to_datetime(
pit : type_pit
) : type_datetime
{
const date_object : Date = pit_to_date_object(pit);
return {
"timezone": "Europe/Berlin", // TODO
"date": {
"year": date_object.getFullYear(),
"month": (date_object.getMonth() + 1),
"day": (date_object.getDate()),
},
"time": {
"hour": date_object.getHours(),
"minute": date_object.getMinutes(),
"second": date_object.getSeconds(),
},
};
}
/**
*/
function pit_from_datetime(
datetime : type_datetime
) : type_pit
{
// TODO: timezone
const date_object : Date = new Date(
datetime.date.year,
(datetime.date.month - 1), // TODO
datetime.date.day,
((datetime.time === null) ? 0 : datetime.time.hour),
((datetime.time === null) ? 0 : datetime.time.minute),
((datetime.time === null) ? 0 : datetime.time.second)
);
return pit_from_date_object(date_object);
}
/**
*/
function pit_is_before(
pit : type_pit,
reference : type_pit
) : boolean
{
return (pit < reference);
}
/**
*/
function pit_is_after(
pit : type_pit,
reference : type_pit
) : boolean
{
return (pit > reference);
}
/**
*/
function pit_is_between(
pit : type_pit,
reference_left : type_pit,
reference_right : type_pit
) : boolean
{
return (
pit_is_after(pit, reference_left)
&&
pit_is_before(pit, reference_right)
);
}
/**
*/
function pit_shift_hour(
pit : type_pit,
increment : int
) : type_pit
{
return (pit + (60 * 60 * increment));
}
/**
*/
function pit_shift_day(
pit : type_pit,
increment : int
) : type_pit
{
return (pit + (60 * 60 * 24 * increment));
}
/**
*/
function pit_shift_week(
pit : type_pit,
increment : int
) : type_pit
{
return (pit + (60 * 60 * 24 * 7 * increment));
}
/**
*/
function pit_shift_year(
pit : type_pit,
increment : int
) : type_pit
{
return (pit + (60 * 60 * 24 * 365 * increment));
}
/**
*/
function pit_trunc_minute(
pit : type_pit
) : type_pit
{
const datetime_input : type_datetime = pit_to_datetime(pit);
const datetime_output : type_datetime = {
"timezone": "Europe/Berlin", // TODO
"date": {
"year": datetime_input.date.year,
"month": datetime_input.date.month,
"day": datetime_input.date.day,
},
"time": {
"hour": ((datetime_input.time === null) ? 0 : datetime_input.time.hour),
"minute": ((datetime_input.time === null) ? 0 : datetime_input.time.minute),
"second": 0,
},
};
return pit_from_datetime(datetime_output);
}
/**
*/
function pit_trunc_hour(
pit : type_pit
) : type_pit
{
const datetime_input : type_datetime = pit_to_datetime(pit);
const datetime_output : type_datetime = {
"timezone": "Europe/Berlin", // TODO
"date": {
"year": datetime_input.date.year,
"month": datetime_input.date.month,
"day": datetime_input.date.day,
},
"time": {
"hour": ((datetime_input.time === null) ? 0 : datetime_input.time.hour),
"minute": 0,
"second": 0,
},
};
return pit_from_datetime(datetime_output);
}
/**
*/
function pit_trunc_day(
pit : type_pit
) : type_pit
{
const datetime_input : type_datetime = pit_to_datetime(pit);
const datetime_output : type_datetime = {
"timezone": "Europe/Berlin", // TODO
"date": {
"year": datetime_input.date.year,
"month": datetime_input.date.month,
"day": datetime_input.date.day,
},
"time": {
"hour": 0,
"minute": 0,
"second": 0,
},
};
return pit_from_datetime(datetime_output);
}
/**
*/
function pit_trunc_week(
pit : type_pit
) : type_pit
{
const date_object : Date = pit_to_date_object(pit);
return pit_shift_day(
pit,
-(date_object.getDay())
);
}
/**
*/
function pit_trunc_month(
pit : type_pit
) : type_pit
{
const datetime_input : type_datetime = pit_to_datetime(pit);
const datetime_output : type_datetime = {
"timezone": "Europe/Berlin", // TODO
"date": {
"year": datetime_input.date.year,
"month": datetime_input.date.month,
"day": 1,
},
"time": {
"hour": 0,
"minute": 0,
"second": 0,
},
};
return pit_from_datetime(datetime_output);
}
/**
*/
function pit_trunc_year(
pit : type_pit
) : type_pit
{
const datetime_input : type_datetime = pit_to_datetime(pit);
const datetime_output : type_datetime = {
"timezone": "Europe/Berlin", // TODO
"date": {
"year": datetime_input.date.year,
"month": 1,
"day": 1,
},
"time": {
"hour": 0,
"minute": 0,
"second": 0,
},
};
return pit_from_datetime(datetime_output);
}
/**
*/
function pit_from_year_and_week(
year : int,
week : int
) : type_pit
{
return pit_trunc_week(
pit_shift_week(
pit_from_datetime(
{
"timezone": "Europe/Berlin", // TODO
"date": {
"year": year,
"month": 1,
"day": 1,
},
"time": {
"hour": 12,
"minute": 0,
"second": 0
}
}
),
week
)
)
}
// ---
type type_role = (
"editor"
|
"viewer"
);
type type_user_id = int;
type type_user_object = {
name : string;
};
type type_event = {
name : string;
begin : type_datetime;
end : (
null
|
type_datetime
);
description : (
null
|
string
);
};
type type_calendar_id = int;
type type_calendar_object = (
{
kind : "concrete";
data : {
name : string;
users : Array<
{
id : type_user_id;
role : type_role;
}
>;
events : Array<type_event>;
};
}
|
{
kind : "collection";
data : {
name : string;
sources : Array<
type_calendar_id
>;
}
}
);
type type_datamodel = {
users : Array<
{
id : type_user_id;
object : type_user_object;
}
>;
calendars : Array<
{
id : type_calendar_id;
object : type_calendar_object;
}
>;
};
/**
*/
function calendar_list(
data : type_datamodel
) : Array<
{
key : type_calendar_id;
preview : {
name : string;
}
}
>
{
return (
data.calendars
.map(
(calendar_entry) => ({
"key": calendar_entry.id,
"preview": {
"name": calendar_entry.object.data.name,
}
})
)
);
}
/**
*/
function calendar_read(
data : type_datamodel,
calendar_id : type_calendar_id
) : type_calendar_object
{
const hits = (
data.calendars
.filter(
(calendar_entry) => (calendar_entry.id === calendar_id)
)
);
if (hits.length <= 0) {
throw (new Error("not found"));
}
else {
return hits[0].object;
}
}
/**
*/
function calendar_gather_events(
data : type_datamodel,
calendar_id : type_calendar_id,
from_pit : type_pit,
to_pit : type_pit
) : Array<
{
calendar_id : type_calendar_id;
event : type_event;
}
>
{
const calendar_object : type_calendar_object = calendar_read(
data,
calendar_id
);
switch (calendar_object.kind) {
case "concrete": {
return (
calendar_object.data.events
.filter(
(event) => pit_is_between(
pit_from_datetime(event.begin),
from_pit,
to_pit
)
)
.map(
(event) => ({"calendar_id": calendar_id, "event": event})
)
);
break;
}
case "collection": {
return (
calendar_object.data.sources
.map(
(source_calendar_id) => calendar_gather_events(
data,
source_calendar_id,
from_pit,
to_pit
)
)
.reduce(
(x, y) => x.concat(y),
[]
)
);
break;
}
}
}
/**
*/
function calendar_view_table(
data : type_datamodel,
calendar_id : type_calendar_id,
from : {
year : int;
week : int;
},
to : {
year : int;
week : int;
}
) : Array<
Array<
{
date : type_date;
entries : Array<
{
calendar_id : type_calendar_id;
event : type_event;
}
>;
}
>
>
{
/*
const calendar_object : type_calendar_object = calendar_read(
data,
calendar_id
);
*/
const from_pit : type_pit = pit_from_year_and_week(from.year, from.week);
const to_pit : type_pit = pit_from_year_and_week(to.year, to.week);
// prepare
const entries : Array<
{
calendar_id : type_calendar_id;
event : type_event;
}
> = calendar_gather_events(
data,
calendar_id,
from_pit,
to_pit
);
let result : Array<
Array<
{
date : type_date;
entries : Array<
{
calendar_id : type_calendar_id;
event : type_event;
}
>;
}
>
> = [];
let row : Array<
{
date : type_date;
entries : Array<
{
calendar_id : type_calendar_id;
event : type_event;
}
>;
}
> = [];
let day : int = 0;
while (true) {
let pit_current : type_pit = pit_shift_day(from_pit, day);
if (pit_is_before(pit_current, to_pit)) {
day += 1;
row.push(
{
"date": pit_to_datetime(pit_shift_hour(pit_current, 12)).date,
"entries": [],
}
);
if (day % 7 === 0) {
result.push(row);
row = [];
}
else {
// do nothing
}
}
else {
break;
}
}
// fill
(
entries
.forEach(
(entry) => {
const distance_seconds : int = (pit_from_datetime(entry.event.begin) - from_pit);
const distance_days : int = (distance_seconds / (60 * 60 * 24));
const week : int = Math.floor(Math.floor(distance_days) / 7);
const day : int = (Math.floor(distance_days) % 7);
// process.stdout.write(JSON.stringify({entry, week, day}, undefined, "\t") + "\n");
result[week][day].entries.push(entry);
}
)
);
return result;
}
/**
*/
function calendar_view_table_html(
data : type_datamodel,
calendar_id : type_calendar_id,
from : {
year : int;
week : int;
},
to : {
year : int;
week : int;
}
) : string
{
const rows = calendar_view_table(
data,
calendar_id,
from,
to
);
return (
"<table>\n"
+
(
"\t<thead>\n"
+
"\t</thead>\n"
)
+
(
"\t<tbody>\n"
+
(
rows
.map(
(row) => (
"\t\t<tr>\n"
+
(
row
.map(
(cell) => (
"\t\t\t<td>\n"
+
// (cell.entries.map(entry => entry.event.name).join(" | ") + "\n")
JSON.stringify(cell) + "\n"
+
"\t\t\t</td>\n"
)
)
.join("")
)
+
"\t\t</tr>\n"
)
)
.join("")
)
+
"\t</tbody>\n"
)
+
"</table>\n"
);
}
/**
*/
function main(
) : void
{
const data : type_datamodel = {
"users": [
{
"id": 1,
"object": {
"name": "Anton"
}
},
{
"id": 2,
"object": {
"name": "Berta"
}
},
{
"id": 3,
"object": {
"name": "Caesar"
}
},
],
"calendars": [
{
"id": 1,
"object": {
"kind": "concrete",
"data": {
"name": "Garten",
"users": [
{
"id": 1,
"role": "editor"
},
],
"events": [
{
"name": "Unkraut jähten",
"begin": {
"timezone": "Europe/Berlin",
"date": {"year": 2024, "month": 9, "day": 19},
"time": {"hour": 10, "minute": 0, "second": 0}
},
"end": null,
"description": null
},
{
"name": "Kartoffeln ernten",
"begin": {
"timezone": "Europe/Berlin",
"date": {"year": 2024, "month": 9, "day": 24},
"time": {"hour": 17, "minute": 0, "second": 0}
},
"end": {
"timezone": "Europe/Berlin",
"date": {"year": 2024, "month": 9, "day": 24},
"time": {"hour": 20, "minute": 0, "second": 0}
},
"description": null
},
]
}
}
},
{
"id": 2,
"object": {
"kind": "concrete",
"data": {
"name": "Kochen",
"users": [
{
"id": 1,
"role": "editor"
},
{
"id": 2,
"role": "viewer"
},
],
"events": [
{
"name": "38.KW",
"begin": {
"timezone": "Europe/Berlin",
"date": {"year": 2024, "month": 9, "day": 20},
"time": {"hour": 17, "minute": 0, "second": 0}
},
"end": {
"timezone": "Europe/Berlin",
"date": {"year": 2024, "month": 9, "day": 20},
"time": {"hour": 19, "minute": 0, "second": 0}
},
"description": "Krautnudeln"
}
]
}
}
},
{
"id": 3,
"object": {
"kind": "concrete",
"data": {
"name": "Band",
"users": [
{
"id": 2,
"role": "editor"
},
],
"events": [
{
"name": "Auftritt im Park",
"begin": {
"timezone": "Europe/Berlin",
"date": {"year": 2024, "month": 9, "day": 21},
"time": {"hour": 20, "minute": 0, "second": 0}
},
"end": {
"timezone": "Europe/Berlin",
"date": {"year": 2024, "month": 9, "day": 21},
"time": {"hour": 21, "minute": 0, "second": 0}
},
"description": null
}
]
}
}
},
{
"id": 4,
"object": {
"kind": "collection",
"data": {
"name": "Überblick",
"sources": [
1,
2,
3
]
}
}
},
]
};
const output : string = calendar_view_table_html(
data,
4,
{
"year": 2024,
"week": 37
},
{
"year": 2024,
"week": 40
}
);
process.stdout.write(output);
}
main();

4
tools/build Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env sh
make -f tools/makefile

31
tools/makefile Normal file
View file

@ -0,0 +1,31 @@
## dirs
dir_source := source
dir_temp := temp
dir_build := build
## cmds
cmd_log := echo "--"
cmd_tsc := tsc --strict
cmd_cat := cat
cmd_mkdir := mkdir -p
## rules
.PHONY: _default
_default: ${dir_build}/kalender
${dir_temp}/kalender-unlinked.js: ${dir_source}/main.ts
@ ${cmd_log} "compiling …"
@ ${cmd_mkdir} $(dir $@)
@ ${cmd_tsc} $^ --outFile $@
${dir_build}/kalender: ${dir_temp}/kalender-unlinked.js
@ ${cmd_log} "linking …"
@ ${cmd_mkdir} $(dir $@)
@ ${cmd_cat} $^ > $@
@ chmod +x $@