datetime-timezone — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited datetime-timezone (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Correctly handle datetime values across the full stack: browser → API → database → back to browser.
datetime-local HTML inputsStore UTC, Display Local
Browser (local) → Convert to UTC → API → Store UTC → Database
Database → Return UTC → API → Browser → Display in localThe HTML <input type="datetime-local"> returns a string WITHOUT timezone info:
<input type="datetime-local" value="2025-12-15T23:13">This gives you "2025-12-15T23:13" - but is this UTC? Local time? The browser doesn't tell you.
// WRONG - Backend interprets as UTC, but user entered local time!
const dueDate = "2025-12-15T23:13" // User in PKT (UTC+5)
await api.createTask({ due_date: dueDate })
// Backend stores as 23:13 UTC
// But user meant 23:13 PKT = 18:13 UTC
// Task is now 5 hours late!// CORRECT - Convert local datetime to UTC ISO string
function handleSubmit() {
let dueDateUTC: string | undefined = undefined
if (dueDate) {
// datetime-local gives "2025-12-15T23:13" (local time, no TZ)
// new Date() interprets it in browser's local timezone
// toISOString() converts to UTC: "2025-12-15T18:13:00.000Z"
const localDate = new Date(dueDate)
dueDateUTC = localDate.toISOString()
}
await api.createTask({ due_date: dueDateUTC })
}// Input: "2025-12-15T23:13" (from datetime-local)
// Output: "2025-12-15T18:13:00.000Z" (UTC ISO string)
function localToUTC(localDatetime: string): string {
const date = new Date(localDatetime)
return date.toISOString()
}// Input: "2025-12-15T18:13:00Z" (UTC from API)
// Output: "2025-12-15T23:13" (local, for datetime-local input)
function utcToLocal(utcDatetime: string): string {
const date = new Date(utcDatetime)
// Format: YYYY-MM-DDTHH:MM (no seconds, no Z)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day}T${hours}:${minutes}`
}"use client"
import { useState } from "react"
export function TaskForm() {
const [dueDate, setDueDate] = useState("") // Local datetime string
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
// Convert local to UTC for API
let dueDateUTC: string | undefined
if (dueDate) {
dueDateUTC = new Date(dueDate).toISOString()
}
await api.createTask({
title: "My Task",
due_date: dueDateUTC, // UTC ISO string
})
}
return (
<form onSubmit={handleSubmit}>
<input
type="datetime-local"
value={dueDate}
onChange={(e) => setDueDate(e.target.value)}
/>
<button type="submit">Create</button>
</form>
)
}"use client"
import { useState, useEffect } from "react"
export function EditTaskForm({ task }: { task: Task }) {
const [dueDate, setDueDate] = useState("")
useEffect(() => {
// Convert UTC from API to local for input
if (task.due_date) {
setDueDate(utcToLocal(task.due_date))
}
}, [task])
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
// Convert back to UTC for API
const dueDateUTC = dueDate ? new Date(dueDate).toISOString() : undefined
await api.updateTask(task.id, { due_date: dueDateUTC })
}
return (
<input
type="datetime-local"
value={dueDate}
onChange={(e) => setDueDate(e.target.value)}
/>
)
}from datetime import UTC, datetime
from pydantic import field_validator
from sqlmodel import SQLModel
class TaskCreate(SQLModel):
title: str
due_date: datetime | None = None
@field_validator("due_date", mode="after")
@classmethod
def normalize_datetime(cls, v: datetime | None) -> datetime | None:
"""Convert timezone-aware to naive UTC for storage."""
if v is None:
return None
# If timezone-aware, convert to UTC and strip timezone
if v.tzinfo is not None:
v = v.astimezone(UTC).replace(tzinfo=None)
return v@router.post("/tasks", response_model=TaskRead)
async def create_task(
task_in: TaskCreate,
session: AsyncSession = Depends(get_session),
):
# due_date is already normalized to naive UTC by validator
task = Task(**task_in.model_dump())
session.add(task)
await session.commit()
await session.refresh(task)
return taskfrom datetime import datetime
class TaskRead(SQLModel):
id: int
title: str
due_date: datetime | None = None # Returned as ISO string with Z suffixFastAPI automatically serializes datetime as ISO 8601 with Z suffix (UTC).
Always store as naive UTC (no timezone info):
-- PostgreSQL
due_date TIMESTAMP -- NOT TIMESTAMP WITH TIME ZONE# SQLModel
from datetime import datetime
from sqlmodel import Field
class Task(SQLModel, table=True):
due_date: datetime | None = Field(default=None)import { formatDistanceToNow } from 'date-fns'
function formatRelative(utcDatetime: string): string {
const date = new Date(utcDatetime)
return formatDistanceToNow(date, { addSuffix: true })
// "in 2 hours", "3 days ago"
}function formatLocal(utcDatetime: string): string {
const date = new Date(utcDatetime)
return date.toLocaleString()
// "12/15/2025, 11:13:00 PM" (in user's locale)
}import { format } from 'date-fns'
function formatDateTime(utcDatetime: string): string {
const date = new Date(utcDatetime)
return format(date, 'MMM d, yyyy h:mm a')
// "Dec 15, 2025 11:13 PM"
}// Frontend
console.log("datetime-local value:", dueDate) // "2025-12-15T23:13"
console.log("Converted to UTC:", new Date(dueDate).toISOString()) // "2025-12-15T18:13:00.000Z"# Backend
logger.info("Received due_date: %s", task_in.due_date) # 2025-12-15T18:13:00+00:00
logger.info("Stored due_date: %s", task.due_date) # 2025-12-15 18:13:00console.log(Intl.DateTimeFormat().resolvedOptions().timeZone)
// "Asia/Karachi" (PKT = UTC+5)const date = new Date("2025-12-15T23:13")
console.log("UTC offset (minutes):", date.getTimezoneOffset())
// -300 for UTC+5 (PKT)// WRONG
const dueDate = e.target.value // "2025-12-15T23:13"
await api.post({ due_date: dueDate }) // No timezone!
// CORRECT
const dueDate = new Date(e.target.value).toISOString()
await api.post({ due_date: dueDate }) // "2025-12-15T18:13:00.000Z"// WRONG - sends milliseconds timestamp
await api.post({ timestamp: Date.now() }) // 1734285180000
// CORRECT
await api.post({ timestamp: new Date().toISOString() }) // "2025-12-15T18:13:00.000Z"# WRONG - comparing aware and naive
if task.due_date < datetime.now(UTC): # Error or wrong result
# CORRECT - both naive UTC
if task.due_date < datetime.utcnow():// WRONG - shows UTC time to user
<span>{task.due_date}</span> // "2025-12-15T18:13:00Z" (confusing!)
// CORRECT - convert to local
<span>{new Date(task.due_date).toLocaleString()}</span> // "Dec 15, 2025, 11:13 PM"describe('datetime conversion', () => {
it('converts local to UTC correctly', () => {
// Mock timezone to PKT (UTC+5)
const localDatetime = "2025-12-15T23:13"
const utc = new Date(localDatetime).toISOString()
// In PKT, 23:13 local = 18:13 UTC
expect(utc).toBe("2025-12-15T18:13:00.000Z")
})
})def test_datetime_normalization():
# With timezone
task = TaskCreate(
title="Test",
due_date=datetime(2025, 12, 15, 18, 13, tzinfo=UTC)
)
assert task.due_date.tzinfo is None # Normalized to naive
assert task.due_date == datetime(2025, 12, 15, 18, 13)| Location | Format | Example |
|---|---|---|
datetime-local input | Local, no TZ | 2025-12-15T23:13 |
| API request/response | UTC ISO 8601 | 2025-12-15T18:13:00.000Z |
| Database | Naive UTC | 2025-12-15 18:13:00 |
| Display to user | Local formatted | Dec 15, 2025 11:13 PM |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.