refactor: move all datetime.now() calls to UTC (#1589)

# What does this PR do?

Updated all instances of datetime.now() to use timezone.utc for
consistency in handling time across different systems. This ensures that
timestamps are always in Coordinated Universal Time (UTC), avoiding
issues with time zone discrepancies and promoting uniformity in
time-related data.

Signed-off-by: Sébastien Han <seb@redhat.com>
This commit is contained in:
Sébastien Han 2025-03-13 23:34:53 +01:00 committed by GitHub
parent b906bad238
commit 98b1b15e0f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 52 additions and 45 deletions

View file

@ -10,7 +10,7 @@ import json
import os import os
import shutil import shutil
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime, timezone
from functools import partial from functools import partial
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional from typing import Dict, List, Optional
@ -404,7 +404,7 @@ def _download_from_manifest(manifest_file: str, max_concurrent_downloads: int):
d = json.load(f) d = json.load(f)
manifest = Manifest(**d) manifest = Manifest(**d)
if datetime.now() > manifest.expires_on: if datetime.now(timezone.utc) > manifest.expires_on:
raise ValueError(f"Manifest URLs have expired on {manifest.expires_on}") raise ValueError(f"Manifest URLs have expired on {manifest.expires_on}")
console = Console() console = Console()

View file

@ -34,7 +34,9 @@ class SystemDefaultGenerator(PromptTemplateGeneratorBase):
) )
return PromptTemplate( return PromptTemplate(
template_str.lstrip("\n"), template_str.lstrip("\n"),
{"today": datetime.now().strftime("%d %B %Y")}, {
"today": datetime.now().strftime("%d %B %Y") # noqa: DTZ005 - we don't care about timezones here since we are displaying the date
},
) )
def data_examples(self) -> List[Any]: def data_examples(self) -> List[Any]:

View file

