generated from nathanwoodburn/python-webserver-template
parent
66620204ac
commit
6d7019bba5
24
README.md
24
README.md
@ -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"]
|
||||
}'
|
||||
```
|
@ -1,4 +1,5 @@
|
||||
flask
|
||||
gunicorn
|
||||
requests
|
||||
python-dotenv
|
||||
python-dotenv
|
||||
pytz
|
118
server.py
118
server.py
@ -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):
|
||||
|
@ -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;
|
||||
|
80
templates/assets/js/index.js
Normal file
80
templates/assets/js/index.js
Normal 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'));
|
||||
}
|
||||
});
|
@ -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>
|
Loading…
Reference in New Issue
Block a user