display last updated timestamp parsed from Excel filename
This commit is contained in:
parent
078050dce6
commit
06ead18b38
2 changed files with 59 additions and 2 deletions
39
main.go
39
main.go
|
|
@ -9,8 +9,11 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/xuri/excelize/v2"
|
"github.com/xuri/excelize/v2"
|
||||||
)
|
)
|
||||||
|
|
@ -36,6 +39,11 @@ type Station struct {
|
||||||
Diesel float64 `json:"diesel"`
|
Diesel float64 `json:"diesel"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StationsResponse struct {
|
||||||
|
LastUpdated string `json:"lastUpdated"`
|
||||||
|
Stations []Station `json:"stations"`
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
port := os.Getenv("PORT")
|
port := os.Getenv("PORT")
|
||||||
if port == "" {
|
if port == "" {
|
||||||
|
|
@ -61,9 +69,14 @@ func handleStations(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resp := StationsResponse{
|
||||||
|
LastUpdated: parseTimestampFromURL(dataURL),
|
||||||
|
Stations: stations,
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Header().Set("Cache-Control", "public, max-age=3600")
|
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||||
json.NewEncoder(w).Encode(stations)
|
json.NewEncoder(w).Encode(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchAndParse() ([]Station, error) {
|
func fetchAndParse() ([]Station, error) {
|
||||||
|
|
@ -169,3 +182,27 @@ func parsePrice(s string) float64 {
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tsRegex = regexp.MustCompile(`(\d{14})`)
|
||||||
|
|
||||||
|
// parseTimestampFromURL extracts a YYYYMMDDHHmmSS timestamp from the URL
|
||||||
|
// filename and returns it as a human-readable string.
|
||||||
|
func parseTimestampFromURL(rawURL string) string {
|
||||||
|
base := path.Base(rawURL)
|
||||||
|
match := tsRegex.FindString(base)
|
||||||
|
if match == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
loc, err := time.LoadLocation("America/Montreal")
|
||||||
|
if err != nil {
|
||||||
|
loc = time.UTC
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := time.ParseInLocation("20060102150405", match, loc)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,13 @@
|
||||||
.legend-labels { display: flex; justify-content: space-between; font-size: 11px; color: #666; }
|
.legend-labels { display: flex; justify-content: space-between; font-size: 11px; color: #666; }
|
||||||
#stats { margin-top: 8px; font-size: 11px; color: #888; }
|
#stats { margin-top: 8px; font-size: 11px; color: #888; }
|
||||||
|
|
||||||
|
#last-updated {
|
||||||
|
position: fixed; bottom: 8px; left: 56px;
|
||||||
|
background: white; padding: 5px 10px;
|
||||||
|
border-radius: 6px; box-shadow: 0 1px 4px rgba(0,0,0,0.15);
|
||||||
|
z-index: 2000; font-size: 11px; color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
/* Colored pin markers */
|
/* Colored pin markers */
|
||||||
.pin-icon {
|
.pin-icon {
|
||||||
filter: drop-shadow(0 1px 3px rgba(0,0,0,0.35));
|
filter: drop-shadow(0 1px 3px rgba(0,0,0,0.35));
|
||||||
|
|
@ -89,6 +96,8 @@
|
||||||
<div id="visible-count"></div>
|
<div id="visible-count"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="last-updated"></div>
|
||||||
|
|
||||||
<div id="legend">
|
<div id="legend">
|
||||||
<h3>Prix régulier (¢/L)</h3>
|
<h3>Prix régulier (¢/L)</h3>
|
||||||
<div class="legend-gradient"></div>
|
<div class="legend-gradient"></div>
|
||||||
|
|
@ -126,10 +135,21 @@
|
||||||
|
|
||||||
fetch('/api/stations')
|
fetch('/api/stations')
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(stations => {
|
.then(data => {
|
||||||
document.getElementById('loading').style.display = 'none';
|
document.getElementById('loading').style.display = 'none';
|
||||||
|
const stations = data.stations;
|
||||||
allStations = stations;
|
allStations = stations;
|
||||||
|
|
||||||
|
// Last updated
|
||||||
|
if (data.lastUpdated) {
|
||||||
|
const d = new Date(data.lastUpdated);
|
||||||
|
document.getElementById('last-updated').textContent =
|
||||||
|
'Dernière mise à jour: ' + d.toLocaleString('fr-CA', {
|
||||||
|
year: 'numeric', month: 'long', day: 'numeric',
|
||||||
|
hour: '2-digit', minute: '2-digit',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const prices = stations.map(s => s.regular).filter(p => p > 0);
|
const prices = stations.map(s => s.regular).filter(p => p > 0);
|
||||||
minPrice = Math.min(...prices);
|
minPrice = Math.min(...prices);
|
||||||
maxPrice = Math.max(...prices);
|
maxPrice = Math.max(...prices);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue