timestampinfo.fr
Herramientas y documentación

DOCUMENTACIÓN DEL TIEMPO

Timestamps en NestJS: validación y conversión en API

Valida timestamps recibidos por una API NestJS, especifica su unidad y devuelve una fecha UTC sin perder precisión.

Definir un contrato de API explícito

Una API NestJS utiliza las mismas fechas que JavaScript: Date espera milisegundos. Pide al cliente la unidad en vez de adivinarla. El siguiente ejemplo acepta un entero con signo y unit=s o unit=ms.

Controlador: validar, convertir y responder con JSON

En un proyecto NestJS existente, coloca este código en timestamps.module.ts y añade TimestampsModule a los imports del módulo principal. No se necesitan dependencias aparte de Nest.

import { BadRequestException, Controller, Get, Module, Query } from '@nestjs/common';

@Controller('timestamps')
export class TimestampsController {
  @Get('convert')
  convert(@Query('value') value: unknown, @Query('unit') unit: unknown) {
    if (typeof value !== 'string' || !/^-?\d+$/.test(value)) {
      throw new BadRequestException('value debe ser un entero decimal');
    }
    if (unit !== 's' && unit !== 'ms') {
      throw new BadRequestException('unit debe ser s o ms');
    }
    const input = Number(value);
    const milliseconds = unit === 's' ? input * 1000 : input;
    if (!Number.isSafeInteger(input) || !Number.isSafeInteger(milliseconds)
        || Math.abs(milliseconds) > 8_640_000_000_000_000) {
      throw new BadRequestException('Timestamp fuera de rango');
    }
    const date = new Date(milliseconds);
    return {
      timestampMilliseconds: milliseconds,
      timestampSeconds: Math.floor(milliseconds / 1000),
      isoUtc: date.toISOString(),
    };
  }
}

@Module({ controllers: [TimestampsController] })
export class TimestampsModule {}

La comprobación del tipo también rechaza parámetros repetidos recibidos como arrays. El límite de Date es más estricto que el límite de enteros seguros de JavaScript: ambas comprobaciones son útiles antes de toISOString().

Ejemplo de petición y respuesta

GET /timestamps/convert?value=1711972800&unit=s
{
  "timestampMilliseconds": 1711972800000,
  "timestampSeconds": 1711972800,
  "isoUtc": "2024-04-01T12:00:00.000Z"
}

Los valores inválidos, la ausencia de unidad y las fechas fuera de rango producen una respuesta HTTP 400. Este endpoint de ejemplo pertenece a tu aplicación NestJS; Timestampinfo no ofrece esta API.

¿Por qué no basta con ParseIntPipe?

ParseIntPipe convierte un parámetro a entero y rechaza entradas no numéricas. No sustituye las comprobaciones de unidad, precisión y rango de tu aplicación. Para varios campos, un DTO con ValidationPipe centraliza la validación. La anotación TypeScript value: number por sí sola no valida una petición HTTP.

Variante DTO con class-validator

Para un cuerpo JSON, estos son dos contratos independientes: un entero en milisegundos o una cadena ISO con desfase explícito. Instala class-validator y class-transformer y activa la validación en main.ts.

import { ValidationPipe } from '@nestjs/common';

// Después de NestFactory.create(AppModule):
app.useGlobalPipes(new ValidationPipe({
  transform: true,
  whitelist: true,
  forbidNonWhitelisted: true,
}));
import { Type } from 'class-transformer';
import { IsInt, IsISO8601, Matches, Max, Min } from 'class-validator';

export class TimestampDto {
  @Type(() => Number)
  @IsInt()
  @Min(-8_640_000_000_000_000)
  @Max(8_640_000_000_000_000)
  timestampMs!: number;
}

export class IsoDateDto {
  @IsISO8601({ strict: true })
  @Matches(/T.*(?:Z|[+-]\d{2}:\d{2})$/)
  at!: string;
}

Utiliza estas clases con @Body() body: TimestampDto o @Body() body: IsoDateDto. Añade @Min(0) solo si las reglas de negocio prohíben fechas anteriores a 1970. La conversión @Type(() => Number) es permisiva: una cadena vacía o null pueden convertirse en cero. Si tu API debe rechazarlos, utiliza la validación estricta del primer ejemplo o rechaza estos valores antes de transformarlos.

Transformar un entero en Date con Transform

Esta variante solo acepta un número JSON entero en milisegundos. Un valor inválido se deja sin cambios para que @IsDate() lo rechace; esto evita convertir null en la época.

import { Transform } from 'class-transformer';
import { IsDate } from 'class-validator';

export class DateFromTimestampDto {
  @Transform(({ value }) =>
    typeof value === 'number' && Number.isSafeInteger(value)
      && Math.abs(value) <= 8_640_000_000_000_000
      ? new Date(value)
      : value,
    { toClassOnly: true },
  )
  @IsDate()
  at!: Date;
}

Normalizar una respuesta sin transformar todo su contenido

Un interceptor puede formatear un campo conocido. Este espera una respuesta de tipo { createdAt: Date } y solo debe aplicarse a rutas que sigan este contrato mediante @UseInterceptors(CreatedAtInterceptor). No intentes adivinar si cada entero de una respuesta es un timestamp.

import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable, map } from 'rxjs';

type Source = { createdAt: Date };
type Response = { createdAt: string };

@Injectable()
export class CreatedAtInterceptor implements NestInterceptor<Source, Response> {
  intercept(_context: ExecutionContext, next: CallHandler<Source>): Observable<Response> {
    return next.handle().pipe(
      map(({ createdAt }) => ({ createdAt: createdAt.toISOString() })),
    );
  }
}

La fecha ya debe ser válida en el servicio. Para un objeto simple, basta con llamar explícitamente a toISOString() al construir la respuesta.

Fracciones, valores negativos y unidades grandes

Este contrato rechaza intencionadamente los segundos fraccionarios. Envía milisegundos para conservar esa precisión. Para value=-1&unit=ms, el resultado ISO es 1969-12-31T23:59:59.999Z; el campo de segundos es -1, porque se redondea hacia abajo.

Para nanosegundos que superen la precisión de Number, transporta una cadena decimal y procésala con BigInt. Un BigInt no se puede serializar directamente a JSON: devuelve una cadena. La conversión posterior a Date pierde la fracción de milisegundo.

Zonas horarias y almacenamiento

toISOString() produce aquí una fecha UTC terminada en Z. Almacena un instante común en la base de datos y aplica la zona horaria al mostrarlo. Con Prisma o TypeORM, comprueba también el tipo de columna y la configuración del controlador: el ORM por sí solo no garantiza la gestión de zonas horarias. Una hora de negocio introducida sin desfase necesita una regla explícita sobre su zona y los cambios de hora.

Comprueba los valores con el Conversor Unix o el Conversor ISO 8601, compara las unidades de timestamp y consulta la guía de JavaScript para formatear con Intl.DateTimeFormat.

Referencias oficiales

NestJS — controladores · Pipes y ParseIntPipe · ValidationPipe y DTO · MDN — Date

Versión en español publicada el 26 de septiembre de 2026.