generated from nathanwoodburn/python-webserver-template
parent
66620204ac
commit
6d7019bba5
24
README.md
24
README.md
@ -1,3 +1,25 @@
|
|||||||
# python-webserver-template
|
# 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
|
flask
|
||||||
gunicorn
|
gunicorn
|
||||||
requests
|
requests
|
||||||
python-dotenv
|
python-dotenv
|
||||||
|
pytz
|
118
server.py
118
server.py
@ -13,8 +13,12 @@ from flask import (
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
import dotenv
|
import dotenv
|
||||||
|
from pytz import timezone
|
||||||
|
import pytz
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
dotenv.load_dotenv()
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
@ -76,6 +80,118 @@ def wellknown(path):
|
|||||||
def index():
|
def index():
|
||||||
return render_template("index.html")
|
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>")
|
@app.route("/<path:path>")
|
||||||
def catch_all(path: str):
|
def catch_all(path: str):
|
||||||
|
@ -11,6 +11,52 @@ h1 {
|
|||||||
margin-top: 10%;
|
margin-top: 10%;
|
||||||
text-align: center;
|
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 {
|
a {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
text-decoration: none;
|
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>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<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="icon" href="/assets/img/favicon.png" type="image/png">
|
||||||
<link rel="stylesheet" href="/assets/css/index.css">
|
<link rel="stylesheet" href="/assets/css/index.css">
|
||||||
</head>
|
</head>
|
||||||
@ -12,8 +12,22 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<div class="centre">
|
<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>
|
</div>
|
||||||
|
<script src="/assets/js/index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
Loading…
Reference in New Issue
Block a user