Skip to main content
Glama

Todoist MCP Server

datetime-utils.ts4.23 kB
import { TodoistTask, TodoistTaskDueData } from "../types.js"; function getTimeZoneOffset(date: Date, timeZone: string): number { const dtf = new Intl.DateTimeFormat("en-US", { timeZone, hour12: false, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", }); const parts = dtf.formatToParts(date); const lookup = new Map(parts.map((part) => [part.type, part.value])); const year = Number(lookup.get("year")); const month = Number(lookup.get("month")); const day = Number(lookup.get("day")); const hour = Number(lookup.get("hour")); const minute = Number(lookup.get("minute")); const second = Number(lookup.get("second")); if ( [year, month, day, hour, minute, second].some((value) => Number.isNaN(value) ) ) { throw new Error("Invalid timezone conversion parts"); } const asUtc = Date.UTC(year, month - 1, day, hour, minute, second); return asUtc - date.getTime(); } /** * Returns a Date object set to UTC midnight for the provided YYYY-MM-DD string. * Applies timezone conversion when a Todoist timezone value is supplied. */ export function startOfDayUtc( dateString: string, timeZone?: string | null ): Date | null { const [year, month, day] = dateString.split("-").map(Number); if ( Number.isNaN(year) || Number.isNaN(month) || Number.isNaN(day) || month < 1 || month > 12 || day < 1 || day > 31 ) { return null; } const utcMidnight = Date.UTC(year, month - 1, day, 0, 0, 0, 0); if (!timeZone) { return new Date(utcMidnight); } try { const baseline = new Date(utcMidnight); const offset = getTimeZoneOffset(baseline, timeZone); return new Date(utcMidnight - offset); } catch { // Fallback to plain UTC midnight if the timezone conversion fails return new Date(utcMidnight); } } /** * Creates a new Date that is `days` days after the provided date (UTC). */ export function addDays(date: Date, days: number): Date { const result = new Date(date); result.setUTCDate(result.getUTCDate() + days); return result; } /** * Attempts to convert a Todoist due object into an absolute JavaScript Date. * Falls back to null when the due information cannot be parsed. */ export function getTaskDueDate(task: Pick<TodoistTask, "due">): Date | null { return parseDueDate(task.due); } /** * Safely parses Todoist due metadata to a JavaScript Date (UTC). */ export function parseDueDate( due: TodoistTaskDueData | null | undefined ): Date | null { if (!due) return null; if (due.datetime) { const datetime = new Date(due.datetime); if (!Number.isNaN(datetime.getTime())) { return datetime; } } if (due.date) { const dateOnly = startOfDayUtc(due.date, due.timezone); if (dateOnly) { return dateOnly; } } if (due.string) { const parsed = new Date(due.string); if (!Number.isNaN(parsed.getTime())) { return parsed; } } return null; } /** * Extracts a YYYY-MM-DD representation from the Todoist due metadata. */ export function getDueDateOnly( due: TodoistTaskDueData | null | undefined ): string | null { if (!due) return null; if (due.date) { return due.date; } if (due.datetime) { const datetime = new Date(due.datetime); if (!Number.isNaN(datetime.getTime())) { return datetime.toISOString().split("T")[0] || null; } } if (due.string) { const parsed = new Date(due.string); if (!Number.isNaN(parsed.getTime())) { return parsed.toISOString().split("T")[0] || null; } } return null; } /** * Formats Todoist due information with full context for human-friendly display. */ export function formatDueDetails( due: TodoistTaskDueData | null | undefined ): string | null { if (!due) { return null; } const parts: string[] = []; if (due.date) { parts.push(`date=${due.date}`); } if (due.datetime) { parts.push(`datetime=${due.datetime}`); } if (due.timezone) { parts.push(`timezone=${due.timezone}`); } if (due.string) { parts.push(`phrase="${due.string}"`); } return parts.length > 0 ? parts.join(", ") : null; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/greirson/mcp-todoist'

If you have feedback or need assistance with the MCP directory API, please join our Discord server