@ -11,7 +11,7 @@ import re
import secrets import secrets
import string import string
import uuid import uuid
from datetime import datetime from datetime import datetime, timezone
from typing import AsyncGenerator, List, Optional, Union from typing import AsyncGenerator, List, Optional, Union
from urllib.parse import urlparse from urllib.parse import urlparse
@ -239,7 +239,7 @@ class ChatAgent(ShieldRunnerMixin):
in_progress_tool_call_step = await self.storage.get_in_progress_tool_call_step( in_progress_tool_call_step = await self.storage.get_in_progress_tool_call_step(
request.session_id, request.turn_id request.session_id, request.turn_id
) )
now = datetime.now().astimezone().isoformat() now = datetime.now(timezone.utc).isoformat()
tool_execution_step = ToolExecutionStep( tool_execution_step = ToolExecutionStep(
step_id=(in_progress_tool_call_step.step_id if in_progress_tool_call_step else str(uuid.uuid4())), step_id=(in_progress_tool_call_step.step_id if in_progress_tool_call_step else str(uuid.uuid4())),
turn_id=request.turn_id, turn_id=request.turn_id,
@ -264,7 +264,7 @@ class ChatAgent(ShieldRunnerMixin):
start_time = last_turn.started_at start_time = last_turn.started_at
else: else:
messages.extend(request.messages) messages.extend(request.messages)
start_time = datetime.now().astimezone().isoformat() start_time = datetime.now(timezone.utc).isoformat()
input_messages = request.messages input_messages = request.messages
output_message = None output_message = None
@ -295,7 +295,7 @@ class ChatAgent(ShieldRunnerMixin):
input_messages=input_messages, input_messages=input_messages,
output_message=output_message, output_message=output_message,
started_at=start_time, started_at=start_time,
completed_at=datetime.now().astimezone().isoformat(), completed_at=datetime.now(timezone.utc).isoformat(),
steps=steps, steps=steps,
) )
await self.storage.add_turn_to_session(request.session_id, turn) await self.storage.add_turn_to_session(request.session_id, turn)
@ -386,7 +386,7 @@ class ChatAgent(ShieldRunnerMixin):
return return
step_id = str(uuid.uuid4()) step_id = str(uuid.uuid4())
shield_call_start_time = datetime.now().astimezone().isoformat() shield_call_start_time = datetime.now(timezone.utc).isoformat()
try: try:
yield AgentTurnResponseStreamChunk( yield AgentTurnResponseStreamChunk(
event=AgentTurnResponseEvent( event=AgentTurnResponseEvent(
@ -410,7 +410,7 @@ class ChatAgent(ShieldRunnerMixin):
turn_id=turn_id, turn_id=turn_id,
violation=e.violation, violation=e.violation,
started_at=shield_call_start_time, started_at=shield_call_start_time,
completed_at=datetime.now().astimezone().isoformat(), completed_at=datetime.now(timezone.utc).isoformat(),
), ),
) )
) )
@ -433,7 +433,7 @@ class ChatAgent(ShieldRunnerMixin):
turn_id=turn_id, turn_id=turn_id,
violation=None, violation=None,
started_at=shield_call_start_time, started_at=shield_call_start_time,
completed_at=datetime.now().astimezone().isoformat(), completed_at=datetime.now(timezone.utc).isoformat(),
), ),
) )
) )
@ -472,7 +472,7 @@ class ChatAgent(ShieldRunnerMixin):
client_tools[tool.name] = tool client_tools[tool.name] = tool
while True: while True:
step_id = str(uuid.uuid4()) step_id = str(uuid.uuid4())
inference_start_time = datetime.now().astimezone().isoformat() inference_start_time = datetime.now(timezone.utc).isoformat()
yield AgentTurnResponseStreamChunk( yield AgentTurnResponseStreamChunk(
event=AgentTurnResponseEvent( event=AgentTurnResponseEvent(
payload=AgentTurnResponseStepStartPayload( payload=AgentTurnResponseStepStartPayload(
@ -582,7 +582,7 @@ class ChatAgent(ShieldRunnerMixin):
turn_id=turn_id, turn_id=turn_id,
model_response=copy.deepcopy(message), model_response=copy.deepcopy(message),
started_at=inference_start_time, started_at=inference_start_time,
completed_at=datetime.now().astimezone().isoformat(), completed_at=datetime.now(timezone.utc).isoformat(),
), ),
) )
) )
@ -653,7 +653,7 @@ class ChatAgent(ShieldRunnerMixin):
turn_id=turn_id, turn_id=turn_id,
tool_calls=[tool_call], tool_calls=[tool_call],
tool_responses=[], tool_responses=[],
started_at=datetime.now().astimezone().isoformat(), started_at=datetime.now(timezone.utc).isoformat(),
), ),
) )
yield message yield message
@ -670,7 +670,7 @@ class ChatAgent(ShieldRunnerMixin):
"input": message.model_dump_json(), "input": message.model_dump_json(),
}, },
) as span: ) as span:
tool_execution_start_time = datetime.now().astimezone().isoformat() tool_execution_start_time = datetime.now(timezone.utc).isoformat()
tool_call = message.tool_calls[0] tool_call = message.tool_calls[0]
tool_result = await self.execute_tool_call_maybe( tool_result = await self.execute_tool_call_maybe(
session_id, session_id,
@ -708,7 +708,7 @@ class ChatAgent(ShieldRunnerMixin):
) )
], ],
started_at=tool_execution_start_time, started_at=tool_execution_start_time,
completed_at=datetime.now().astimezone().isoformat(), completed_at=datetime.now(timezone.utc).isoformat(),
), ),
) )
) )

View file

@ -7,7 +7,7 @@
import json import json
import logging import logging
import uuid import uuid
from datetime import datetime from datetime import datetime, timezone
from typing import List, Optional from typing import List, Optional
from pydantic import BaseModel from pydantic import BaseModel
@ -36,7 +36,7 @@ class AgentPersistence:
session_info = AgentSessionInfo( session_info = AgentSessionInfo(
session_id=session_id, session_id=session_id,
session_name=name, session_name=name,
started_at=datetime.now(), started_at=datetime.now(timezone.utc),
) )
await self.kvstore.set( await self.kvstore.set(
key=f"session:{self.agent_id}:{session_id}", key=f"session:{self.agent_id}:{session_id}",

View file

@ -3,7 +3,7 @@
# #
# This source code is licensed under the terms described in the LICENSE file in # This source code is licensed under the terms described in the LICENSE file in
# the root directory of this source tree. # the root directory of this source tree.
from datetime import datetime from datetime import datetime, timezone
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
from llama_stack.apis.datasetio import DatasetIO from llama_stack.apis.datasetio import DatasetIO
@ -64,7 +64,7 @@ class TorchtunePostTrainingImpl:
job_status_response = PostTrainingJobStatusResponse( job_status_response = PostTrainingJobStatusResponse(
job_uuid=job_uuid, job_uuid=job_uuid,
status=JobStatus.scheduled, status=JobStatus.scheduled,
scheduled_at=datetime.now(), scheduled_at=datetime.now(timezone.utc),
) )
self.jobs[job_uuid] = job_status_response self.jobs[job_uuid] = job_status_response
@ -84,7 +84,7 @@ class TorchtunePostTrainingImpl:
) )
job_status_response.status = JobStatus.in_progress job_status_response.status = JobStatus.in_progress
job_status_response.started_at = datetime.now() job_status_response.started_at = datetime.now(timezone.utc)
await recipe.setup() await recipe.setup()
resources_allocated, checkpoints = await recipe.train() resources_allocated, checkpoints = await recipe.train()
@ -93,7 +93,7 @@ class TorchtunePostTrainingImpl:
job_status_response.resources_allocated = resources_allocated job_status_response.resources_allocated = resources_allocated
job_status_response.checkpoints = checkpoints job_status_response.checkpoints = checkpoints
job_status_response.status = JobStatus.completed job_status_response.status = JobStatus.completed
job_status_response.completed_at = datetime.now() job_status_response.completed_at = datetime.now(timezone.utc)
except Exception: except Exception:
job_status_response.status = JobStatus.failed job_status_response.status = JobStatus.failed

