Compare commits

..
10 Commits
Author SHA1 Message Date
James Woglom 68dcb25911 v2.3.4 2025-12-14 23:59:06 -05:00
James Woglom 795361c6ad timezone tests 2025-12-14 23:58:35 -05:00
James Woglom c9190df50a timestamp override in tests 2025-12-14 23:56:30 -05:00
James Woglom 15160537f5 add process_cgm_reading tests 2025-12-14 23:52:55 -05:00
Andy LowandJames Woglom 1f1e755503 Add timezone support to timestamp calculation
Update timestamp handling to include timezone information.
2025-12-14 23:51:35 -05:00
James Woglom aea3f6bd6b v2.3.3 2025-11-23 00:03:34 -05:00
James Woglom 2bf40cac28 fix TypeError: can't compare offset-naive and offset-aware datetimes 2025-11-23 00:03:16 -05:00
James Woglom 73dabe5ead v2.3.2 2025-11-18 14:01:18 -05:00
James Woglom e848a8482d add new process test for tz change 2025-11-18 14:00:54 -05:00
James Woglom 9c06efb045 when pump TZ changes moves date into the future, do not add "current" basal to extend to that timestamp 2025-11-18 13:59:17 -05:00
6 changed files with 520 additions and 4 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[metadata]
name = tconnectsync
version = 2.3.1
version = 2.3.4
author = James Woglom
author_email = j@wogloms.net
description = Syncs Tandem Source (formerly t:connect) insulin pump data to Nightscout for the t:slim X2
+1
View File
@@ -6,6 +6,7 @@ from ..secret import TIMEZONE_NAME
from dataclasses import dataclass
EVENT_LEN = 26
# Big endian
UINT16 = '>H'
UINT32 = '>I'
TANDEM_EPOCH = 1199145600
+6 -1
View File
@@ -1,5 +1,6 @@
import logging
import collections
import arrow
from ...features import DEVICE_STATUS, DEFAULT_FEATURES
from ...eventparser import events as eventtypes
@@ -81,7 +82,11 @@ class ProcessTimeRange:
c = self.event_classes[clazz](self.tconnect, self.nightscout, self.tconnect_device_id, self.pretend, self.features)
if c.enabled():
logger.info("%s is enabled from features %s" % (clazz, self.features))
ns_entries = c.process(events, events_first_time, events_last_time)
# Cap events_last_time at time_end to handle pump clock drift
# Ensure time_end is timezone-aware for comparison
time_end_aware = arrow.get(time_end)
capped_time_end = min(events_last_time, time_end_aware) if events_last_time else time_end_aware
ns_entries = c.process(events, events_first_time, capped_time_end)
w = c.write(ns_entries)
if w:
processed_count += w
@@ -3,6 +3,7 @@ import arrow
from ...features import DEFAULT_FEATURES
from ... import features
from ... import secret
from ...eventparser.generic import Events, decode_raw_events, EVENT_LEN
from ...eventparser.utils import bitmask_to_list
from ...eventparser.raw_event import TANDEM_EPOCH
@@ -16,12 +17,13 @@ from ...parser.nightscout import (
logger = logging.getLogger(__name__)
class ProcessCGMReading:
def __init__(self, tconnect, nightscout, tconnect_device_id, pretend, features=DEFAULT_FEATURES):
def __init__(self, tconnect, nightscout, tconnect_device_id, pretend, features=DEFAULT_FEATURES, timezone=None):
self.tconnect = tconnect
self.nightscout = nightscout
self.tconnect_device_id = tconnect_device_id
self.pretend = pretend
self.features = features
self.timezone = timezone or secret.TIMEZONE_NAME
def enabled(self):
return features.CGM in self.features
@@ -66,7 +68,7 @@ class ProcessCGMReading:
def timestamp_for(self, event):
# For backfills the time the event was added to the pump's event store
# might not be the time it actually occurred, so we use the egvTimestamp
return arrow.get(TANDEM_EPOCH + event.egvTimestamp)
return arrow.get(TANDEM_EPOCH + event.egvTimestamp, tzinfo='UTC').replace(tzinfo=self.timezone)
def to_nsentry(self, event):
return NightscoutEntry.entry(
+150
View File
@@ -0,0 +1,150 @@
#!/usr/bin/env python3
import unittest
import arrow
from tconnectsync.sync.tandemsource.process import ProcessTimeRange
from tconnectsync.eventparser import events as eventtypes
from tconnectsync.eventparser.generic import Event
from ...api.fake import TConnectApi
from ...nightscout_fake import NightscoutApi
from ...secrets import build_secrets
# Raw event bytes for testing
# LidBasalDelivery (id=279) at 2025-11-18 13:12:40-05:00, rate=800 milliunits
BASAL_EVENT_1 = b'\x01\x17!\xa2\xeeH\x00\x01\x86\xa1\x00\x00\x00\x03\x03 \x03 \x00\x00\x03 \x00\x00\x00\x00'
# LidBasalDelivery (id=279) at 2025-11-18 13:17:40-05:00, rate=800 milliunits
BASAL_EVENT_2 = b'\x01\x17!\xa2\xeft\x00\x01\x86\xa2\x00\x00\x00\x03\x03 \x03 \x00\x00\x03 \x00\x00\x00\x00'
# LidCgmDataG7 (id=399) at 2025-11-19 03:00:00-05:00 (future timestamp for testing clock drift)
CGM_EVENT_FUTURE = b'\x01\x8f!\xa3\xb00\x00\x03\rA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
# LidCgmDataG7 (id=399) at 2025-11-18 13:22:40-05:00 (normal timestamp)
CGM_EVENT_NORMAL = b'\x01\x8f!\xa2\xf0\xa0\x00\x03\rB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
class FakeTandemSourceApi:
"""Fake TandemSource API for testing"""
def __init__(self):
self.events = []
def pump_events(self, device_id, time_start, time_end, fetch_all_event_types=False):
return self.events
def pump_event_metadata(self):
"""Return empty metadata for testing"""
return {}
def needs_relogin(self):
return False
class TestProcessTimeRangeBasalDuration(unittest.TestCase):
"""Test that basal duration calculation caps events_last_time at time_end"""
maxDiff = None
def setUp(self):
self.tconnect = TConnectApi()
self.tconnect._tandemsource = FakeTandemSourceApi()
self.nightscout = NightscoutApi()
self.nightscout.last_uploaded_entry = lambda *args, **kwargs: None
self.tconnectDevice = {
'tconnectDeviceId': 'test-device-123',
'maxDateWithEvents': '2025-11-18T13:00:00-05:00'
}
self.secret = build_secrets(
FETCH_ALL_EVENT_TYPES=False
)
self.process = ProcessTimeRange(
self.tconnect,
self.nightscout,
self.tconnectDevice,
pretend=False,
secret=self.secret
)
def test_basal_duration_capped_when_future_event_timestamp(self):
"""Test that basal duration is capped at time_end when last event is in the future"""
# Create events from raw bytes
basal_event_1 = Event(BASAL_EVENT_1) # 2025-11-18 13:12:40-05:00
basal_event_2 = Event(BASAL_EVENT_2) # 2025-11-18 13:17:40-05:00
future_cgm_event = Event(CGM_EVENT_FUTURE) # 2025-11-19 03:00:00-05:00 (future)
self.assertEqual(type(basal_event_1), eventtypes.LidBasalDelivery)
self.assertEqual(type(basal_event_2), eventtypes.LidBasalDelivery)
self.assertEqual(type(future_cgm_event), eventtypes.LidCgmDataG7)
# Set up the fake API to return these events
self.tconnect._tandemsource.events = [basal_event_1, basal_event_2, future_cgm_event]
# time_end is "now" at 13:29:00
time_start = arrow.get('2025-11-18T13:00:00-05:00')
time_end = arrow.get('2025-11-18T13:29:00-05:00')
# Process the events
count, last_seqnum = self.process.process(time_start, time_end)
# Verify that basal events were uploaded
self.assertEqual(len(self.nightscout.uploaded_entries['treatments']), 2)
# First basal: from 13:12:40 to 13:17:40 = 5 minutes
basal_1 = self.nightscout.uploaded_entries['treatments'][0]
self.assertEqual(basal_1['eventType'], 'Temp Basal')
self.assertEqual(basal_1['created_at'], '2025-11-18 13:12:40-05:00')
self.assertEqual(basal_1['duration'], 5.0)
# Second basal: from 13:17:40 to time_end (13:29:00) = 11.333... minutes
# NOT from 13:17:40 to future_cgm_event (03:00:00) which would be ~822 minutes
basal_2 = self.nightscout.uploaded_entries['treatments'][1]
self.assertEqual(basal_2['eventType'], 'Temp Basal')
self.assertEqual(basal_2['created_at'], '2025-11-18 13:17:40-05:00')
# Duration should be capped at time_end, not extended to future CGM event
expected_duration = (time_end - arrow.get('2025-11-18T13:17:40-05:00')).seconds / 60
self.assertAlmostEqual(basal_2['duration'], expected_duration, places=2)
# Verify it's NOT the inflated duration to the future event
self.assertLess(basal_2['duration'], 100) # Should be ~11 min, not ~822 min
def test_basal_duration_normal_when_all_events_in_past(self):
"""Test that basal duration uses events_last_time when it's <= time_end"""
# Create events from raw bytes
basal_event_1 = Event(BASAL_EVENT_1) # 2025-11-18 13:12:40-05:00
basal_event_2 = Event(BASAL_EVENT_2) # 2025-11-18 13:17:40-05:00
cgm_event = Event(CGM_EVENT_NORMAL) # 2025-11-18 13:22:40-05:00 (normal)
self.assertEqual(type(basal_event_1), eventtypes.LidBasalDelivery)
self.assertEqual(type(basal_event_2), eventtypes.LidBasalDelivery)
self.assertEqual(type(cgm_event), eventtypes.LidCgmDataG7)
# Set up the fake API to return these events
self.tconnect._tandemsource.events = [basal_event_1, basal_event_2, cgm_event]
time_start = arrow.get('2025-11-18T13:00:00-05:00')
time_end = arrow.get('2025-11-18T13:29:00-05:00')
# Process the events
count, last_seqnum = self.process.process(time_start, time_end)
# Verify that basal events were uploaded
self.assertEqual(len(self.nightscout.uploaded_entries['treatments']), 2)
# First basal: from 13:12:40 to 13:17:40 = 5 minutes
basal_1 = self.nightscout.uploaded_entries['treatments'][0]
self.assertEqual(basal_1['duration'], 5.0)
# Second basal: should use events_last_time (13:22:40) not time_end (13:29:00)
# Duration: 13:17:40 to 13:22:40 = 5 minutes
basal_2 = self.nightscout.uploaded_entries['treatments'][1]
expected_duration = (arrow.get('2025-11-18T13:22:40-05:00') - arrow.get('2025-11-18T13:17:40-05:00')).seconds / 60
self.assertAlmostEqual(basal_2['duration'], expected_duration, places=2)
self.assertEqual(basal_2['duration'], 5.0)
if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,358 @@
#!/usr/bin/env python3
import unittest
import arrow
from tconnectsync.sync.tandemsource.process_cgm_reading import ProcessCGMReading
from tconnectsync.eventparser import events as eventtypes
from tconnectsync.eventparser.generic import Event
from ...api.fake import TConnectApi
from ...nightscout_fake import NightscoutApi
# timestamp 2025-12-13T18:16:44-0500, value 80
G7_EVENT_1 = b'\x01\x8f!\xc4+\x0f\x00\x07\xb8\r\xf7\x01\x00\x00\x00P\xc7 !\xc4+\x0c\x00\x00\x19\xe1'
# timestamp 2025-12-13T18:21:44-0500, value 71
G7_EVENT_2 = b'\x01\x8f!\xc4,;\x00\x07\xb8\x1a\xf4\x01\x00\x00\x00G\xc1 !\xc4,8\x00\x00\x19\xe1'
# timestamp 2025-12-13T18:46:44-0500, value 119
G7_EVENT_3 = b'\x01\x8f!\xc42\x17\x00\x07\xb8\xd6\x13\x01\x00\x00\x00w\xc1 !\xc42\x14\x00\x00\x19\xe1'
# timestamp 2025-12-13T18:51:44-0500, value 105
G7_EVENT_4 = b'\x01\x8f!\xc43C\x00\x07\xb8\xea\x13\x01\x00\x00\x00\x81\xbf !\xc43@\x00\x00\x19\xe1'
# timestamp 2025-12-13T18:56:44-0500, value 98
G7_EVENT_5 = b"\x01\x8f!\xc45\x9b\x00\x07\xb9\'\x11\x01\x00\x00\x00\x92\xbb !\xc45\x98\x00\x00\x19\xe1"
class TestProcessCGMReadingG7(unittest.TestCase):
"""Test with only G7 reading data"""
maxDiff = None
def setUp(self):
self.tconnect = TConnectApi()
self.nightscout = NightscoutApi()
self.nightscout.last_uploaded_bg_entry = lambda *args, **kwargs: None
self.tconnect_device_id = 'abcdef'
self.process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False, timezone='America/New_York')
def test_single_g7_reading_no_last_uploaded(self):
"""Test processing a single G7 CGM reading with no prior uploads"""
events = [Event(G7_EVENT_1)]
self.assertEqual(type(events[0]), eventtypes.LidCgmDataG7)
self.assertEqual(events[0].raw.timestampRaw, 566504207)
self.assertEqual(events[0].egvTimestamp, 566504204)
self.assertEqual(events[0].seqNum, 505869)
self.assertEqual(events[0].currentglucosedisplayvalue, 80)
self.assertEqual(events[0].rateRaw, -9)
p = self.process.process(events, time_start=None, time_end=None)
self.assertEqual(len(p), 1)
self.assertIn('sgv', p[0])
self.assertEqual(p[0]['sgv'], 80)
self.assertEqual(p[0]['dateString'], '2025-12-13T18:16:44-0500')
def test_multiple_g7_readings(self):
"""Test processing multiple G7 CGM readings"""
events = [
Event(G7_EVENT_1),
Event(G7_EVENT_2),
Event(G7_EVENT_3)
]
# Verify all events are LidCgmDataG7
for event in events:
self.assertEqual(type(event), eventtypes.LidCgmDataG7)
self.assertEqual(events[0].currentglucosedisplayvalue, 80)
self.assertEqual(events[1].currentglucosedisplayvalue, 71)
self.assertEqual(events[2].currentglucosedisplayvalue, 119)
p = self.process.process(events, time_start=None, time_end=None)
self.assertEqual(len(p), 3)
# Check glucose values
self.assertEqual(p[0]['sgv'], 80)
self.assertEqual(p[0]['dateString'], '2025-12-13T18:16:44-0500')
self.assertEqual(p[1]['sgv'], 71)
self.assertEqual(p[1]['dateString'], '2025-12-13T18:21:44-0500')
self.assertEqual(p[2]['sgv'], 119)
self.assertEqual(p[2]['dateString'], '2025-12-13T18:46:44-0500')
def test_g7_reading_with_last_uploaded(self):
"""Test that already uploaded readings are skipped"""
events = [
Event(G7_EVENT_1),
Event(G7_EVENT_2),
Event(G7_EVENT_3)
]
# Set last upload time to be between event 1 and event 2
event_1_time = self.process.timestamp_for(events[0])
event_2_time = self.process.timestamp_for(events[1])
# Set last upload to event 1's timestamp
self.nightscout.last_uploaded_bg_entry = lambda *args, **kwargs: {
'dateString': event_1_time.format()
}
p = self.process.process(events, time_start=None, time_end=None)
# Only events 2 and 3 should be processed (after the last upload time)
self.assertEqual(len(p), 2)
self.assertEqual(p[0]['sgv'], 71)
self.assertEqual(p[0]['dateString'], '2025-12-13T18:21:44-0500')
self.assertEqual(p[1]['sgv'], 119)
self.assertEqual(p[1]['dateString'], '2025-12-13T18:46:44-0500')
# timestamp 2022-01-28T00:01:09, value 75
G6_EVENT_1 = b'\x01\x00\x1ay\xaf\xc5\x00\x02\x00m\xfc\x01\x00\x00\x00K\xaf\x06\x1ay\xaf\xc5\x01\x00\x01\xe1'
# timestamp 2022-01-28T00:06:08, value 77
G6_EVENT_2 = b'\x01\x00\x1ay\xb0\xf0\x00\x02\x00v\xfd\x01\x00\x00\x00M\xa8\x06\x1ay\xb0\xf0\x01\x00\x01\xe1'
# timestamp 2022-01-28T01:51:07, value 76, BACKFILL
G6_EVENT_3 = b'\x01\x00\x1ay\xca\xb7\x00\x02\x01>\x00\x02\x00\x00\x00L\xb5\x06\x1ay\xc9\x8b\x00\x01\x01\xe2'
# timestamp 2022-01-28T01:56:07, value 75
G6_EVENT_4 = b'\x01\x00\x1ay\xca\xb7\x00\x02\x01=\x01\x01\x00\x00\x00K\xb5\x06\x1ay\xca\xb7\x02\x00\x01\xe1'
# timestamp 2022-01-28T02:01:06, value 74
G6_EVENT_5 = b'\x01\x00\x1ay\xcb\xe2\x00\x02\x01G\x00\x01\x00\x00\x00J\xb5\x06\x1ay\xcb\xe2\x01\x00\x01\xe1'
class TestProcessCGMReadingG6(unittest.TestCase):
"""Test with only G6 reading data"""
maxDiff = None
def setUp(self):
self.tconnect = TConnectApi()
self.nightscout = NightscoutApi()
self.nightscout.last_uploaded_bg_entry = lambda *args, **kwargs: None
self.tconnect_device_id = 'abcdef'
self.process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False, timezone='America/New_York')
def test_single_g6_reading_no_last_uploaded(self):
"""Test processing a single G6 CGM reading with no prior uploads"""
events = [Event(G6_EVENT_1)]
# timestamp 2022-01-28T00:01:09 confirmed w/ tandem csv export
self.assertEqual(type(events[0]), eventtypes.LidCgmDataGxb)
self.assertEqual(events[0].raw.timestampRaw, 444182469)
self.assertEqual(events[0].egvTimestamp, 444182469)
self.assertEqual(events[0].currentglucosedisplayvalue, 75)
self.assertEqual(events[0].rateRaw, -4)
p = self.process.process(events, time_start=None, time_end=None)
self.assertEqual(len(p), 1)
self.assertIn('sgv', p[0])
self.assertEqual(p[0]['sgv'], 75)
self.assertEqual(p[0]['dateString'], '2022-01-28T00:01:09-0500')
def test_multiple_g6_readings(self):
"""Test processing multiple G6 CGM readings"""
events = [
Event(G6_EVENT_1),
Event(G6_EVENT_2),
Event(G6_EVENT_3)
]
# Verify all events are LidCgmDataGxb
for event in events:
self.assertEqual(type(event), eventtypes.LidCgmDataGxb)
self.assertEqual(events[0].currentglucosedisplayvalue, 75)
self.assertEqual(events[1].currentglucosedisplayvalue, 77)
self.assertEqual(events[2].currentglucosedisplayvalue, 76)
p = self.process.process(events, time_start=None, time_end=None)
self.assertEqual(len(p), 3)
self.assertEqual(p[0]['sgv'], 75)
self.assertEqual(p[0]['dateString'], '2022-01-28T00:01:09-0500')
self.assertEqual(p[1]['sgv'], 77)
self.assertEqual(p[1]['dateString'], '2022-01-28T00:06:08-0500')
self.assertEqual(p[2]['sgv'], 76)
self.assertEqual(p[2]['dateString'], '2022-01-28T01:51:07-0500')
def test_multiple_g6_readings_with_backfill(self):
"""Test processing multiple G6 CGM readings"""
events = [
Event(G6_EVENT_1),
Event(G6_EVENT_2),
Event(G6_EVENT_3),
Event(G6_EVENT_4),
Event(G6_EVENT_5)
]
# Verify all events are LidCgmDataGxb
for event in events:
self.assertEqual(type(event), eventtypes.LidCgmDataGxb)
self.assertEqual(events[0].currentglucosedisplayvalue, 75)
self.assertEqual(events[1].currentglucosedisplayvalue, 77)
self.assertEqual(events[2].currentglucosedisplayvalue, 76) # backfill
self.assertEqual(events[3].currentglucosedisplayvalue, 75)
self.assertEqual(events[4].currentglucosedisplayvalue, 74)
self.assertEqual(events[0].raw.timestampRaw, 444182469)
self.assertEqual(events[1].raw.timestampRaw, 444182768)
self.assertEqual(events[2].raw.timestampRaw, 444189367)
self.assertEqual(events[3].raw.timestampRaw, 444189367)
self.assertEqual(events[4].raw.timestampRaw, 444189666)
self.assertEqual(events[0].egvTimestamp, 444182469)
self.assertEqual(events[1].egvTimestamp, 444182768)
self.assertEqual(events[2].egvTimestamp, 444189067)
self.assertEqual(events[3].egvTimestamp, 444189367)
self.assertEqual(events[4].egvTimestamp, 444189666)
p = self.process.process(events, time_start=None, time_end=None)
self.assertEqual(len(p), 5)
self.assertEqual(p[0]['sgv'], 75)
self.assertEqual(p[0]['dateString'], '2022-01-28T00:01:09-0500')
self.assertEqual(p[1]['sgv'], 77)
self.assertEqual(p[1]['dateString'], '2022-01-28T00:06:08-0500')
self.assertEqual(p[2]['sgv'], 76)
self.assertEqual(p[2]['dateString'], '2022-01-28T01:51:07-0500')
self.assertEqual(p[3]['sgv'], 75)
self.assertEqual(p[3]['dateString'], '2022-01-28T01:56:07-0500')
self.assertEqual(p[4]['sgv'], 74)
self.assertEqual(p[4]['dateString'], '2022-01-28T02:01:06-0500')
def test_g6_reading_with_last_uploaded(self):
"""Test that already uploaded G6 readings are skipped"""
events = [
Event(G6_EVENT_1),
Event(G6_EVENT_2),
Event(G6_EVENT_3)
]
# Set last upload time using 'date' field (milliseconds)
event_1_time = self.process.timestamp_for(events[0])
self.nightscout.last_uploaded_bg_entry = lambda *args, **kwargs: {
'date': event_1_time.timestamp() * 1000
}
p = self.process.process(events, time_start=None, time_end=None)
# Only events 2 and 3 should be processed
self.assertEqual(len(p), 2)
self.assertEqual(p[0]['sgv'], 77)
self.assertEqual(p[1]['sgv'], 76)
class TestProcessCGMReadingWrite(unittest.TestCase):
"""Tests for writing CGM readings to Nightscout"""
maxDiff = None
def setUp(self):
self.tconnect = TConnectApi()
self.nightscout = NightscoutApi()
self.nightscout.last_uploaded_bg_entry = lambda *args, **kwargs: None
self.tconnect_device_id = 'abcdef'
def test_write_entries_pretend_mode(self):
"""Test that pretend mode doesn't actually upload"""
process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=True, timezone='America/New_York')
events = [Event(G7_EVENT_1)]
ns_entries = process.process(events, time_start=None, time_end=None)
count = process.write(ns_entries)
self.assertEqual(count, 1)
# In pretend mode, nothing should be uploaded
self.assertEqual(len(self.nightscout.uploaded_entries.get('entries', [])), 0)
def test_write_entries_real_mode(self):
"""Test that real mode uploads entries"""
process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False, timezone='America/New_York')
events = [Event(G7_EVENT_1)]
ns_entries = process.process(events, time_start=None, time_end=None)
count = process.write(ns_entries)
self.assertEqual(count, 1)
# In real mode, entries should be uploaded
self.assertEqual(len(self.nightscout.uploaded_entries['entries']), 1)
self.assertEqual(self.nightscout.uploaded_entries['entries'][0]['sgv'], 80)
self.assertEqual(self.nightscout.uploaded_entries['entries'][0]['dateString'], '2025-12-13T18:16:44-0500')
def test_write_mixed_g6_and_g7(self):
"""Test writing a mix of G6 and G7 readings"""
process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False, timezone='America/New_York')
events = [
Event(G6_EVENT_1),
Event(G6_EVENT_2),
Event(G7_EVENT_1),
Event(G7_EVENT_2)
]
ns_entries = process.process(events, time_start=None, time_end=None)
count = process.write(ns_entries)
self.assertEqual(count, 4)
self.assertEqual(len(self.nightscout.uploaded_entries['entries']), 4)
# g6
self.assertEqual(self.nightscout.uploaded_entries['entries'][0]['sgv'], 75)
self.assertEqual(self.nightscout.uploaded_entries['entries'][0]['dateString'], '2022-01-28T00:01:09-0500')
self.assertEqual(self.nightscout.uploaded_entries['entries'][1]['sgv'], 77)
self.assertEqual(self.nightscout.uploaded_entries['entries'][1]['dateString'], '2022-01-28T00:06:08-0500')
# g7
self.assertEqual(self.nightscout.uploaded_entries['entries'][2]['sgv'], 80)
self.assertEqual(self.nightscout.uploaded_entries['entries'][2]['dateString'], '2025-12-13T18:16:44-0500')
self.assertEqual(self.nightscout.uploaded_entries['entries'][3]['sgv'], 71)
self.assertEqual(self.nightscout.uploaded_entries['entries'][3]['dateString'], '2025-12-13T18:21:44-0500')
class TestProcessCGMReadingMultipleTimezones(unittest.TestCase):
"""Test timezone processing across TIMEZONE_NAME values."""
maxDiff = None
def setUp(self):
self.tconnect = TConnectApi()
self.nightscout = NightscoutApi()
self.nightscout.last_uploaded_bg_entry = lambda *args, **kwargs: None
self.tconnect_device_id = 'abcdef'
def test_America_New_York(self):
timezone = 'America/New_York'
events = [Event(G7_EVENT_1)]
process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False, timezone=timezone)
p = process.process(events, time_start=None, time_end=None)
self.assertEqual(len(p), 1)
self.assertEqual(p[0]['dateString'], '2025-12-13T18:16:44-0500')
def test_America_Chicago(self):
timezone = 'America/Chicago'
events = [Event(G7_EVENT_1)]
process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False, timezone=timezone)
p = process.process(events, time_start=None, time_end=None)
self.assertEqual(len(p), 1)
self.assertEqual(p[0]['dateString'], '2025-12-13T18:16:44-0600')
def test_GMT(self):
timezone = 'Etc/GMT'
events = [Event(G7_EVENT_1)]
process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False, timezone=timezone)
p = process.process(events, time_start=None, time_end=None)
self.assertEqual(len(p), 1)
self.assertEqual(p[0]['dateString'], '2025-12-13T18:16:44+0000')
if __name__ == '__main__':
unittest.main()