Fastify and Native Date Objects: Regional Time Zone Interoperability
0 reputation · 12 Aug 2026, 10:45 UTC
0 reputation · 12 Aug 2026, 10:45 UTC
Fastify handles incoming JSON payloads and query strings by passing date-formatted strings directly to the application logic. When using JSON Schema validation with format: 'date-time', the framework ensures the string conforms to ISO 8601, but the actual conversion to a JavaScript Date object typically occurs within the handler or via a custom coercion layer.
Because the native Date constructor relies on the host environment's system time zone, there is a risk of inconsistent data interpretation when deploying the same Fastify application across servers in different geographical regions. This creates uncertainty regarding whether the framework provides a mechanism to enforce a specific time zone during the parsing phase before the data reaches the route handler.
34775 reputation · 12 Aug 2026, 17:26 UTC
Fastify does not expose a global setting that forces all incoming date‑time strings to be parsed in a specific time zone. The framework’s JSON Schema validation (via Ajv) only guarantees that a string matches the ISO 8601 format; it does not alter the string or apply a time‑zone conversion. The native Date constructor interprets ISO strings with an explicit UTC offset as UTC, and ISO strings without an offset are treated as UTC by the ECMAScript spec, independent of the host OS locale.
To avoid any drift caused by the server’s local time zone, normalise dates as early as possible in the request lifecycle:
Date objects or normalised ISO strings in UTC using a dedicated library such as luxon, date-fns-tz, or moment-timezone.date-time-utc that coerces the value into a UTC Date during validation. Enable coerceTypes so that the handler receives the coerced value.Install a zone‑aware library (e.g., npm i luxon).
Create a plugin file plugins/utc-normalizer.js:
const { DateTime } = require('luxon');
module.exports = async function (fastify, opts) {
fastify.addHook('preHandler', async (request, reply) => {
const normalise = (obj) => {
if (!obj) return;
for (const key in obj) {
const val = obj[key];
if (typeof val === 'string' && val.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|[\+-]\d{2}:\d{2})?$/)) {
// Convert to UTC Date
obj[key] = DateTime.fromISO(val, { zone: 'utc' }).toJSDate();
} else if (typeof val === 'object') {
normalise(val);
}
}
};
normalise(request.body);
normalise(request.query);
});
};Register the plugin in your Fastify instance:
const fastify = require('fastify')();
const utcNormalizer = require('./plugins/utc-normalizer');
fastify.register(utcNormalizer);
fastify.post('/event', async (req, reply) => {
// req.body.startTime is now a Date in UTC
reply.send({ received: req.body.startTime.toISOString() });
});
fastify.listen(3000);
Test by sending a payload with "2026-10-13T21:19:09.778Z" and verifying that toISOString() prints the same value regardless of the server’s TZ environment variable.
If you prefer to keep the conversion declarative within the schema, add a custom format:
const Ajv = require('ajv');
const { DateTime } = require('luxon');
const ajv = new Ajv({ coerceTypes: true });
ajv.addFormat('date-time-utc', {
type: 'string',
validate: (s) => {
const dt = DateTime.fromISO(s, { zone: 'utc' });
if (!dt.isValid) return false;
// Coerce into a Date object
return true;
}
});
// In your schema
const schema = {
type: 'object',
properties: {
startTime: { type: 'string', format: 'date-time-utc' }
}
};
With coerceTypes: true, Ajv will replace the string with the coerced Date object before the handler runs.
To fine‑tune the conversion strategy, could you confirm whether the client consistently sends an explicit UTC offset (e.g., Z or +02:00) or if some strings omit it? Knowing this helps decide whether to enforce an offset during validation or rely on the ISO default.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.