From 933004e792cd5d7d6ff067aab4b2abe1e6be9919 Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Wed, 11 Feb 2026 03:56:37 +0000 Subject: [PATCH] fixes --- front/js/common.js | 8 +++---- server/models/event_instance.py | 2 +- server/utils/datetime_utils.py | 21 +++++++++++-------- test/api_endpoints/test_sessions_endpoints.py | 2 +- test/server/test_datetime_utils.py | 16 +++++++------- 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/front/js/common.js b/front/js/common.js index 76f2d193..4753db38 100755 --- a/front/js/common.js +++ b/front/js/common.js @@ -452,11 +452,11 @@ function localizeTimestamp(input) { // - Has GMT±offset: "Wed Feb 11 2026 12:34:12 GMT+1100 (...)" // - Has offset at end: "2026-02-11 11:37:02+11:00" // - Has timezone name in parentheses: "(Australian Eastern Daylight Time)" - const hasOffset = /Z$/i.test(str.trim()) || + const hasOffset = /Z$/i.test(str.trim()) || /GMT[+-]\d{2,4}/.test(str) || /[+-]\d{2}:?\d{2}$/.test(str.trim()) || /\([^)]+\)$/.test(str.trim()); - + // ⚠️ CRITICAL: All DB timestamps are stored in UTC without timezone markers. // If no offset is present, we must explicitly mark it as UTC by appending 'Z' // so JavaScript doesn't interpret it as local browser time. @@ -464,9 +464,9 @@ function localizeTimestamp(input) { if (!hasOffset) { // Ensure proper ISO format before appending Z // Replace space with 'T' if needed: "2026-02-11 11:37:02" → "2026-02-11T11:37:02Z" - isoStr = isoStr.replace(' ', 'T') + 'Z'; + isoStr = isoStr.trim().replace(/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})$/, '$1T$2') + 'Z'; } - + const date = new Date(isoStr); if (!isFinite(date)) { console.error(`ERROR: Couldn't parse date: '${str}' with TIMEZONE ${tz}`); diff --git a/server/models/event_instance.py b/server/models/event_instance.py index 26e1fda1..7e943c15 100644 --- a/server/models/event_instance.py +++ b/server/models/event_instance.py @@ -93,7 +93,7 @@ class EventInstance: eve_EventType, eve_AdditionalInfo, eve_PendingAlertEmail, eve_PairEventRowid ) VALUES (?,?,?,?,?,?,?) - """, (mac, ip, timeNowUTC(as_string=False), eventType, info, + """, (mac, ip, timeNowUTC(), eventType, info, 1 if pendingAlert else 0, pairRow)) conn.commit() conn.close() diff --git a/server/utils/datetime_utils.py b/server/utils/datetime_utils.py index bdffe434..2f1bf7ac 100644 --- a/server/utils/datetime_utils.py +++ b/server/utils/datetime_utils.py @@ -27,18 +27,18 @@ DATETIME_REGEX = re.compile(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$') def timeNowUTC(as_string=True): """ Return the current time in UTC. - + This is the ONLY function that calls datetime.datetime.now() in the entire codebase. All timestamps stored in the database MUST use UTC format. - + Args: as_string (bool): If True, returns formatted string for DB storage. If False, returns datetime object for operations. - + Returns: str: UTC timestamp as 'YYYY-MM-DD HH:MM:SS' when as_string=True datetime.datetime: UTC datetime object when as_string=False - + Examples: timeNowUTC() → '2025-11-04 07:09:11' (for DB writes) timeNowUTC(as_string=False) → datetime.datetime(2025, 11, 4, 7, 9, 11, tzinfo=UTC) @@ -48,9 +48,12 @@ def timeNowUTC(as_string=True): def get_timezone_offset(): - now = timeNowUTC(as_string=False).replace(tzinfo=conf.tz) if conf.tz else timeNowUTC(as_string=False) - offset_hours = now.utcoffset().total_seconds() / 3600 if now.utcoffset() else 0 - offset_formatted = "{:+03d}:{:02d}".format(int(offset_hours), int((offset_hours % 1) * 60)) + if conf.tz: + now = timeNowUTC(as_string=False).astimezone(conf.tz) + offset_hours = now.utcoffset().total_seconds() / 3600 + else: + offset_hours = 0 + offset_formatted = "{:+03d}:{:02d}".format(int(offset_hours), int((offset_hours % 1) * 60)) return offset_formatted @@ -107,7 +110,7 @@ def normalizeTimeStamp(inputTimeStamp): # ------------------------------------------------------------------------------------------- def format_date_iso(date_val: str) -> Optional[str]: """Ensures a date string from DB is returned as a proper ISO string with TZ. - + Assumes DB timestamps are stored in UTC and converts them to user's configured timezone. """ if not date_val: @@ -173,7 +176,7 @@ def parse_datetime(dt_str): def format_date(date_str: str) -> str: """Format a date string from DB for display. - + Assumes DB timestamps are stored in UTC and converts them to user's configured timezone. """ try: diff --git a/test/api_endpoints/test_sessions_endpoints.py b/test/api_endpoints/test_sessions_endpoints.py index 778525f4..72fd47e5 100644 --- a/test/api_endpoints/test_sessions_endpoints.py +++ b/test/api_endpoints/test_sessions_endpoints.py @@ -188,7 +188,7 @@ def test_get_sessions_calendar(client, api_token, test_mac): Cleans up test sessions after test. """ # --- Setup: create two sessions for the test MAC --- - now = datetime.now() + now = timeNowUTC(as_string=False) start1 = (now - timedelta(days=2)).isoformat(timespec="seconds") end1 = (now - timedelta(days=1, hours=20)).isoformat(timespec="seconds") diff --git a/test/server/test_datetime_utils.py b/test/server/test_datetime_utils.py index 12abad72..0161e765 100644 --- a/test/server/test_datetime_utils.py +++ b/test/server/test_datetime_utils.py @@ -44,7 +44,7 @@ class TestTimeNowUTC: def test_timeNowUTC_datetime_has_UTC_timezone(self): """Test that datetime object has UTC timezone""" result = timeNowUTC(as_string=False) - assert result.tzinfo is datetime.UTC or result.tzinfo is not None + assert result.tzinfo is datetime.UTC def test_timeNowUTC_datetime_no_microseconds(self): """Test that datetime object has microseconds set to 0""" @@ -55,7 +55,7 @@ class TestTimeNowUTC: """Test that string and datetime modes return consistent values""" dt_obj = timeNowUTC(as_string=False) str_result = timeNowUTC(as_string=True) - + # Convert datetime to string and compare (within 1 second tolerance) dt_str = dt_obj.strftime(DATETIME_PATTERN) # Parse both to compare timestamps @@ -68,7 +68,7 @@ class TestTimeNowUTC: """Test that timeNowUTC() returns actual UTC time, not local time""" utc_now = datetime.datetime.now(datetime.UTC).replace(microsecond=0) result = timeNowUTC(as_string=False) - + # Should be within 1 second diff = abs((utc_now - result).total_seconds()) assert diff <= 1 @@ -77,10 +77,10 @@ class TestTimeNowUTC: """Test that string result matches datetime object conversion""" dt_obj = timeNowUTC(as_string=False) str_result = timeNowUTC(as_string=True) - + # Convert datetime to string using same format expected = dt_obj.strftime(DATETIME_PATTERN) - + # Should be same or within 1 second t1 = datetime.datetime.strptime(expected, DATETIME_PATTERN) t2 = datetime.datetime.strptime(str_result, DATETIME_PATTERN) @@ -95,12 +95,12 @@ class TestTimeNowUTC: def test_timeNowUTC_multiple_calls_increase(self): """Test that subsequent calls return increasing timestamps""" import time - + t1_str = timeNowUTC() time.sleep(0.1) t2_str = timeNowUTC() - + t1 = datetime.datetime.strptime(t1_str, DATETIME_PATTERN) t2 = datetime.datetime.strptime(t2_str, DATETIME_PATTERN) - + assert t2 >= t1