From 35d204d9b05bbfe05504fa01c734ecaa3692beb1 Mon Sep 17 00:00:00 2001 From: lelo Date: Wed, 2 Apr 2025 06:36:03 +0000 Subject: [PATCH] fix timezone-aware timestamp --- analytics.py | 25 +++++++++++++++------ convert_db.py | 48 ++++++++++++++++++++++++++++++++++++++++ templates/dashboard.html | 14 ++++++------ 3 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 convert_db.py diff --git a/analytics.py b/analytics.py index 889b7b7..c67ddfc 100644 --- a/analytics.py +++ b/analytics.py @@ -1,6 +1,6 @@ import sqlite3 from flask import render_template, request, session -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import geoip2.database from auth import require_secret import os @@ -59,7 +59,8 @@ def get_device_type(user_agent): def log_file_access(rel_path, filesize, mime, ip_address, user_agent, device_id, cached): """Insert a file access record into the database and prune entries older than 10 minutes.""" global file_access_temp - timestamp = datetime.now() + # Create a timezone-aware timestamp (local time with offset) + timestamp = datetime.now(timezone.utc).astimezone() iso_ts = timestamp.isoformat() with log_db: @@ -70,7 +71,7 @@ def log_file_access(rel_path, filesize, mime, ip_address, user_agent, device_id, ''', (iso_ts, rel_path, filesize, mime, ip_address, user_agent, device_id, cached)) # Remove entries older than 10 minutes - cutoff_time = datetime.now() - timedelta(minutes=10) + cutoff_time = datetime.now(timezone.utc).astimezone() - timedelta(minutes=10) file_access_temp[:] = [ entry for entry in file_access_temp if datetime.fromisoformat(entry[0]) >= cutoff_time @@ -81,9 +82,19 @@ def log_file_access(rel_path, filesize, mime, ip_address, user_agent, device_id, return True def return_file_access(): - """Return recent file access logs from memory.""" + """Return recent file access logs from memory (the last 10 minutes).""" global file_access_temp - return file_access_temp + if file_access_temp: + # Create a timezone-aware cutoff time + cutoff_time = datetime.now(timezone.utc).astimezone() - timedelta(minutes=10) + # Only keep entries with timestamps greater than or equal to cutoff_time + file_access_temp[:] = [ + entry for entry in file_access_temp + if datetime.fromisoformat(entry[0]) >= cutoff_time + ] + return file_access_temp + else: + return [] @require_secret def connections(): @@ -162,7 +173,7 @@ def dashboard(): if session['timeframe'] == 'last24hours': # Group by hour: substr(timestamp, 12, 2) -> HH query = f''' - SELECT substr(timestamp, 1, 13) AS bucket, COUNT(DISTINCT device_id) AS count + SELECT strftime('%Y-%m-%dT%H:00:00Z', replace(timestamp, 'T', ' ')) AS bucket, COUNT(DISTINCT device_id) AS count FROM file_access_log WHERE timestamp >= ? {filetype_filter_sql} GROUP BY bucket @@ -207,7 +218,7 @@ def dashboard(): if session['timeframe'] == 'last24hours': # Hour: substr(timestamp, 12, 2) -> HH query = f''' - SELECT substr(timestamp, 1, 13) AS bucket, COUNT(*) AS count + SELECT strftime('%Y-%m-%dT%H:00:00Z', replace(timestamp, 'T', ' ')) AS bucket, COUNT(*) AS count FROM file_access_log WHERE timestamp >= ? {filetype_filter_sql} GROUP BY bucket diff --git a/convert_db.py b/convert_db.py new file mode 100644 index 0000000..fcf3834 --- /dev/null +++ b/convert_db.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import sqlite3 +from datetime import datetime, timezone + +# Name of your SQLite database file +DB_NAME = 'access_log.db' + +def convert_naive_to_timezone_aware(naive_ts): + """ + Convert a naive ISO timestamp (assumed to be in local time) into a timezone-aware timestamp. + """ + try: + dt = datetime.fromisoformat(naive_ts) + except Exception as e: + print(f"Error parsing timestamp {naive_ts}: {e}") + return naive_ts # If parsing fails, return the original value + + # If the timestamp is naive (i.e., no tzinfo), make it timezone-aware by assuming it's local time. + if dt.tzinfo is None: + # Get the local timezone info using the current time's offset. + local_tz = datetime.now(timezone.utc).astimezone().tzinfo + dt = dt.replace(tzinfo=local_tz) + return dt.isoformat() + else: + # Timestamp is already timezone-aware + return naive_ts + +def update_database(): + conn = sqlite3.connect(DB_NAME) + cursor = conn.cursor() + + # Fetch all records' IDs and timestamps + cursor.execute("SELECT id, timestamp FROM file_access_log") + rows = cursor.fetchall() + updated_count = 0 + + for rec_id, ts in rows: + new_ts = convert_naive_to_timezone_aware(ts) + if new_ts != ts: + cursor.execute("UPDATE file_access_log SET timestamp = ? WHERE id = ?", (new_ts, rec_id)) + updated_count += 1 + + conn.commit() + conn.close() + print(f"Updated {updated_count} records to timezone-aware timestamps.") + +if __name__ == "__main__": + update_database() diff --git a/templates/dashboard.html b/templates/dashboard.html index b486d4c..05808dd 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -216,20 +216,20 @@ const timeframe = "{{ timeframe }}"; // e.g., 'last24hours', '7days', '30days', or '365days' const shiftedLabels = timeframeData.map(item => { if (timeframe === 'last24hours') { - // item.bucket will be something like "2025-04-01T15" - const bucketDate = new Date(item.bucket + ":00:00"); // Convert to a full datetime by appending minutes and seconds. + // item.bucket is now in the format "YYYY-MM-DDTHH:00:00Z" + const bucketDate = new Date(item.bucket); const now = new Date(); - - // Check if this bucket corresponds to the current hour + + // Check if this bucket corresponds to the current hour (in client's local time) const isCurrentHour = bucketDate.getFullYear() === now.getFullYear() && bucketDate.getMonth() === now.getMonth() && bucketDate.getDate() === now.getDate() && bucketDate.getHours() === now.getHours(); - - // If it is the current hour, use the current time as the end; otherwise, add one hour. + + // For the current hour, use the current time as the bucket end. const bucketEnd = isCurrentHour ? now : new Date(bucketDate.getTime() + 3600 * 1000); - + return `${bucketDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} - ${bucketEnd.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`; } else if (timeframe === '7days' || timeframe === '30days') { const localDate = new Date(item.bucket);