Skip to content

Commit 84298e5

Browse files
authored
fix: update datetime usage to ensure UTC consistency across the appli… (#66)
* fix: update datetime usage to ensure UTC consistency across the application * run precommit
1 parent 20f3ce5 commit 84298e5

File tree

6 files changed

+29
-24
lines changed

6 files changed

+29
-24
lines changed

mcp_email_server/config.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
from pathlib import Path
66
from typing import Any
7+
from zoneinfo import ZoneInfo
78

89
import tomli_w
910
from pydantic import BaseModel, ConfigDict, Field, field_serializer, model_validator
@@ -37,8 +38,8 @@ class AccountAttributes(BaseModel):
3738
model_config = ConfigDict(json_encoders={datetime.datetime: lambda v: v.isoformat()})
3839
account_name: str
3940
description: str = ""
40-
created_at: datetime.datetime = Field(default_factory=datetime.datetime.now)
41-
updated_at: datetime.datetime = Field(default_factory=datetime.datetime.now)
41+
created_at: datetime.datetime = Field(default_factory=lambda: datetime.datetime.now(ZoneInfo("UTC")))
42+
updated_at: datetime.datetime = Field(default_factory=lambda: datetime.datetime.now(ZoneInfo("UTC")))
4243

4344
@model_validator(mode="after")
4445
@classmethod
@@ -48,7 +49,7 @@ def update_updated_at(cls, obj: AccountAttributes) -> AccountAttributes:
4849
obj.model_config["validate_assignment"] = False
4950

5051
# update updated_at field
51-
obj.updated_at = datetime.datetime.now()
52+
obj.updated_at = datetime.datetime.now(ZoneInfo("UTC"))
5253

5354
# enable validation again
5455
obj.model_config["validate_assignment"] = True

mcp_email_server/emails/classic.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import email.utils
22
from collections.abc import AsyncGenerator
3-
from datetime import datetime
3+
from datetime import datetime, timezone
44
from email.header import Header
55
from email.mime.text import MIMEText
66
from email.parser import BytesParser
@@ -56,9 +56,13 @@ def _parse_email_data(self, raw_email: bytes, email_id: str | None = None) -> di
5656
# Parse date
5757
try:
5858
date_tuple = email.utils.parsedate_tz(date_str)
59-
date = datetime.fromtimestamp(email.utils.mktime_tz(date_tuple)) if date_tuple else datetime.now()
59+
date = (
60+
datetime.fromtimestamp(email.utils.mktime_tz(date_tuple), tz=timezone.utc)
61+
if date_tuple
62+
else datetime.now(timezone.utc)
63+
)
6064
except Exception:
61-
date = datetime.now()
65+
date = datetime.now(timezone.utc)
6266

6367
# Get body content
6468
body = ""
@@ -267,12 +271,12 @@ async def get_emails_metadata_stream( # noqa: C901
267271
try:
268272
date_tuple = email.utils.parsedate_tz(date_str)
269273
date = (
270-
datetime.fromtimestamp(email.utils.mktime_tz(date_tuple))
274+
datetime.fromtimestamp(email.utils.mktime_tz(date_tuple), tz=timezone.utc)
271275
if date_tuple
272-
else datetime.now()
276+
else datetime.now(timezone.utc)
273277
)
274278
except Exception:
275-
date = datetime.now()
279+
date = datetime.now(timezone.utc)
276280

277281
# For metadata, we don't fetch attachments to save bandwidth
278282
# We'll mark it as unknown for now

tests/test_classic_handler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime
1+
from datetime import datetime, timezone
22
from unittest.mock import AsyncMock, patch
33

44
import pytest
@@ -54,7 +54,7 @@ def test_init(self, email_settings):
5454
async def test_get_emails(self, classic_handler):
5555
"""Test get_emails method."""
5656
# Create test data
57-
now = datetime.now()
57+
now = datetime.now(timezone.utc)
5858
email_data = {
5959
"email_id": "123",
6060
"subject": "Test Subject",

tests/test_email_client.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import asyncio
22
import email
3-
from datetime import datetime
3+
from datetime import datetime, timezone
44
from email.mime.text import MIMEText
55
from unittest.mock import AsyncMock, MagicMock, patch
66

@@ -104,12 +104,12 @@ def test_build_search_criteria(self):
104104
assert criteria == ["ALL"]
105105

106106
# Test with before date
107-
before_date = datetime(2023, 1, 1)
107+
before_date = datetime(2023, 1, 1, tzinfo=timezone.utc)
108108
criteria = EmailClient._build_search_criteria(before=before_date)
109109
assert criteria == ["BEFORE", "01-JAN-2023"]
110110

111111
# Test with since date
112-
since_date = datetime(2023, 1, 1)
112+
since_date = datetime(2023, 1, 1, tzinfo=timezone.utc)
113113
criteria = EmailClient._build_search_criteria(since=since_date)
114114
assert criteria == ["SINCE", "01-JAN-2023"]
115115

@@ -135,7 +135,7 @@ def test_build_search_criteria(self):
135135

136136
# Test with multiple criteria
137137
criteria = EmailClient._build_search_criteria(
138-
subject="Test", from_address="test@example.com", since=datetime(2023, 1, 1)
138+
subject="Test", from_address="test@example.com", since=datetime(2023, 1, 1, tzinfo=timezone.utc)
139139
)
140140
assert criteria == ["SINCE", "01-JAN-2023", "SUBJECT", "Test", "FROM", "test@example.com"]
141141

@@ -172,7 +172,7 @@ async def test_get_emails_stream(self, email_client):
172172
"subject": "Test Subject",
173173
"from": "sender@example.com",
174174
"body": "Test Body",
175-
"date": datetime.now(),
175+
"date": datetime.now(timezone.utc),
176176
"attachments": [],
177177
}
178178

tests/test_mcp_tools.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime
1+
from datetime import datetime, timezone
22
from unittest.mock import AsyncMock, MagicMock, patch
33

44
import pytest
@@ -108,7 +108,7 @@ async def test_add_email_account(self):
108108
async def test_list_emails_metadata(self):
109109
"""Test list_emails_metadata MCP tool."""
110110
# Create test data
111-
now = datetime.now()
111+
now = datetime.now(timezone.utc)
112112
email_metadata = EmailMetadata(
113113
email_id="12345",
114114
subject="Test Subject",
@@ -171,7 +171,7 @@ async def test_list_emails_metadata(self):
171171
async def test_get_emails_content_single(self):
172172
"""Test get_emails_content MCP tool with single email."""
173173
# Create test data
174-
now = datetime.now()
174+
now = datetime.now(timezone.utc)
175175
email_body = EmailBodyResponse(
176176
email_id="12345",
177177
subject="Test Subject",
@@ -216,7 +216,7 @@ async def test_get_emails_content_single(self):
216216
async def test_get_emails_content_batch(self):
217217
"""Test get_emails_content MCP tool with multiple emails."""
218218
# Create test data
219-
now = datetime.now()
219+
now = datetime.now(timezone.utc)
220220
email1 = EmailBodyResponse(
221221
email_id="12345",
222222
subject="Test Subject 1",

tests/test_models.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime
1+
from datetime import datetime, timezone
22

33
from mcp_email_server.emails.models import EmailMetadata, EmailMetadataPageResponse
44

@@ -11,7 +11,7 @@ def test_init(self):
1111
subject="Test Subject",
1212
sender="test@example.com",
1313
recipients=["recipient@example.com"],
14-
date=datetime.now(),
14+
date=datetime.now(timezone.utc),
1515
attachments=["file1.txt", "file2.pdf"],
1616
)
1717

@@ -23,7 +23,7 @@ def test_init(self):
2323

2424
def test_from_email(self):
2525
"""Test from_email class method."""
26-
now = datetime.now()
26+
now = datetime.now(timezone.utc)
2727
email_dict = {
2828
"email_id": "123",
2929
"subject": "Test Subject",
@@ -45,7 +45,7 @@ def test_from_email(self):
4545
class TestEmailMetadataPageResponse:
4646
def test_init(self):
4747
"""Test initialization with valid data."""
48-
now = datetime.now()
48+
now = datetime.now(timezone.utc)
4949
email_data = EmailMetadata(
5050
email_id="123",
5151
subject="Test Subject",

0 commit comments

Comments
 (0)