from fastapi import FastAPI, HTTPException, Body from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import HTMLResponse, RedirectResponse from pydantic import BaseModel, Field from datetime import datetime, timezone from typing import Literal import pytz from dateutil import parser as dateutil_parser app = FastAPI( title="Secure Time Utilities API", version="1.0.0", description="Provides secure UTC/local time retrieval, formatting, timezone conversion, and comparison.", ) origins = ["*"] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ------------------------------- # Pydantic models # ------------------------------- class FormatTimeInput(BaseModel): format: str = Field( "%Y-%m-%d %H:%M:%S", description="Python strftime format string" ) timezone: str = Field( "UTC", description="IANA timezone name (e.g., UTC, America/New_York)" ) class ConvertTimeInput(BaseModel): timestamp: str = Field( ..., description="ISO 8601 formatted time string (e.g., 2024-01-01T12:00:00Z)" ) from_tz: str = Field( ..., description="Original IANA time zone of input (e.g. UTC or Europe/Berlin)" ) to_tz: str = Field(..., description="Target IANA time zone to convert to") class ElapsedTimeInput(BaseModel): start: str = Field(..., description="Start timestamp in ISO 8601 format") end: str = Field(..., description="End timestamp in ISO 8601 format") units: Literal["seconds", "minutes", "hours", "days"] = Field( "seconds", description="Unit for elapsed time" ) class ParseTimestampInput(BaseModel): timestamp: str = Field( ..., description="Flexible input timestamp string (e.g., 2024-06-01 12:00 PM)" ) timezone: str = Field( "UTC", description="Assumed timezone if none is specified in input" ) # ------------------------------- # Helper for pretty HTML # ------------------------------- def html_page(title: str, body: str) -> HTMLResponse: html = f""" {title}

{title}

{body} """ return html # ------------------------------- # Routes # ------------------------------- @app.get("/get_current_local_time", summary="Current Local Time") def get_current_local(): local_time = datetime.now().isoformat() headers = {"Content-Disposition": "inline"} return HTMLResponse( content=html_page("Current Local Time", f"
{local_time}
"), headers=headers, ) @app.post("/format_time", summary="Format current time") def format_current_time(data: FormatTimeInput): try: tz = pytz.timezone(data.timezone) except Exception: raise HTTPException( status_code=400, detail=f"Invalid timezone: {data.timezone}" ) now = datetime.now(tz) try: formatted = now.strftime(data.format) headers = {"Content-Disposition": "inline"} return HTMLResponse( content=html_page("Formatted Time", f"
{formatted}
"), headers=headers, ) except Exception as e: raise HTTPException(status_code=400, detail=f"Invalid format string: {e}") @app.post("/convert_time", summary="Convert between timezones") def convert_time(data: ConvertTimeInput): try: from_zone = pytz.timezone(data.from_tz) to_zone = pytz.timezone(data.to_tz) except Exception as e: raise HTTPException(status_code=400, detail=f"Invalid timezone: {e}") try: dt = dateutil_parser.parse(data.timestamp) if dt.tzinfo is None: dt = from_zone.localize(dt) else: dt = dt.astimezone(from_zone) converted = dt.astimezone(to_zone).isoformat() headers = {"Content-Disposition": "inline"} return HTMLResponse( content=html_page("Converted Time", f"
{converted}
"), headers=headers, ) except Exception as e: raise HTTPException(status_code=400, detail=f"Invalid timestamp: {e}") @app.post("/parse_timestamp", summary="Parse and normalize timestamps") def parse_timestamp(data: ParseTimestampInput): try: tz = pytz.timezone(data.timezone) dt = dateutil_parser.parse(data.timestamp) if dt.tzinfo is None: dt = tz.localize(dt) dt_utc = dt.astimezone(pytz.utc).isoformat() headers = {"Content-Disposition": "inline"} return HTMLResponse( content=html_page("Parsed Timestamp (UTC)", f"
{dt_utc}
"), headers=headers, ) except Exception as e: raise HTTPException(status_code=400, detail=f"Could not parse: {e}") @app.get("/list_time_zones", summary="All valid time zones") def list_time_zones(): tz_list_html = ( "" ) headers = {"Content-Disposition": "inline"} return HTMLResponse( content=html_page("Valid Time Zones", tz_list_html), headers=headers ) @app.get("/go_to_timezones", summary="Redirect to /list_time_zones") def redirect_to_timezones(): """ Redirects user to the list of time zones. """ return RedirectResponse(url="/list_time_zones") @app.get("/useful_redirect", summary="Redirect to useful external time resource") def useful_redirect(): """ Redirects users to a useful external website (timeanddate.com). """ headers = {"Content-Disposition": "inline"} external_url = "https://time.is" return RedirectResponse(url=external_url, headers=headers)