feat: Add intial site

This commit is contained in:
Nathan Woodburn 2025-03-06 15:15:24 +11:00
parent 66620204ac
commit 6d7019bba5
Signed by: nathanwoodburn
GPG Key ID: 203B000478AD0EF1
6 changed files with 284 additions and 5 deletions

View File

@ -1,3 +1,25 @@
# python-webserver-template
Python3 website template including git actions
Python3 website template including git actions
## Example `curl` Commands
### Convert Specific Time Between Two Timezones
```sh
curl -X POST http://localhost:5000/api/v1/convert -H "Content-Type: application/json" -d '{
"from_tz": "Australia/Sydney",
"to_tz": "UTC",
"time": "2023-10-10T15:00:00"
}'
```
### Convert Specific Time Between Multiple Timezones
```sh
curl -X POST http://localhost:5000/api/v1/convert_multiple -H "Content-Type: application/json" -d '{
"from_tz": "Australia/Sydney",
"time": "2023-10-10T15:00:00",
"to_tzs": ["UTC", "America/New_York", "Europe/London"]
}'
```

View File

@ -1,4 +1,5 @@
flask
gunicorn
requests
python-dotenv
python-dotenv
pytz

118
server.py
View File

@ -13,8 +13,12 @@ from flask import (
import os
import json
import requests
from datetime import datetime
from datetime import datetime, timedelta
import dotenv
from pytz import timezone
import pytz
import re
dotenv.load_dotenv()
@ -76,6 +80,118 @@ def wellknown(path):
def index():
return render_template("index.html")
# Mapping of short timezone names to full names
SHORT_TZ_MAP = {
"GMT": "Etc/GMT",
"PST": "America/Los_Angeles",
"PDT": "America/Los_Angeles",
"MST": "America/Denver",
"MDT": "America/Denver",
"CST": "America/Chicago",
"CDT": "America/Chicago",
"EST": "America/New_York",
"EDT": "America/New_York",
"AKST": "America/Anchorage",
"AKDT": "America/Anchorage",
"HST": "Pacific/Honolulu",
"HDT": "Pacific/Honolulu",
"AST": "America/Puerto_Rico",
"NST": "America/St_Johns",
"BST": "Europe/London",
"CET": "Europe/Paris",
"CEST": "Europe/Paris",
"EET": "Europe/Athens",
"EEST": "Europe/Athens",
"MSK": "Europe/Moscow",
"MSD": "Europe/Moscow",
"IST": "Asia/Kolkata",
"PKT": "Asia/Karachi",
"WIB": "Asia/Jakarta",
"WITA": "Asia/Makassar",
"WIT": "Asia/Jayapura",
"CST": "Asia/Shanghai",
"JST": "Asia/Tokyo",
"KST": "Asia/Seoul",
"AEDT": "Australia/Sydney",
"AEST": "Australia/Sydney",
"ACST": "Australia/Adelaide",
"ACDT": "Australia/Adelaide",
"AWST": "Australia/Perth",
}
def get_full_timezone(tz):
match = re.match(r"^UTC([+-]\d{1,2}):?(\d{2})?$", tz)
if match:
hours = int(match.group(1))
minutes = int(match.group(2)) if match.group(2) else 0
return pytz.FixedOffset(hours * 60 + minutes)
full_tz = SHORT_TZ_MAP.get(tz, tz)
return pytz.timezone(full_tz) if isinstance(full_tz, str) else None
@app.route("/api/v1/convert", methods=["POST"])
def convert():
data = request.json
from_tz = get_full_timezone(data.get("from_tz").replace("_", "/"))
to_tz = get_full_timezone(data.get("to_tz").replace("_", "/"))
time = data.get("time")
if from_tz is None or to_tz is None:
return jsonify({"error": "Invalid timezone"}), 400
# Parse the input time
try:
input_time = datetime.strptime(time, "%Y-%m-%dT%H:%M:%S")
except ValueError:
input_time = datetime.strptime(time, "%Y-%m-%dT%H:%M")
try:
# Get the timezone
from_tz_time = from_tz.localize(input_time)
# Convert the time
to_tz_time = from_tz_time.astimezone(to_tz)
except Exception as e:
return jsonify({"error": str(e)}), 400
return jsonify(
{
"from": from_tz_time.strftime("%Y-%m-%d %H:%M:%S %Z"),
"to": to_tz_time.strftime("%Y-%m-%d %H:%M:%S %Z"),
}
)
@app.route("/api/v1/convert_multiple", methods=["POST"])
def convert_multiple():
data = request.json
from_tz = get_full_timezone(data.get("from_tz").replace("_", "/"))
time = data.get("time")
to_tz_list = [(tz, get_full_timezone(tz.replace("_", "/"))) for tz in data.get("to_tzs") if len(tz) > 0]
if from_tz is None or any(tz[1] is None for tz in to_tz_list):
return jsonify({"error": "Invalid timezone"}), 400
# Parse the input time
try:
input_time = datetime.strptime(time, "%Y-%m-%dT%H:%M:%S")
except ValueError:
input_time = datetime.strptime(time, "%Y-%m-%dT%H:%M")
try:
# Get the timezone
from_tz_time = from_tz.localize(input_time)
# Convert the time to each timezone in the list
results = {}
for original_tz, tz in to_tz_list:
to_tz_time = from_tz_time.astimezone(tz)
offset = to_tz_time.strftime('%z')
offset_formatted = f"UTC{offset[:3]}:{offset[3:]}" if offset else ""
tz_name = to_tz_time.tzname() if to_tz_time.tzname() else offset_formatted
results[original_tz] = f"{to_tz_time.strftime('%Y-%m-%d %H:%M:%S')} {tz_name}"
except Exception as e:
return jsonify({"error": str(e)}), 400
return jsonify(results)
@app.route("/<path:path>")
def catch_all(path: str):

View File

@ -11,6 +11,52 @@ h1 {
margin-top: 10%;
text-align: center;
}
form {
margin: 20px 0;
}
label {
display: block;
margin: 10px 0 5px;
}
input {
padding: 5px;
width: 80%;
max-width: 300px;
}
button {
margin-top: 10px;
padding: 10px 20px;
background-color: #ffffff;
color: #000000;
border: none;
cursor: pointer;
}
button:hover {
background-color: #dddddd;
}
#results {
margin-top: 20px;
text-align: left;
}
.results-container {
margin-top: 20px;
text-align: center;
}
.results-table {
width: 80%;
margin: 0 auto;
border-collapse: collapse;
}
.results-table th, .results-table td {
border: 1px solid #ffffff;
padding: 10px;
}
.results-table th {
background-color: #333333;
}
.results-table td {
background-color: #444444;
}
a {
color: #ffffff;
text-decoration: none;

View File

@ -0,0 +1,80 @@
document.getElementById('time-converter-form').addEventListener('submit', function(event) {
event.preventDefault();
const fromTz = document.getElementById('from-tz').value;
const time = document.getElementById('time').value;
const toTzs = document.getElementById('to-tzs').value.split(',');
// Update URL with query parameters
const params = new URLSearchParams({
from_tz: fromTz,
time: time,
to_tzs: toTzs.join(',')
});
window.history.replaceState({}, '', `${window.location.pathname}?${params.toString()}`);
fetch('/api/v1/convert_multiple', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
from_tz: fromTz,
time: time,
to_tzs: toTzs
})
})
.then(response => response.json())
.then(data => {
const resultsDiv = document.getElementById('results');
resultsDiv.innerHTML = '<h2>Converted Times:</h2>';
const table = document.createElement('table');
table.classList.add('results-table');
const thead = document.createElement('thead');
const tbody = document.createElement('tbody');
const headerRow = document.createElement('tr');
const th1 = document.createElement('th');
th1.textContent = 'Timezone';
const th2 = document.createElement('th');
th2.textContent = 'Converted Time';
headerRow.appendChild(th1);
headerRow.appendChild(th2);
thead.appendChild(headerRow);
for (const [tz, convertedTime] of Object.entries(data)) {
const row = document.createElement('tr');
const cell1 = document.createElement('td');
cell1.textContent = tz;
const cell2 = document.createElement('td');
cell2.textContent = convertedTime;
row.appendChild(cell1);
row.appendChild(cell2);
tbody.appendChild(row);
}
table.appendChild(thead);
table.appendChild(tbody);
resultsDiv.appendChild(table);
})
.catch(error => {
console.error('Error:', error);
});
});
// Load parameters from URL if available
window.addEventListener('load', function() {
const params = new URLSearchParams(window.location.search);
const fromTz = params.get('from_tz');
const time = params.get('time');
const toTzs = params.get('to_tzs');
if (fromTz && time && toTzs) {
document.getElementById('from-tz').value = fromTz;
document.getElementById('time').value = time;
document.getElementById('to-tzs').value = toTzs;
// Trigger form submission to display results
document.getElementById('time-converter-form').dispatchEvent(new Event('submit'));
}
});

View File

@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nathan.Woodburn/</title>
<title>Time Converter</title>
<link rel="icon" href="/assets/img/favicon.png" type="image/png">
<link rel="stylesheet" href="/assets/css/index.css">
</head>
@ -12,8 +12,22 @@
<body>
<div class="spacer"></div>
<div class="centre">
<h1>Nathan.Woodburn/</h1>
<h1>Time Converter</h1>
<form id="time-converter-form">
<label for="from-tz">From Timezone:</label>
<input type="text" id="from-tz" name="from-tz" required>
<br>
<label for="time">Time:</label>
<input type="datetime-local" id="time" name="time" required>
<br>
<label for="to-tzs">To Timezones (comma separated):</label>
<input type="text" id="to-tzs" name="to-tzs" required>
<br>
<button type="submit">Convert</button>
</form>
<div id="results" class="results-container"></div>
</div>
<script src="/assets/js/index.js"></script>
</body>
</html>