View file

@ -8,7 +8,7 @@ import gc
import logging import logging
import os import os
import time import time
from datetime import datetime from datetime import datetime, timezone
from functools import partial from functools import partial
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
@ -532,7 +532,7 @@ class LoraFinetuningSingleDevice:
checkpoint_path = await self.save_checkpoint(epoch=curr_epoch) checkpoint_path = await self.save_checkpoint(epoch=curr_epoch)
checkpoint = Checkpoint( checkpoint = Checkpoint(
identifier=f"{self.model_id}-sft-{curr_epoch}", identifier=f"{self.model_id}-sft-{curr_epoch}",
created_at=datetime.now(), created_at=datetime.now(timezone.utc),
epoch=curr_epoch, epoch=curr_epoch,
post_training_job_id=self.job_uuid, post_training_job_id=self.job_uuid,
path=checkpoint_path, path=checkpoint_path,

View file

@ -5,7 +5,7 @@
# the root directory of this source tree. # the root directory of this source tree.
import json import json
from datetime import datetime from datetime import datetime, timezone
from opentelemetry.sdk.trace import ReadableSpan from opentelemetry.sdk.trace import ReadableSpan
from opentelemetry.sdk.trace.export import SpanProcessor from opentelemetry.sdk.trace.export import SpanProcessor
@ -34,7 +34,7 @@ class ConsoleSpanProcessor(SpanProcessor):
if span.attributes and span.attributes.get("__autotraced__"): if span.attributes and span.attributes.get("__autotraced__"):
return return
timestamp = datetime.utcfromtimestamp(span.start_time / 1e9).strftime("%H:%M:%S.%f")[:-3] timestamp = datetime.fromtimestamp(span.start_time / 1e9, tz=timezone.utc).strftime("%H:%M:%S.%f")[:-3]
print( print(
f"{COLORS['dim']}{timestamp}{COLORS['reset']} " f"{COLORS['dim']}{timestamp}{COLORS['reset']} "
@ -46,7 +46,7 @@ class ConsoleSpanProcessor(SpanProcessor):
if span.attributes and span.attributes.get("__autotraced__"): if span.attributes and span.attributes.get("__autotraced__"):
return return
timestamp = datetime.utcfromtimestamp(span.end_time / 1e9).strftime("%H:%M:%S.%f")[:-3] timestamp = datetime.fromtimestamp(span.end_time / 1e9, tz=timezone.utc).strftime("%H:%M:%S.%f")[:-3]
span_context = ( span_context = (
f"{COLORS['dim']}{timestamp}{COLORS['reset']} " f"{COLORS['dim']}{timestamp}{COLORS['reset']} "
@ -74,7 +74,7 @@ class ConsoleSpanProcessor(SpanProcessor):
print(f" {COLORS['dim']}{key}: {str_value}{COLORS['reset']}") print(f" {COLORS['dim']}{key}: {str_value}{COLORS['reset']}")
for event in span.events: for event in span.events:
event_time = datetime.utcfromtimestamp(event.timestamp / 1e9).strftime("%H:%M:%S.%f")[:-3] event_time = datetime.fromtimestamp(event.timestamp / 1e9, tz=timezone.utc).strftime("%H:%M:%S.%f")[:-3]
severity = event.attributes.get("severity", "info") severity = event.attributes.get("severity", "info")
message = event.attributes.get("message", event.name) message = event.attributes.get("message", event.name)

View file

@ -8,7 +8,7 @@ import json
import os import os
import sqlite3 import sqlite3
import threading import threading
from datetime import datetime from datetime import datetime, timezone
from opentelemetry.sdk.trace import SpanProcessor from opentelemetry.sdk.trace import SpanProcessor
from opentelemetry.trace import Span from opentelemetry.trace import Span
@ -124,8 +124,8 @@ class SQLiteSpanProcessor(SpanProcessor):
trace_id, trace_id,
service_name, service_name,
(span_id if not parent_span_id else None), (span_id if not parent_span_id else None),
datetime.fromtimestamp(span.start_time / 1e9).isoformat(), datetime.fromtimestamp(span.start_time / 1e9, timezone.utc).isoformat(),
datetime.fromtimestamp(span.end_time / 1e9).isoformat(), datetime.fromtimestamp(span.end_time / 1e9, timezone.utc).isoformat(),
), ),
) )
@ -143,8 +143,8 @@ class SQLiteSpanProcessor(SpanProcessor):
trace_id, trace_id,
parent_span_id, parent_span_id,
span.name, span.name,
datetime.fromtimestamp(span.start_time / 1e9).isoformat(), datetime.fromtimestamp(span.start_time / 1e9, timezone.utc).isoformat(),
datetime.fromtimestamp(span.end_time / 1e9).isoformat(), datetime.fromtimestamp(span.end_time / 1e9, timezone.utc).isoformat(),
json.dumps(dict(span.attributes)), json.dumps(dict(span.attributes)),
span.status.status_code.name, span.status.status_code.name,
span.kind.name, span.kind.name,
@ -161,7 +161,7 @@ class SQLiteSpanProcessor(SpanProcessor):
( (
span_id, span_id,
event.name, event.name,
datetime.fromtimestamp(event.timestamp / 1e9).isoformat(), datetime.fromtimestamp(event.timestamp / 1e9, timezone.utc).isoformat(),
json.dumps(dict(event.attributes)), json.dumps(dict(event.attributes)),
), ),
) )

