/
fleisar
/
agent-timetracker
Обзор
Документация
Войти
/
fleisar
/
agent-timetracker
Код
Запросы
0
Задачи
Вики
Пакеты
1
Релизы
2
CI/CD
Аналитика
Безопасность
main
src/util/time.ts
156 строк
3 KB
Matvey Kuznetsov
fix: handle early ISO years in time parsing
02 июл 2026, 18:27
02 июл 2026, 18:27
5c45d73
Код
Авторство
О чём код?
import { TrackerError, trackerError } from "../domain/errors.js"; type ParsedIsoTimestamp = { year: number; month: number; day: number; hour: number; minute: number; second: number; millisecond: number; offsetMinutes: number; }; const ISO_TIMESTAMP_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(Z|[+-]\d{2}:\d{2})$/; function invalidTimestamp(message: string): TrackerError { return trackerError("invalid_timestamp", message); } function parseOffset(value: string): number { if (value === "Z") { return 0; } const sign = value[0] === "-" ? -1 : 1; const hours = Number(value.slice(1, 3)); const minutes = Number(value.slice(4, 6)); if (hours > 23 || minutes > 59) { throw invalidTimestamp("Invalid timestamp offset"); } return sign * (hours * 60 + minutes); } function isLeapYear(year: number): boolean { if (year % 400 === 0) { return true; } if (year % 100 === 0) { return false; } return year % 4 === 0; } function daysInMonth(year: number, month: number): number { switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: return 31; case 4: case 6: case 9: case 11: return 30; case 2: return isLeapYear(year) ? 29 : 28; default: throw invalidTimestamp("Invalid timestamp month"); } } function parseIsoParts(value: string): ParsedIsoTimestamp { const match = ISO_TIMESTAMP_PATTERN.exec(value); if (!match) { throw invalidTimestamp("Invalid timestamp format"); } const year = Number(match[1]); const month = Number(match[2]); const day = Number(match[3]); const hour = Number(match[4]); const minute = Number(match[5]); const second = Number(match[6]); const millisecond = Number((match[7] ?? "0").padEnd(3, "0")); const offsetMinutes = parseOffset(match[8]); if (month < 1 || month > 12) { throw invalidTimestamp("Invalid timestamp month"); } if (hour > 23 || minute > 59 || second > 59) { throw invalidTimestamp("Invalid timestamp time"); } if (day < 1 || day > daysInMonth(year, month)) { throw invalidTimestamp("Invalid timestamp date"); } return { year, month, day, hour, minute, second, millisecond, offsetMinutes }; } function buildUtcDate(parts: ParsedIsoTimestamp): Date { const date = new Date( Date.UTC( 2000, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second, parts.millisecond ) ); date.setUTCFullYear(parts.year); return date; } export function parseIsoTimestamp(value: string): Date { const parts = parseIsoParts(value); const date = buildUtcDate(parts); date.setTime(date.getTime() - parts.offsetMinutes * 60_000); return date; } export function formatIsoTimestamp(date: Date): string { if (!(date instanceof Date) || Number.isNaN(date.getTime())) { throw invalidTimestamp("Invalid date"); } return date.toISOString(); } export function secondsBetween(startIso: string, endIso: string): number { const start = parseIsoTimestamp(startIso).getTime(); const end = parseIsoTimestamp(endIso).getTime(); return Math.trunc((end - start) / 1000); } export function addSeconds(iso: string, seconds: number): string { const instant = parseIsoTimestamp(iso).getTime() + Math.trunc(seconds) * 1000; return new Date(instant).toISOString(); }