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; }; } | { 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 ( "\n" + ( "\t\n" + "\t\n" ) + ( "\t\n" + ( rows .map( (row) => ( "\t\t\n" + ( row .map( (cell) => ( "\t\t\t\n" ) ) .join("") ) + "\t\t\n" ) ) .join("") ) + "\t\n" ) + "
\n" + // (cell.entries.map(entry => entry.event.name).join(" | ") + "\n") JSON.stringify(cell) + "\n" + "\t\t\t
\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();