timestampinfo.fr
Tools & documentation

TIME DOCUMENTATION

NestJS timestamps: API validation and conversion

Validate timestamps received by a NestJS API, specify their unit and return a UTC date without losing precision.

Define an explicit API contract

A NestJS API uses the same dates as JavaScript: Date expects milliseconds. Ask the client for the unit instead of guessing it. The following example accepts a signed integer and unit=s or unit=ms.

Controller: validate, convert and respond with JSON

In an existing NestJS project, place this code in timestamps.module.ts and add TimestampsModule to the imports of your main module. No dependency beyond Nest is required.

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 must be a decimal integer');
    }
    if (unit !== 's' && unit !== 'ms') {
      throw new BadRequestException('unit must be s or 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 out of range');
    }
    const date = new Date(milliseconds);
    return {
      timestampMilliseconds: milliseconds,
      timestampSeconds: Math.floor(milliseconds / 1000),
      isoUtc: date.toISOString(),
    };
  }
}

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

The type check also rejects repeated parameters received as arrays. The limit of Date is stricter than the JavaScript safe-integer limit: both checks are useful before toISOString().

Example request and response

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

Invalid values, a missing unit and out-of-range dates produce an HTTP 400 response. This example endpoint belongs to your NestJS application; Timestampinfo does not provide this API.

Why is ParseIntPipe alone not enough?

ParseIntPipe converts a parameter to an integer and rejects non-numeric input. It does not replace checks for the unit, precision and range allowed by your application. For multiple fields, a DTO with ValidationPipe centralizes validation. The TypeScript annotation value: number alone does not validate an HTTP request.

DTO variant with class-validator

For a JSON body, here are two independent contracts: an integer in milliseconds or an ISO string with an explicit offset. Install class-validator and class-transformer, then enable validation in main.ts.

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

// After 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;
}

Use these classes with @Body() body: TimestampDto or @Body() body: IsoDateDto. Add @Min(0) only if your business rules prohibit dates before 1970. The conversion @Type(() => Number) is permissive: an empty string or null may become zero. If your API must reject them, use the strict validation in the first example or reject these values before transforming them.

Transform an integer into a Date with Transform

This variant accepts only an integer JSON number in milliseconds. An invalid value is left unchanged so that @IsDate() rejects it; this avoids converting null into the Epoch.

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;
}

Normalize a response without transforming the entire payload

An interceptor can format a known field. This one expects a response of { createdAt: Date } and should only be attached to routes that follow this contract, using @UseInterceptors(CreatedAtInterceptor). Do not try to guess whether every integer in a payload is a 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() })),
    );
  }
}

The date must already be valid in the service. For a simple object, explicitly calling toISOString() in the response mapping is sufficient.

Fractions, negative values and large units

This contract intentionally rejects fractional seconds. Send milliseconds to preserve that precision. For value=-1&unit=ms, the ISO result is 1969-12-31T23:59:59.999Z; the seconds field is -1, because it is rounded down.

For nanoseconds beyond the precision of Number, transport a decimal string and process it with BigInt. A BigInt cannot be serialized directly to JSON: return a string. Subsequently converting to Date loses the fraction of a millisecond.

Time zones and storage

toISOString() produces a UTC date ending in Z here. Store a common instant in the database and apply the time zone for display. With Prisma or TypeORM, also check the column type and driver configuration: the ORM alone does not guarantee time zone handling. A business time entered without an offset needs an explicit rule for its time zone and clock changes.

Check values with the Unix converter or the ISO 8601 converter, compare timestamp units and see the JavaScript guide for formatting with Intl.DateTimeFormat.

Official references

NestJS — controllers · Pipes and ParseIntPipe · ValidationPipe and DTOs · MDN — Date

English version published September 26, 2026.