PHP source for web UI (I like single page self contained PHP scripts for their portability.)
<?php
$svg_content = "";
if (isset($_GET["weekp"])) {
$week_input = filter_input(INPUT_GET, 'weekp', FILTER_SANITIZE_STRING);
// Extract year and week from the weekp value
$dates = explode(" - ", $week_input);
if (count($dates) === 2) {
$startd = date("Y-m-d", strtotime($dates[0]));
$endd = date("Y-m-d", strtotime($dates[1]));
if ($startd && $endd) {
$start_date=$startd;
$end_date=$endd;
$current_weekp=htmlspecialchars($week_input);
}
$svg_content = generateSvg($start_date);
}
?>
<?php if ($_GET['puresvg'] == 1) {
header('Content-type: image/svg+xml');
echo $svg_content;
} else {
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.15">
<meta charset="UTF-8">
<title>Viikko-kalenteri-kone</title>
<link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css'>
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.7.1/css/bootstrap-datepicker3.css'>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
}
h1 {
margin-top: 0;
margin-bottom: 0.3em;
}
form,
.buttons {
margin-bottom: 20px;
}
.controls {
background-color: #f5f5f5;
position: fixed;
padding: 3px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
z-index: 10;
max-width: 85%;
margin-left: 0;
}
.controls button {
max-width: 100%;
}
.svg-container {
overflow-x: auto;
white-space: nowrap;
}
svg {
display: inline-block;
max-height: 100%;
transform: scale(0.75);
transform-origin: top left;
}
input[type="date"],
button {
font-size: 16px;
padding: 10px;
margin: 5px 0;
display: block;
width: 100%;
box-sizing: border-box;
/* Ensures padding doesn't affect overall width */
}
button {
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
@media screen {
.svg-container {
margin-top: 23em !important;
/* Adjust based on the height of your controls */
}
}
@media screen and (max-width: 600px) {
input[type="date"],
button {
font-size: 18px;
/* Larger font size for mobile */
padding: 15px;
}
/* Adjusting for a more modern, mobile-friendly UI */
body {
padding: 10px;
}
form,
.buttons {
margin-bottom: 15px;
}
}
@media print {
.no-print,
.no-print * {
display: none !important;
}
.print-only {
display: block;
}
}
@media print {
/* Hide elements with these classes or IDs */
#weekForm,
.hide-on-print {
display: none !important;
}
}
.svg-container {
overflow-x: visible;
margin-top: 0;
width: auto;
}
svg {
transform: scale(1);
/* Reset any scaling to fit the print page */
}
}
.datepicker .datepicker-days tr td.active~td,
.datepicker .datepicker-days tr td.active {
color: #af1623 !important;
background: transparent !important;
}
.datepicker .datepicker-days tr:hover td {
color: #000;
background: #e5e2e3;
border-radius: 0;
}
</style>
<script src='//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
<script src='bootstrap.min.3.3.7.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.7.1/js/bootstrap-datepicker.min.js'></script>
<script>
window.console = window.console || function(t) {};
</script>
</head>
<body>
<div class="controls hide-on-print">
<h1 style="letter-spacing:-0.02em;"><b>Viikko</b><span style="color:green;letter-spacing:-0.04em;">kalenteri</span><b>kone</b></h1>
<form method="get" id="weekForm">
<label class="control-label " for="weekpicker">Valitse mikä tahansa päivä viikolla jolta <br>haluat kalenterin (klikkaa alla olevaa kenttää):</label>
<span class="icon-block ">
<input type="text" name="weekp" class="form-control" id="weekpicker">
<span class="icon-date"></span>
</span>
<div class="week-controls">
<!-- <button id="prevWeek" style="display:inline;width:20em;" class="prev-week">Edellinen viikko</button>
<button id="nextWeek" style="display:inline;width:10em;" class="next-week">Seuraava</button>-->
</div>
<!--<label for="date"></label>
<input type="date" id="date" name="date" required onchange="updateWeekField()">
--> <input type="hidden" id="week" name="week">
<button style="display:block;width:20em;" type="submit">Tee kalenteri</button>
</form>
<script>
// Ensures the week field is updated on form load if there's a query string
window.onload = function() {
if (window.location.search.indexOf('week=') > -1) {
updateWeekField(true);
}
}
function updateWeekField(onLoad = false) {
// Get the date input field element
const dateInput = document.getElementById('date');
// Get the week input field element (hidden input that will hold the week number value)
//const weekInput = document.getElementById('week');
const weekInput = document.getElementById('weekpicker');
if (onLoad) { // Check if the function was called during page load
// Parse the current URL query parameters
const queryParams = new URLSearchParams(window.location.search);
// Extract the 'weekp' value from the query parameters
const weekpValue = queryParams.get('weekp');
if (weekpValue) { // Check if 'weekp' parameter exists
// Set the value of the weekp input field to the 'weekp' value
weekInput.value = decodeURIComponent(weekpValue); // Decode URI component to handle special characters
}
}
if (onLoad) { // Check if the function was called during page load
// Parse the current URL query parameters
const queryParams = new URLSearchParams(window.location.search);
// Extract the 'week' value from the query parameters
const weekValue = queryParams.get('week');
// Extract the year from the week value (format: YYYY-WWW)
const year = weekValue.substring(0, 4);
// Extract the week number from the week value
const weekNumber = weekValue.substring(6);
// Create a new Date object for the extracted year
const date = new Date(year);
// Adjust the date to the start date of the extracted week number (considering the first week starts at day 1)
date.setDate(date.getDate() + (weekNumber - 1) * 7);
// Set the date input field's value to the calculated date
dateInput.valueAsDate = date;
} else { // If the function was not called during page load (e.g., user interaction)
// Create a Date object from the value of the date input field
const date = new Date(dateInput.value);
// Calculate the week number for the selected date
const weekNumber = getISOWeekFromDate(date);
// Extract the year from the selected date
const year = date.getFullYear();
// Set the week input field's value to the calculated week number in the format YYYY-WWW
weekInput.value = `${year}-W${String(weekNumber).padStart(2, '0')}`;
}
}
$(document).ready(function() {
var startDate, endDate;
$('#weekpicker').datepicker({
autoclose: true,
format: 'dd/mm/yyyy',
forceParse: false
}).on("changeDate", function(e) {
var date = e.date;
startDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay());
endDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay() + 6);
$('#weekpicker').datepicker('update', startDate);
$('#weekpicker').val(
startDate.getDate() + '.' + (startDate.getMonth() + 1) + '.' + startDate.getFullYear() + ' - ' +
endDate.getDate() + '.' + (endDate.getMonth() + 1) + '.' + endDate.getFullYear()
);
});
// Previous week button
$('#prevWeek').click(function(e) {
var date = $('#weekpicker').datepicker('getDate');
startDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay() - 7);
endDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay() - 1);
$('#weekpicker').datepicker("setDate", new Date(startDate));
$('#weekpicker').val(
(startDate.getMonth() + 1) + '/' + startDate.getDate() + '/' + startDate.getFullYear() + ' - ' +
(endDate.getMonth() + 1) + '/' + endDate.getDate() + '/' + endDate.getFullYear()
);
return false;
});
// Next week button
$('#nextWeek').click(function() {
var date = $('#weekpicker').datepicker('getDate');
startDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay() + 7);
endDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay() + 13);
$('#weekpicker').datepicker("setDate", new Date(startDate));
$('#weekpicker').val(
(startDate.getMonth() + 1) + '/' + startDate.getDate() + '/' + startDate.getFullYear() + ' - ' +
(endDate.getMonth() + 1) + '/' + endDate.getDate() + '/' + endDate.getFullYear()
);
return false;
});
});
var startDate,
endDate;
$('#weekpicker').datepicker({
autoclose: true,
format: 'mm/dd/yyyy',
forceParse: false,
language: 'fi'
}).on("changeDate", function(e) {
//console.log(e.date);
var date = e.date;
startDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay());
endDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay() + 6);
$('#weekpicker').datepicker('update', startDate);
$('#weekpicker').val((startDate.getMonth() + 1) + '/' + startDate.getDate() + '/' + startDate.getFullYear() + ' - ' + (endDate.getMonth() + 1) + '/' + endDate.getDate() + '/' + endDate.getFullYear());
});
$('#prevWeek').click(function(e) {
var date = $('#weekpicker').datepicker('getDate');
//dateFormat = "mm/dd/yy"; //$.datepicker._defaults.dateFormat;
startDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay() - 7);
endDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay() - 1);
$('#weekpicker').datepicker("setDate", new Date(startDate));
$('#weekpicker').val((startDate.getMonth() + 1) + '/' + startDate.getDate() + '/' + startDate.getFullYear() + ' - ' + (endDate.getMonth() + 1) + '/' + endDate.getDate() + '/' + endDate.getFullYear());
return false;
});
$('#nextWeek').click(function() {
var date = $('#weekpicker').datepicker('getDate');
//dateFormat = "mm/dd/yy"; // $.datepicker._defaults.dateFormat;
startDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay() + 7);
endDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay() + 13);
$('#weekpicker').datepicker("setDate", new Date(startDate));
$('#weekpicker').val((startDate.getMonth() + 1) + '/' + startDate.getDate() + '/' + startDate.getFullYear() + ' - ' + (endDate.getMonth() + 1) + '/' + endDate.getDate() + '/' + endDate.getFullYear());
return false;
});
</script>
<?php if (!empty($svg_content)): ?>
<div>
<button onclick="window.print();" style="display:block;width:20em;" class="hide-on-print">Tulosta kalenteri</button>
</div>
<?php
?>
<a href="<?php echo $_SERVER['PHP_SELF'] . '?puresvg=1&weekp=' . urlencode($current_weekp); ?>" class='hide-on-print' download>Lataa SVG</a>
|
<a href="<?php echo $_SERVER['PHP_SELF'] . '?puresvg=1&weekp=' . urlencode($current_weekp); ?>" class='hide-on-print'>Näytä vain kalenteri</a>
</div>
<div class="svg-container">
<?php echo $svg_content; ?>
</div>
<?php endif; ?>
</body>
</html>
<?php } ?>
<?php
// Function to generate SVG content
function generateSvg($start_date)
{
$finnish_days = ['Maanantai', 'Tiistai', 'Keskiviikko', 'Torstai', 'Perjantai', 'Lauantai', 'Sunnuntai'];
$week_number = date('W', strtotime($start_date));
$year = date('o', strtotime($start_date)); // 'o' gives the ISO-8601 week-numbering year, which is what we need here.
$margin = 20; // Margin between days
$num_hours = 14; // Number of hour slots (8-21, inclusive)
$hour_height = 40;
$hour_number_width = 70; // Additional space for hour numbers
// Adjusted SVG dimensions to accommodate margins and hour numbers
$width = 1000 + (count($finnish_days) - 1) * $margin + $hour_number_width;
$height = ($num_hours + 2) * $hour_height; // Add extra space for the last hour
// Adjusted calculations for layout
$day_width = ($width - $margin * (count($finnish_days) - 1) - $hour_number_width) / count($finnish_days);
$svg_start = "<svg width=\"$width\" height=\"$height\" xmlns=\"http://www.w3.org/2000/svg\" style=\"font-family:sans-serif;\">";
// Week Number and Year
$week_number_svg = "<text x=\"" . ($width / 2) . "\" y=\"20\" font-size=\"24\" text-anchor=\"middle\">Viikko $week_number, $year</text>";
// Drawing Days, Dates, and Vertical Lines
$days_svg = $lines_svg = $hours_svg = '';
for ($i = 0; $i < count($finnish_days); $i++) {
$day = $finnish_days[$i];
// Format the date to remove leading zeros from the month
$date = date("j.n.", strtotime($start_date . " +$i days"));
$x_position = $i * ($day_width + $margin) + $hour_number_width;
$days_svg .= "<text x=\"" . ($x_position + $day_width / 2) . "\" y=\"50\" font-size=\"20\" text-anchor=\"middle\">$day $date</text>";
}
// Drawing Hours and Shading, Adjust for Hour Number Space
for ($i = 0; $i < $num_hours; $i++) {
$hour = 8 + $i;
$y_position = $hour_height * ($i + 1) + 30;
$shade_of_grey = $i % 2 == 0 ? "#f0f0f0" : "#d0d0d0";
$hours_svg .= "<rect x=\"$hour_number_width\" y=\"$y_position\" width=\"" . ($width - $hour_number_width) . "\" height=\"$hour_height\" fill=\"$shade_of_grey\" />";
$hours_svg .= "<text x=\"10\" y=\"" . ($y_position + $hour_height / 2 + 5) . "\" font-size=\"20\" text-anchor=\"start\">$hour:00</text>";
}
for ($i = 1; $i < count($finnish_days); $i++) {
$x_position = $i * ($day_width + $margin) + $hour_number_width - $margin / 2;
$lines_svg .= "<line x1=\"$x_position\" y1=\"60\" x2=\"$x_position\" y2=\"$height\" stroke=\"black\" stroke-width=\"2\"/>";
}
$svg_end = '</svg>';
// Complete SVG
return $svg_start . $week_number_svg . $days_svg . $hours_svg . $lines_svg . $svg_end;
}
?>
Python source for just calendar creation, less advanced:
Earlier Python version
from datetime import datetime, timedelta
# Date settings
start_date = datetime(2024, 4, 1)
finnish_days = ['Maanantai', 'Tiistai', 'Keskiviikko', 'Torstai', 'Perjantai', 'Lauantai', 'Sunnuntai']
# Calculate week number
week_number = start_date.isocalendar()[1]
# Generate dates for the week, including the month number
week_dates = [(start_date + timedelta(days=i)).strftime("%d.%-m.") for i in range(7)]
# SVG adjustments
margin = 20 # Margin between days
num_hours = 14 # Number of hour slots (8-21, inclusive)
hour_height = 40
hour_number_width = 70 # Additional space for hour numbers
# Adjusted SVG dimensions to accommodate margins and hour numbers
width = 1000 + (len(finnish_days) - 1) * margin + hour_number_width
height = (num_hours + 1) * hour_height # Add extra space for the last hour
# Adjusted calculations for layout
day_width = (width - margin * (len(finnish_days) - 1) - hour_number_width) / len(finnish_days)
# Starting SVG
svg_start = f'<svg width="{width}" height="{height}" xmlns="http://www.w3.org/2000/svg" style="font-family:sans-serif;">'
# Week Number
week_number_svg = f'<text x="{width/2}" y="20" font-size="24" text-anchor="middle">Viikko {week_number}</text>'
# Drawing Days, Dates
days_svg = ''
for i, (day, date) in enumerate(zip(finnish_days, week_dates), start=1):
x_position = (i-1) * (day_width + margin) + hour_number_width
days_svg += f'<text x="{x_position + day_width/2}" y="50" font-size="20" text-anchor="middle">{day} {date}</text>'
# Drawing Hours and Shading, Adjust for Hour Number Space
hours_svg = ''
for i, hour in enumerate(range(8, 22)):
y_position = hour_height * (i + 1) + 30
shade_of_grey = "#f0f0f0" if i % 2 == 0 else "#d0d0d0"
hours_svg += f'<rect x="{hour_number_width}" y="{y_position}" width="{width - hour_number_width}" height="{hour_height}" fill="{shade_of_grey}" />'
hours_svg += f'<text x="10" y="{y_position + hour_height/2 + 5}" font-size="20" text-anchor="start">{hour}:00</text>'
# Drawing Vertical Lines on Top
lines_svg = ''
for i in range(1, len(finnish_days)):
x_position = i * (day_width + margin) + hour_number_width - margin / 2
lines_svg += f'<line x1="{x_position}" y1="60" x2="{x_position}" y2="{height}" stroke="black" stroke-width="2"/>'
# Ending SVG
svg_end = '</svg>'
# Complete SVG
svg_content = svg_start + week_number_svg + days_svg + hours_svg + lines_svg + svg_end
# Displaying or saving the SVG content
print(svg_content)