View file

@ -168,7 +168,7 @@ def process_matplotlib_response(response, matplotlib_dump_dir: str):
image_paths = [] image_paths = []
for i, img in enumerate(images): for i, img in enumerate(images):
# create new directory for each day to better organize data: # create new directory for each day to better organize data:
dump_dname = datetime.today().strftime("%Y-%m-%d") dump_dname = datetime.today().strftime("%Y-%m-%d") # noqa: DTZ002 - we don't care about timezones here since we are displaying the date
dump_dpath = Path(matplotlib_dump_dir, dump_dname) dump_dpath = Path(matplotlib_dump_dir, dump_dname)
dump_dpath.mkdir(parents=True, exist_ok=True) dump_dpath.mkdir(parents=True, exist_ok=True)
# save image into a file # save image into a file

View file

@ -11,7 +11,7 @@ import logging
import queue import queue
import threading import threading
import uuid import uuid
from datetime import datetime from datetime import datetime, timezone
from functools import wraps from functools import wraps
from typing import Any, Callable, Dict, List, Optional from typing import Any, Callable, Dict, List, Optional
@ -86,7 +86,7 @@ class TraceContext:
span_id=generate_short_uuid(), span_id=generate_short_uuid(),
trace_id=self.trace_id, trace_id=self.trace_id,
name=name, name=name,
start_time=datetime.now(), start_time=datetime.now(timezone.utc),
parent_span_id=current_span.span_id if current_span else None, parent_span_id=current_span.span_id if current_span else None,
attributes=attributes, attributes=attributes,
) )
@ -203,7 +203,7 @@ class TelemetryHandler(logging.Handler):
UnstructuredLogEvent( UnstructuredLogEvent(
trace_id=span.trace_id, trace_id=span.trace_id,
span_id=span.span_id, span_id=span.span_id,
timestamp=datetime.now(), timestamp=datetime.now(timezone.utc),
message=self.format(record), message=self.format(record),
severity=severity(record.levelname), severity=severity(record.levelname),
) )

View file

@ -132,6 +132,7 @@ select = [
"N", # Naming "N", # Naming
"W", # Warnings "W", # Warnings
"I", # isort "I", # isort
"DTZ", # datetime rules
] ]
ignore = [ ignore = [
# The following ignores are desired by the project maintainers. # The following ignores are desired by the project maintainers.
@ -145,6 +146,10 @@ ignore = [
"C901", # Complexity of the function is too high "C901", # Complexity of the function is too high
] ]
# Ignore the following errors for the following files
[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["DTZ"] # Ignore datetime rules for tests
[tool.mypy] [tool.mypy]
mypy_path = ["llama_stack"] mypy_path = ["llama_stack"]
packages = ["llama_stack"] packages = ["llama_stack"]