|
|
|
|
|
|
|
// TODO:
|
|
|
|
// * check what does really mean this illumination or the other one !
|
|
|
|
// like is it really how much the moon is lit ?
|
|
|
|
|
|
|
|
// NOTE:
|
|
|
|
// 1. the date and time DO coincide with
|
|
|
|
// https://www.timeanddate.com/moon/phases/?year=2022
|
|
|
|
// to the minute !
|
|
|
|
// unlike the "self computed values"
|
|
|
|
// (which can differ of 1 day ~ 12h and thus endup in the wrong daybox, even if we don't show the time value)
|
|
|
|
// 2. iCalendar give date and time in CET/CEST
|
|
|
|
// so we have to use luxon to parse the string as CET, and convert it to a proper date object
|
|
|
|
// then we can print this object back according to where the walk happens
|
|
|
|
|
|
|
|
// test zone for other date
|
|
|
|
// first new moon of 2000 according to:
|
|
|
|
// https://www.calendar-12.com/moon_phases/2000
|
|
|
|
// let now = new Date(Date.parse('06 Jan 2000 19:14:00 GMT+1'));
|
|
|
|
// let now = new Date(Date.parse('13 Jan 2000 05:41:00 GMT+1'));
|
|
|
|
// let now = new Date(Date.parse('21 Jan 2000 05:41:00 GMT+1'));
|
|
|
|
let now = new Date();
|
|
|
|
let months_ahead = window.walk_calendar["months_ahead"];
|
|
|
|
let month_to_get = now;
|
|
|
|
let queue = [];
|
|
|
|
|
|
|
|
|
|
|
|
// UTILITIES
|
|
|
|
// ==========================================================================
|
|
|
|
|
|
|
|
function nextMonth(date, i=1){
|
|
|
|
// return the i-th next month
|
|
|
|
let nextMonth = new Date(date); // make a copy of date object
|
|
|
|
nextMonth.setMonth(nextMonth.getMonth() + i); // add i month
|
|
|
|
return nextMonth;
|
|
|
|
}
|
|
|
|
|
|
|
|
function prevMonth(date, i=1){
|
|
|
|
// return the i-th next month
|
|
|
|
let prevMonth = new Date(date); // make a copy of date object
|
|
|
|
prevMonth.setMonth(prevMonth.getMonth() - i); // add i month
|
|
|
|
return prevMonth;
|
|
|
|
}
|
|
|
|
|
|
|
|
function zeroPad(num, places) {
|
|
|
|
var zero = places - num.toString().length + 1;
|
|
|
|
return Array(+(zero > 0 && zero)).join("0") + num;
|
|
|
|
}
|
|
|
|
|
|
|
|
function translatePhaseDutch(phase_name){
|
|
|
|
if (phase_name == 'Full moon'){
|
|
|
|
return 'Volle maan';
|
|
|
|
}
|
|
|
|
else if (phase_name == 'New Moon'){
|
|
|
|
return 'Nieuwe maan';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// FILL CALENDAR WITH DATA
|
|
|
|
// ==========================================================================
|
|
|
|
|
|
|
|
function addDateToCalendar(date, moon){
|
|
|
|
// given a moon state object from the API,
|
|
|
|
// fill in the template and add it to the calendar
|
|
|
|
|
|
|
|
// --- data we need from the yaml
|
|
|
|
let lat = window.walk_calendar["latitude"];
|
|
|
|
let long = window.walk_calendar["longitude"];
|
|
|
|
let timezone = window.walk_calendar["timezone"];
|
|
|
|
let calendar = window.walk_calendar["calendar"];
|
|
|
|
let contact_mail = window.walk_calendar["reservation_mail"];
|
|
|
|
|
|
|
|
// --- converting the date to walk timezone
|
|
|
|
date = date.setZone(timezone);
|
|
|
|
// console.log(date.toString());
|
|
|
|
|
|
|
|
// --- clone template
|
|
|
|
let template = $('#cal-entry__template').html();
|
|
|
|
let $cal_entry = $($.parseHTML(template));
|
|
|
|
|
|
|
|
// --- get suncalc moon object
|
|
|
|
// for complementary info that are not in the moon API object, such as:
|
|
|
|
// earth-moon distance, and illumination
|
|
|
|
let moon_calc = getMoonStateFromDate(date, lat, long);
|
|
|
|
let distance = moon_calc["distance"];
|
|
|
|
// let illumination = (moon_calc["illumination"]*100).toFixed(2);
|
|
|
|
|
|
|
|
// --- get suncalc sun object
|
|
|
|
let walk_sunset = getSunStateFromDate(date, lat, long, timezone)[1];
|
|
|
|
|
|
|
|
// --- formating the info
|
|
|
|
let phase_name = moon.phaseName;
|
|
|
|
let phase = moon.isPhaseLimit === 1 ? 0 : 0.5;
|
|
|
|
|
|
|
|
// API is missing dutch phase name
|
|
|
|
if (window.current_local == "nl-be"){
|
|
|
|
phase_name = translatePhaseDutch(phase_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
// convert those two luxon dates in 'timezone' to human readable strings
|
|
|
|
// whith 'timezone' indicated
|
|
|
|
let walk_sunset_time = format_luxon_date(walk_sunset);
|
|
|
|
let formated_date = format_luxon_date(date);
|
|
|
|
|
|
|
|
// let time_id = date.toUnixInteger();
|
|
|
|
let time_id =formated_date['date'].split(".").join("-");
|
|
|
|
|
|
|
|
// --- filling the info
|
|
|
|
$cal_entry.attr("id", time_id);
|
|
|
|
// --- PHASENAME
|
|
|
|
$cal_entry.find(".phase-name").html(phase_name);
|
|
|
|
// --- EVENT
|
|
|
|
$cal_entry.find(".day").html(formated_date['day']);
|
|
|
|
$cal_entry.find(".date").html(formated_date['date']);
|
|
|
|
|
|
|
|
// --- SECONDARY INFO ---
|
|
|
|
$cal_entry.find(".sunset-time").html(walk_sunset_time['time']);
|
|
|
|
$cal_entry.find(".moon-time").html(formated_date['time']);
|
|
|
|
// $cal_entry.find(".cal-entry__distance span").html(distance.toFixed(0));
|
|
|
|
|
|
|
|
// --- updating the font
|
|
|
|
updateAxis($cal_entry, phase);
|
|
|
|
|
|
|
|
// --- adding walk calendar info
|
|
|
|
if (calendar[time_id]){
|
|
|
|
let walk_entry = calendar[time_id];
|
|
|
|
|
|
|
|
// Event type
|
|
|
|
let event_type = walk_entry.event_type[window.current_local.substring(0,2)];
|
|
|
|
|
|
|
|
// --- add the class
|
|
|
|
$cal_entry.addClass("confirmed");
|
|
|
|
$cal_entry.find(".reservation .not-confirmed").remove();
|
|
|
|
|
|
|
|
// --- fill infos
|
|
|
|
$cal_entry.find(".event-type").html(event_type);
|
|
|
|
$cal_entry.find(".start-time").html(walk_entry.start_time);
|
|
|
|
$cal_entry.find(".start-location a").html(walk_entry.start_location);
|
|
|
|
$cal_entry.find(".start-location a").attr("href", walk_entry.start_location_link);
|
|
|
|
|
|
|
|
// --- mail subject
|
|
|
|
// only added if event has an entry in yaml AND not a past event
|
|
|
|
let mail_subject = [event_type, phase_name, formated_date['date']].join(", ");
|
|
|
|
let uri = "mailto:" + contact_mail + "?subject=";
|
|
|
|
uri += encodeURIComponent(mail_subject);
|
|
|
|
$cal_entry.find(".reservation a").attr("href", uri);
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
$cal_entry.find(".cal-entry__description").remove();
|
|
|
|
$cal_entry.find(".reservation .signup").remove();
|
|
|
|
}
|
|
|
|
|
|
|
|
// --- adding classes
|
|
|
|
if(date < now){
|
|
|
|
$cal_entry.addClass("past");
|
|
|
|
$cal_entry.find(".reservation .signup").remove();
|
|
|
|
$cal_entry.find(".reservation .not-confirmed").remove();
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
$cal_entry.find(".reservation .past").remove();
|
|
|
|
}
|
|
|
|
|
|
|
|
if(phase === 0){
|
|
|
|
$cal_entry.addClass("newmoon");
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
$cal_entry.addClass("fullmoon");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- make it clickable
|
|
|
|
$cal_entry.on( "click", function() {
|
|
|
|
let yaml_entry = '#'+ phase_name + " walk on the " + formated_date['date']
|
|
|
|
+ '\n' + time_id + ":";
|
|
|
|
navigator.clipboard.writeText(yaml_entry);
|
|
|
|
});
|
|
|
|
|
|
|
|
// --- append to body
|
|
|
|
$('#calendar-section .calendar-content').append($cal_entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
function postProcessCalendar(){
|
|
|
|
// is called when calendar is entierly finished
|
|
|
|
console.log("calendar processed");
|
|
|
|
|
|
|
|
$('.cal-entry.past').last().addClass('last-past');
|
|
|
|
$('.cal-loading').remove();
|
|
|
|
|
|
|
|
// --- add animation listener
|
|
|
|
$('.cal-entry').bind("webkitAnimationEnd mozAnimationEnd animationend", function(){
|
|
|
|
$(this).removeClass("animated")
|
|
|
|
})
|
|
|
|
|
|
|
|
$('.cal-entry').mouseenter(function(){
|
|
|
|
$(this).addClass("animated");
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function writeTimezone(){
|
|
|
|
let timezone = window.walk_calendar["timezone"];
|
|
|
|
let temp_date = luxon.DateTime.now().setZone(timezone);
|
|
|
|
temp_date = temp_date.setLocale(window.current_local);
|
|
|
|
let offset = luxon.DateTime.now().setZone(timezone).offsetNameShort;
|
|
|
|
$('.date-time-info span').html(offset + " (" + timezone + ")");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// PROCESSING API RESPONSE
|
|
|
|
// ==========================================================================
|
|
|
|
|
|
|
|
function processMonthResponse(response){
|
|
|
|
|
|
|
|
// process the API response
|
|
|
|
// in this API the new/full moon event last exactly
|
|
|
|
// on the day in which the precise event happens
|
|
|
|
|
|
|
|
let month_config = response.config;
|
|
|
|
let month_calendar = response.calendar;
|
|
|
|
console.log("adding", month_config.month + " " + month_config.year);
|
|
|
|
|
|
|
|
// iterate through every day
|
|
|
|
for(let i = 1; i <= month_calendar.daysMonth; i++){
|
|
|
|
|
|
|
|
let moon = month_calendar.phase[i];
|
|
|
|
|
|
|
|
// if a new/full moon event happens that day
|
|
|
|
// 2 and 4 are first/last quarter,
|
|
|
|
// false are the in between waning/waxing days
|
|
|
|
if (moon.isPhaseLimit == 1 || moon.isPhaseLimit == 3){
|
|
|
|
|
|
|
|
// create back a date object from i and month
|
|
|
|
// to have easier control on it's display
|
|
|
|
// + getting back the exact time of the event from the API
|
|
|
|
|
|
|
|
// NOTE: in which UTC does icalc37 is giving us the calendar?
|
|
|
|
// it seems it's given in CET : https://en.wikipedia.org/wiki/Central_European_Time
|
|
|
|
// which oscillate between UTC+1 and UTC+2 according to winter/summer time
|
|
|
|
// Note that: "When the time zone offset is absent,
|
|
|
|
// date-only forms are interpreted as a UTC time and date-time forms are interpreted as local time."
|
|
|
|
// so here it's working because MY local time is == to CET
|
|
|
|
// but a computer in china would not parse the date correctly...
|
|
|
|
// we can not precise CET and it's non-trivial to convert from: CET -> GMT -> USER TIME
|
|
|
|
// because it has to take in account the rules of when CET = UTC+1 pr UTC+2
|
|
|
|
// https://stackoverflow.com/questions/20867562/create-a-date-object-with-cet-timezone
|
|
|
|
|
|
|
|
// old code
|
|
|
|
// let date_str = [month_config.month, i, month_config.year, moon.timeEvent].join(" ");
|
|
|
|
// let date = new Date(date_str);
|
|
|
|
|
|
|
|
// IANA for the CET date we got from the API: Europe/Brussels
|
|
|
|
// IANA for the place where the walk happens: Europe/Brussels
|
|
|
|
// those are conceptually independant !!
|
|
|
|
// see luxon documentation: https://moment.github.io/luxon/#/zones
|
|
|
|
|
|
|
|
let dateISO = [month_config.year, zeroPad(month_config.month,2), zeroPad(i,2)].join("-");
|
|
|
|
let timeISO = moon.timeEvent.split(":").map(x => zeroPad(x,2)).join(":");
|
|
|
|
let dateTimeISO = dateISO+"T"+timeISO;
|
|
|
|
let date = luxon.DateTime.fromISO(dateTimeISO, { zone: "Europe/Brussels" });
|
|
|
|
// console.log(dateTimeISO);
|
|
|
|
// console.log(date.zoneName);
|
|
|
|
|
|
|
|
addDateToCalendar(date, moon);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// API CALLS & QUEUE HANDLING
|
|
|
|
// ==========================================================================
|
|
|
|
|
|
|
|
//based on http://www.wdisseny.com/lluna/?lang=en
|
|
|
|
function requestMonthMoonPhases(obj, callback){
|
|
|
|
// write the request
|
|
|
|
let gets=[];
|
|
|
|
for (let i in obj){
|
|
|
|
gets.push(i + "=" +encodeURIComponent(obj[i]));
|
|
|
|
}
|
|
|
|
gets.push("LDZ=" + new Date(obj.year,obj.month-1,1) / 1000);
|
|
|
|
let xmlhttp = new XMLHttpRequest();
|
|
|
|
let url = "https://www.icalendar37.net/lunar/api/?" + gets.join("&");
|
|
|
|
|
|
|
|
xmlhttp.onreadystatechange = function() {
|
|
|
|
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
|
|
|
|
|
|
|
|
queue.push({config: obj, calendar: JSON.parse(xmlhttp.responseText)})
|
|
|
|
callback();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
xmlhttp.open("GET", url, true);
|
|
|
|
xmlhttp.send();
|
|
|
|
}
|
|
|
|
|
|
|
|
function processQueue() {
|
|
|
|
|
|
|
|
// Sort the queue according to config month
|
|
|
|
queue.sort(function(a, b) {
|
|
|
|
if(a.config.year != b.config.year){
|
|
|
|
return a.config.year - b.config.year;
|
|
|
|
} else{
|
|
|
|
return a.config.month - b.config.month;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
// let current_queue = queue.map(x => x.config.month + " " + x.config.year);
|
|
|
|
// console.log(current_queue);
|
|
|
|
|
|
|
|
// Test if the queue is not empty
|
|
|
|
// and if it's first element is the next one to add
|
|
|
|
if ( queue.length > 0 &&
|
|
|
|
queue[0].config.month === month_to_get.getMonth() + 1 &&
|
|
|
|
queue[0].config.year === month_to_get.getFullYear()) {
|
|
|
|
|
|
|
|
// add to calendar
|
|
|
|
processMonthResponse(queue[0]);
|
|
|
|
|
|
|
|
// Remove the first item from the queue as it's already processed
|
|
|
|
queue.splice(0, 1);
|
|
|
|
|
|
|
|
// update the next month to get
|
|
|
|
month_to_get = nextMonth(month_to_get);
|
|
|
|
months_ahead = months_ahead - 1;
|
|
|
|
|
|
|
|
// Process the queue again
|
|
|
|
processQueue();
|
|
|
|
}
|
|
|
|
else if ( months_ahead == 0 ) {
|
|
|
|
postProcessCalendar();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MAIN
|
|
|
|
// ==========================================================================
|
|
|
|
|
|
|
|
function createCalendar(months_ahead){
|
|
|
|
|
|
|
|
let new_month = now;
|
|
|
|
|
|
|
|
for (var i = 0; i < months_ahead; i++) {
|
|
|
|
|
|
|
|
let lang = window.current_local.substring(0,2);
|
|
|
|
|
|
|
|
// config to send to the API
|
|
|
|
let config_month = {
|
|
|
|
lang : lang,
|
|
|
|
month : new_month.getMonth() + 1,
|
|
|
|
year : new_month.getFullYear()
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log("doing request", config_month.month + " " + config_month.year + " in " + lang);
|
|
|
|
requestMonthMoonPhases(config_month, processQueue);
|
|
|
|
|
|
|
|
// increase the month by 1
|
|
|
|
new_month = nextMonth(new_month);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$(document).ready(function(){
|
|
|
|
|
|
|
|
createCalendar(months_ahead);
|
|
|
|
|
|
|
|
writeTimezone();
|
|
|
|
|
|
|
|
});
|