Tutorial
Práticas recomendadas de validação de carimbo de data/hora: um guia completo
Introdução
A validação de carimbos de data/hora é crucial para a integridade dos dados, segurança e confiabilidade dos aplicativos. Carimbos de data e hora inválidos podem levar a erros de banco de dados, cálculos incorretos, vulnerabilidades de segurança e experiência do usuário insatisfatória. Este guia abrangente ensinará as melhores práticas para validação de carimbo de data/hora em diferentes cenários.
Por que validar carimbos de data/hora?
Problemas comuns com carimbos de data/hora inválidos
- Corrupção de dados: carimbos de data/hora inválidos podem corromper dados históricos e análises
- Riscos de segurança: a manipulação do carimbo de data/hora pode ignorar a autenticação e autorização
- Erros de lógica de negócios: carimbos de data/hora incorretos podem afetar o faturamento, a programação e os relatórios
- Falhas de integração: formatos inválidos podem interromper integrações de API e pipelines de dados
- Problemas de desempenho: validação deficiente pode causar ineficiências nas consultas ao banco de dados
Impacto no mundo real
// Without validation - vulnerable to attacks
const expiryTime = req.body.expiryTime; // Could be: 9999999999999
if (Date.now() < expiryTime) {
grantAccess(); // Attacker gets permanent access!
}
// With validation - secure
const expiryTime = validateTimestamp(req.body.expiryTime, {
min: Date.now(),
max: Date.now() + 86400000 // 24 hours max
});
if (expiryTime && Date.now() < expiryTime) {
grantAccess(); // Safe access control
}
Técnicas Básicas de Validação
1. Validação de intervalo
Sempre valide se os carimbos de data/hora estão dentro dos limites aceitáveis.
Exemplo de JavaScript
function validateTimestampRange(timestamp, options = {}) {
const {
min = 0, // Unix epoch start (1970-01-01)
max = 253402300799000, // Year 9999 in milliseconds
precision = 'milliseconds'
} = options;
// Normalize to milliseconds
let ts = timestamp;
if (precision === 'seconds' && String(timestamp).length === 10) {
ts = timestamp * 1000;
} else if (precision === 'microseconds' && String(timestamp).length === 16) {
ts = Math.floor(timestamp / 1000);
}
// Type check
if (typeof ts !== 'number' || isNaN(ts)) {
return { valid: false, error: 'Timestamp must be a valid number' };
}
// Finite check
if (!Number.isFinite(ts)) {
return { valid: false, error: 'Timestamp must be finite' };
}
// Range check
if (ts < min) {
return { valid: false, error: `Timestamp ${ts} is before minimum ${min}` };
}
if (ts > max) {
return { valid: false, error: `Timestamp ${ts} exceeds maximum ${max}` };
}
return { valid: true, normalized: ts };
}
// Usage
const result = validateTimestampRange(1704067200000, {
min: Date.parse('2024-01-01'),
max: Date.parse('2024-12-31')
});
console.log(result);
// { valid: true, normalized: 1704067200000 }
Exemplo de Python
from datetime import datetime, timezone
def validate_timestamp_range(timestamp, min_ts=0, max_ts=253402300799):
"""
Validate timestamp is within acceptable range.
Args:
timestamp: Unix timestamp (seconds or milliseconds)
min_ts: Minimum allowed timestamp (default: Unix epoch)
max_ts: Maximum allowed timestamp (default: year 9999)
Returns:
dict: Validation result with 'valid' boolean and optional 'error'
"""
# Type validation
if not isinstance(timestamp, (int, float)):
return {'valid': False, 'error': 'Timestamp must be a number'}
# Detect precision and normalize to seconds
if timestamp > 10000000000: # Likely milliseconds
ts = timestamp / 1000
else:
ts = timestamp
# Finite check
if not (-float('inf') < ts < float('inf')):
return {'valid': False, 'error': 'Timestamp must be finite'}
# Range validation
if ts < min_ts:
return {
'valid': False,
'error': f'Timestamp {ts} is before minimum {min_ts}'
}
if ts > max_ts:
return {
'valid': False,
'error': f'Timestamp {ts} exceeds maximum {max_ts}'
}
return {'valid': True, 'normalized': ts}
# Usage
result = validate_timestamp_range(
1704067200,
min_ts=datetime(2024, 1, 1, tzinfo=timezone.utc).timestamp(),
max_ts=datetime(2024, 12, 31, tzinfo=timezone.utc).timestamp()
)
print(result)
# {'valid': True, 'normalized': 1704067200.0}
2. Validação de formato
Validar o formato do carimbo de data/hora corresponde ao padrão esperado.
Padrões Regex
// Timestamp format validators
const timestampFormats = {
// Unix timestamp - seconds (10 digits)
unixSeconds: /^[0-9]{10}$/,
// Unix timestamp - milliseconds (13 digits)
unixMilliseconds: /^[0-9]{13}$/,
// ISO 8601 basic format
iso8601: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/,
// ISO 8601 with timezone
iso8601Tz: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?([+-]\d{2}:\d{2}|Z)$/,
// RFC 3339
rfc3339: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/,
};
function validateTimestampFormat(timestamp, format = 'unixMilliseconds') {
const pattern = timestampFormats[format];
if (!pattern) {
return { valid: false, error: `Unknown format: ${format}` };
}
const timestampStr = String(timestamp);
if (!pattern.test(timestampStr)) {
return {
valid: false,
error: `Timestamp "${timestampStr}" does not match ${format} format`
};
}
return { valid: true };
}
// Usage examples
console.log(validateTimestampFormat(1704067200000, 'unixMilliseconds'));
// { valid: true }
console.log(validateTimestampFormat('2024-01-01T00:00:00Z', 'iso8601'));
// { valid: true }
console.log(validateTimestampFormat(170406720, 'unixMilliseconds'));
// { valid: false, error: 'Timestamp "170406720" does not match...' }
3. Detecção de precisão
Detecte e valide automaticamente a precisão do carimbo de data/hora.
function detectTimestampPrecision(timestamp) {
const tsStr = String(timestamp);
const length = tsStr.length;
// Precision mapping
const precisionMap = {
10: { precision: 'seconds', multiplier: 1000 },
13: { precision: 'milliseconds', multiplier: 1 },
16: { precision: 'microseconds', multiplier: 0.001 },
19: { precision: 'nanoseconds', multiplier: 0.000001 }
};
const detected = precisionMap[length];
if (!detected) {
return {
valid: false,
error: `Unable to detect precision for ${length}-digit timestamp`
};
}
// Convert to milliseconds
const normalized = timestamp * detected.multiplier;
// Sanity check - should be between 1970 and year 3000
const minDate = 0; // 1970-01-01
const maxDate = 32503680000000; // 3000-01-01
if (normalized < minDate || normalized > maxDate) {
return {
valid: false,
error: `Timestamp ${timestamp} with ${detected.precision} precision is out of valid range`
};
}
return {
valid: true,
precision: detected.precision,
normalized: Math.floor(normalized),
original: timestamp
};
}
// Usage
console.log(detectTimestampPrecision(1704067200));
// { valid: true, precision: 'seconds', normalized: 1704067200000, original: 1704067200 }
console.log(detectTimestampPrecision(1704067200000));
// { valid: true, precision: 'milliseconds', normalized: 1704067200000, original: 1704067200000 }
4. Validação de fuso horário
Validar se as informações de fuso horário estão presentes e são válidas.
function validateTimezone(dateString) {
// Check if timezone designator is present
const hasTimezone = /Z|[+-]\d{2}:\d{2}$/.test(dateString);
if (!hasTimezone) {
return {
valid: false,
warning: 'No timezone information - timestamp is ambiguous'
};
}
// Extract timezone
const tzMatch = dateString.match(/(Z|[+-]\d{2}:\d{2})$/);
const timezone = tzMatch ? tzMatch[1] : null;
// Validate timezone offset range
if (timezone && timezone !== 'Z') {
const [sign, hours, minutes] = timezone.match(/([+-])(\d{2}):(\d{2})/).slice(1);
const offset = parseInt(sign + hours) * 60 + parseInt(sign + minutes);
// Valid timezone range: -12:00 to +14:00
if (offset < -720 || offset > 840) {
return {
valid: false,
error: `Invalid timezone offset: ${timezone}`
};
}
}
return {
valid: true,
timezone: timezone === 'Z' ? 'UTC' : timezone
};
}
// Usage
console.log(validateTimezone('2024-01-01T00:00:00Z'));
// { valid: true, timezone: 'UTC' }
console.log(validateTimezone('2024-01-01T00:00:00+05:30'));
// { valid: true, timezone: '+05:30' }
console.log(validateTimezone('2024-01-01T00:00:00'));
// { valid: false, warning: 'No timezone information - timestamp is ambiguous' }
Considerações de segurança
1. Prevenir ataques baseados no tempo
// BAD: Vulnerable to timing attacks
function validateToken(token, expiryTimestamp) {
return Date.now() < expiryTimestamp;
}
// GOOD: Secure validation with constant-time comparison
function validateTokenSecure(token, expiryTimestamp) {
// Validate timestamp first
const validation = validateTimestampRange(expiryTimestamp, {
min: Date.now(),
max: Date.now() + 86400000 // Max 24 hours in future
});
if (!validation.valid) {
return false;
}
// Use constant-time comparison for sensitive checks
const now = Date.now();
const isValid = now < validation.normalized;
// Add jitter to prevent timing analysis
const jitter = Math.floor(Math.random() * 10);
return isValid;
}
2. Evitar estouro de número inteiro
function safeTimestampAddition(timestamp, milliseconds) {
// Check for potential overflow before adding
if (timestamp > Number.MAX_SAFE_INTEGER - milliseconds) {
throw new Error('Timestamp addition would cause overflow');
}
const result = timestamp + milliseconds;
// Verify result is within safe integer range
if (!Number.isSafeInteger(result)) {
throw new Error('Result timestamp exceeds safe integer range');
}
return result;
}
// Usage
try {
const future = safeTimestampAddition(1704067200000, 86400000);
console.log(future); // 1704153600000
} catch (error) {
console.error(error.message);
}
3. Prevenção de injeção de SQL
// BAD: Vulnerable to SQL injection
function getUserActivityUnsafe(userId, startTime) {
const query = `SELECT * FROM activities WHERE user_id = ${userId}
AND created_at > ${startTime}`;
return db.query(query); // DANGEROUS!
}
// GOOD: Using parameterized queries with validation
function getUserActivitySafe(userId, startTime) {
// Validate timestamp first
const validation = validateTimestampRange(startTime, {
min: 0,
max: Date.now()
});
if (!validation.valid) {
throw new Error(`Invalid timestamp: ${validation.error}`);
}
// Use parameterized query
const query = 'SELECT * FROM activities WHERE user_id = ? AND created_at > ?';
return db.query(query, [userId, new Date(validation.normalized)]);
}
Função de validação completa
Aqui está um validador de carimbo de data/hora pronto para produção que combina todas as práticas recomendadas:
class TimestampValidator {
constructor(options = {}) {
this.minDate = options.minDate || new Date('1970-01-01');
this.maxDate = options.maxDate || new Date('2099-12-31');
this.allowedFormats = options.formats || ['unix', 'iso8601'];
this.requireTimezone = options.requireTimezone || false;
}
validate(timestamp) {
const errors = [];
const warnings = [];
// Step 1: Type validation
if (timestamp === null || timestamp === undefined) {
errors.push('Timestamp is required');
return { valid: false, errors };
}
// Step 2: Format detection and parsing
let parsedDate;
let detectedFormat;
if (typeof timestamp === 'number') {
detectedFormat = 'unix';
const precision = detectTimestampPrecision(timestamp);
if (!precision.valid) {
errors.push(precision.error);
return { valid: false, errors };
}
parsedDate = new Date(precision.normalized);
} else if (typeof timestamp === 'string') {
// Try ISO 8601 parsing
parsedDate = new Date(timestamp);
detectedFormat = 'iso8601';
// Validate timezone if required
if (this.requireTimezone) {
const tzValidation = validateTimezone(timestamp);
if (!tzValidation.valid) {
if (tzValidation.error) {
errors.push(tzValidation.error);
} else if (tzValidation.warning) {
warnings.push(tzValidation.warning);
}
}
}
} else {
errors.push(`Invalid timestamp type: ${typeof timestamp}`);
return { valid: false, errors };
}
// Step 3: Check if format is allowed
if (!this.allowedFormats.includes(detectedFormat)) {
errors.push(`Format ${detectedFormat} is not allowed. Allowed: ${this.allowedFormats.join(', ')}`);
}
// Step 4: Validate parsed date is valid
if (isNaN(parsedDate.getTime())) {
errors.push('Timestamp could not be parsed to a valid date');
return { valid: false, errors };
}
// Step 5: Range validation
if (parsedDate < this.minDate) {
errors.push(`Timestamp ${parsedDate.toISOString()} is before minimum ${this.minDate.toISOString()}`);
}
if (parsedDate > this.maxDate) {
errors.push(`Timestamp ${parsedDate.toISOString()} exceeds maximum ${this.maxDate.toISOString()}`);
}
// Step 6: Additional checks
const timestamp_ms = parsedDate.getTime();
// Check for JavaScript Date edge cases
if (timestamp_ms === -62135596800000) {
warnings.push('Timestamp represents year 0 which may have parsing issues');
}
return {
valid: errors.length === 0,
errors: errors.length > 0 ? errors : undefined,
warnings: warnings.length > 0 ? warnings : undefined,
parsed: parsedDate,
format: detectedFormat,
timestamp: timestamp_ms
};
}
}
// Usage examples
const validator = new TimestampValidator({
minDate: new Date('2024-01-01'),
maxDate: new Date('2024-12-31'),
requireTimezone: true,
formats: ['unix', 'iso8601']
});
// Valid timestamp
console.log(validator.validate(1704067200000));
// {
// valid: true,
// parsed: Date,
// format: 'unix',
// timestamp: 1704067200000
// }
// Invalid - out of range
console.log(validator.validate(1735689600000)); // 2025-01-01
// {
// valid: false,
// errors: ['Timestamp exceeds maximum...']
// }
// Invalid - missing timezone
console.log(validator.validate('2024-06-15T10:30:00'));
// {
// valid: false,
// errors: ['No timezone information - timestamp is ambiguous']
// }
Práticas recomendadas para tratamento de erros
1. Limpar mensagens de erro
function formatValidationError(validation) {
if (validation.valid) {
return null;
}
return {
message: 'Timestamp validation failed',
details: validation.errors,
suggestions: [
'Ensure timestamp is in correct format (Unix milliseconds or ISO 8601)',
'Check timestamp is within valid range (1970-2099)',
'Include timezone information (Z or +HH:MM)',
'Use 13-digit Unix timestamp for millisecond precision'
]
};
}
2. Degradação graciosa
function parseTimestampWithFallback(timestamp, fallback = null) {
const validation = validator.validate(timestamp);
if (validation.valid) {
return validation.parsed;
}
// Log error for monitoring
console.warn('Timestamp validation failed:', validation.errors);
// Return fallback value
return fallback ? new Date(fallback) : new Date();
}
Testando seus validadores
// Test cases for timestamp validation
const testCases = [
// Valid cases
{ input: 1704067200000, expected: true, description: 'Unix milliseconds' },
{ input: '2024-01-01T00:00:00Z', expected: true, description: 'ISO 8601 UTC' },
// Invalid cases
{ input: null, expected: false, description: 'Null timestamp' },
{ input: 'invalid', expected: false, description: 'Invalid string' },
{ input: -1, expected: false, description: 'Negative timestamp' },
{ input: Infinity, expected: false, description: 'Infinity' },
{ input: NaN, expected: false, description: 'NaN' },
// Edge cases
{ input: 0, expected: true, description: 'Unix epoch' },
{ input: 253402300799000, expected: true, description: 'Max timestamp (year 9999)' },
];
testCases.forEach(test => {
const result = validator.validate(test.input);
const passed = result.valid === test.expected;
console.log(`${passed ? '✓' : '✗'} ${test.description}`);
if (!passed) {
console.log(' Expected:', test.expected, 'Got:', result.valid);
}
});
Armadilhas comuns a serem evitadas
- Não validando tipo de entrada - Sempre verifique typeof antes de processar
- Ignorar informações de fuso horário - Pode levar a erros fora do horário comercial
- Usando igualdade flexível (==) - Use igualdade estrita (===) para comparações
- Não trata de casos extremos - Teste com 0, valores negativos, números muito grandes
- Esquecendo diferenças de precisão - Segundos vs milissegundos vs microssegundos
- Confiar em carimbos de data/hora do cliente - Sempre valide no lado do servidor
- Não registrar falhas de validação - Dificulta a depuração
Conclusão
A validação adequada do carimbo de data/hora é essencial para a construção de aplicativos robustos. Seguindo estas práticas recomendadas:
- Sempre valide o intervalo - Certifique-se de que os carimbos de data e hora estejam dentro dos limites aceitáveis
- Verifique o formato explicitamente - Não assuma o formato de entrada
- Trate dos fusos horários com cuidado - Exija informações de fuso horário quando necessário
- Considere as implicações de segurança - Evite manipulação e ataques
- Forneça mensagens de erro claras - Ajude usuários e desenvolvedores a depurar problemas
- Teste completamente - Cubra casos extremos e entradas inválidas
Use essas técnicas de validação para proteger seu aplicativo contra dados inválidos, vulnerabilidades de segurança e erros lógicos.