π Problem Summary
Environment Setup
- Frontend (React + Vite): No timezone configuration β Uses Browser Default (UTC+7)
- Backend (NestJS + TypeORM): No timezone configuration
- Database: Configured as UTC+0
- Server Environment: UTC+7 (Thailand timezone)
Symptoms
-
Frontend: Creates transaction at
2025-06-16 13:52
(UTC+7) -
Backend receives:
2025-06-16 06:52
(UTC) β Correct -
Database stores:
2025-06-16 06:52
(UTC) β Correct -
Backend reads from Database:
2025-06-15T23:52
β Wrong by 7 hours! -
Swagger displays:
2025-06-15 23:52
β Wrong!
π Frontend Timezone Behavior
React/JavaScript Without Timezone Config = Uses Browser Default
Timezone Source in Browser
// JavaScript gets timezone from Browser's Local Timezone
console.log(Intl.DateTimeFormat().resolvedOptions().timeZone);
// Result: "Asia/Bangkok" (if in Thailand)
console.log(new Date().getTimezoneOffset());
// Result: -420 (minutes) = -7 hours (UTC+7)
Timezone Source Priority Order
- Operating System (Windows/Mac/Linux timezone setting)
- Browser setting (sometimes can override)
- Location detection (if browser allows)
Date Object Behavior in Frontend
// When user creates transaction in Thailand (UTC+7)
const now = new Date(); // Current time
console.log(now.toString());
// "Mon Jun 16 2025 13:52:00 GMT+0700 (Indochina Time)"
console.log(now.toLocaleString());
// "16/6/2025, 13:52:00" (displays as local time)
console.log(now.toISOString());
// "2025-06-16T06:52:00.000Z" (converts to UTC for Backend)
Sending Data to Backend
// React sends data
fetch('/api/transactions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
amount: 1000,
createdAt: new Date() // JavaScript auto-converts to ISO string
})
});
// JSON.stringify automatically calls toISOString()
// Sends as: "2025-06-16T06:52:00.000Z" β
Correct!
Testing Frontend Timezone
Add this Component to Check Timezone Info
// TimezoneDebug.jsx
import { useEffect, useState } from 'react';
function TimezoneDebug() {
const [timezoneInfo, setTimezoneInfo] = useState({});
useEffect(() => {
const now = new Date();
setTimezoneInfo({
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
offset: now.getTimezoneOffset() / -60,
localTime: now.toLocaleString(),
isoTime: now.toISOString(),
utcTime: now.toUTCString()
});
}, []);
return (
<div style={{ padding: '16px', border: '1px solid #ccc', margin: '8px' }}>
<h3>Browser Timezone Info</h3>
<p><strong>Timezone:</strong> {timezoneInfo.timezone}</p>
<p><strong>UTC Offset:</strong> +{timezoneInfo.offset} hours</p>
<p><strong>Local Time:</strong> {timezoneInfo.localTime}</p>
<p><strong>ISO Time (sent to Backend):</strong> {timezoneInfo.isoTime}</p>
<p><strong>UTC Time:</strong> {timezoneInfo.utcTime}</p>
</div>
);
}
export default TimezoneDebug;
Expected Results (in Thailand)
Timezone: Asia/Bangkok
UTC Offset: +7 hours
Local Time: 16/6/2025, 13:52:00
ISO Time (sent to Backend): 2025-06-16T06:52:00.000Z
UTC Time: Mon, 16 Jun 2025 06:52:00 GMT
Displaying Time from Backend
// Display time from Backend correctly in User's timezone
function TransactionTime({ utcTimeFromBackend }) {
// Backend sends: "2025-06-16T06:52:00.000Z"
// Display as: "16/6/2025, 13:52:00" (local time)
const localTime = new Date(utcTimeFromBackend).toLocaleString('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
return <span>{localTime}</span>;
}
Frontend Behavior Summary
Frontend is working correctly! β
-
User sees:
13:52
(local time UTC+7) -
Sends to Backend:
06:52 UTC
(auto-converted) - This behavior is standard and correct
The problem is in Backend (TypeORM), not Frontend!
π Root Cause Analysis
TypeORM Timezone Conversion Process
1. Main Issue: TypeORM Doesn't Know Database Timezone
// When no timezone config in TypeORM
TypeOrmModule.forRoot({
type: 'mysql',
// Missing timezone config β Problem is here!
})
2. What Happens Inside TypeORM
// Database returns: "2025-06-16 06:52:00" (no timezone info)
// TypeORM thinks: "This is time in Server Local Timezone (UTC+7)"
// TypeORM converts: 06:52 UTC+7 β 23:52 UTC (previous day)
// Result: "2025-06-15T23:52" β
3. Wrong Conversion Process
Database (UTC+0): 2025-06-16 06:52:00
β
TypeORM assumes: 2025-06-16 06:52:00 (UTC+7) β Wrong interpretation!
β
Converts to UTC: 2025-06-16 06:52 - 7 hours = 2025-06-15 23:52
β
Final result: 2025-06-15T23:52 β
β Solutions
1. Fix TypeORM Database Configuration
Method 1: In app.module.ts
// app.module.ts
TypeOrmModule.forRoot({
type: 'mysql', // or postgres
host: 'your-host',
port: 3306,
username: 'username',
password: 'password',
database: 'database_name',
// β Critical: Tell TypeORM that database uses UTC
timezone: 'Z', // or '+00:00'
// Additional settings
dateStrings: false, // Return Date objects instead of strings
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: false,
})
Method 2: In ormconfig.json
{
"type": "mysql",
"host": "your-host",
"port": 3306,
"username": "username",
"password": "password",
"database": "database_name",
"timezone": "Z",
"dateStrings": false,
"entities": ["dist/**/*.entity{.ts,.js}"],
"synchronize": false
}
Method 3: Using Environment Variables
// app.module.ts
TypeOrmModule.forRoot({
type: process.env.DB_TYPE as any,
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
timezone: 'Z', // β Add this line
entities: [__dirname + '/**/*.entity{.ts,.js}'],
})
2. Set Server Timezone (Additional)
In main.ts
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// β Force Node.js to use UTC
process.env.TZ = 'UTC';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Debug timezone (remove after fixing)
console.log('Server timezone:', Intl.DateTimeFormat().resolvedOptions().timeZone);
console.log('Process TZ:', process.env.TZ);
await app.listen(3000);
}
bootstrap();
3. Fix in Entity (For Additional Control)
// transaction.entity.ts
import { Entity, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('transactions')
export class Transaction {
@Column({ type: 'int', primary: true, generated: true })
id: number;
// β Force column to be UTC
@CreateDateColumn({
type: 'timestamp',
transformer: {
to: (value: Date) => value,
from: (value: string | Date) => {
// Force database data to be interpreted as UTC
if (typeof value === 'string') {
return new Date(value + (value.includes('Z') ? '' : 'Z'));
}
return value;
}
}
})
createdAt: Date;
@UpdateDateColumn({
type: 'timestamp',
transformer: {
to: (value: Date) => value,
from: (value: string | Date) => {
if (typeof value === 'string') {
return new Date(value + (value.includes('Z') ? '' : 'Z'));
}
return value;
}
}
})
updatedAt: Date;
}
π§ͺ Testing and Debugging
1. Create Debug Endpoint
// In controller
@Get('debug-timezone')
async debugTimezone() {
// Test raw query
const rawQuery = await this.transactionRepository.query(
'SELECT created_at FROM transactions ORDER BY id DESC LIMIT 1'
);
// Test TypeORM query
const typeormQuery = await this.transactionRepository.findOne({
order: { id: 'DESC' }
});
return {
rawFromDatabase: rawQuery[0]?.created_at,
fromTypeORM: typeormQuery?.createdAt,
typeormISO: typeormQuery?.createdAt?.toISOString(),
serverTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
processEnvTZ: process.env.TZ
};
}
2. Expected Results After Fix
{
"rawFromDatabase": "2025-06-16 06:52:00", // Direct from database
"fromTypeORM": "2025-06-16T06:52:00.000Z", // Through TypeORM (with Z)
"typeormISO": "2025-06-16T06:52:00.000Z", // Converted to ISO string
"serverTimezone": "UTC", // Server timezone
"processEnvTZ": "UTC" // Process timezone
}
π Fix Checklist
β What to Do
- [ ] Add
timezone: 'Z'
to TypeORM config - [ ] Set
process.env.TZ = 'UTC'
in main.ts - [ ] Test with debug endpoint
- [ ] Verify Swagger displays correct time
β οΈ What to Avoid
- Don't change database configuration (keep it as UTC+0)
- Don't modify Frontend (let it work normally)
- Check existing data - may need to convert existing records
π― Results After Fix
Correct Timeline
-
Frontend:
2025-06-16 13:52
(UTC+7) -
Sent to Backend:
2025-06-16T06:52:00.000Z
(UTC) β -
Database stores:
2025-06-16 06:52
(UTC) β -
Backend reads:
2025-06-16T06:52:00.000Z
(UTC) β -
Swagger displays:
2025-06-16T06:52:00.000Z
or13:52 local time
β
Frontend Display
// React will display time in user's timezone automatically
const date = new Date("2025-06-16T06:52:00.000Z");
console.log(date.toLocaleString('en-US')); // "6/16/2025, 1:52:00 PM" (UTC+7)
π‘ Best Practices
1. Recommended Approach
- Always store data as UTC in database
- Configure Backend to work in UTC
- Let Frontend handle timezone display for users
2. Avoid
- Don't store local timezone data in database
- Don't let Backend convert timezones manually
- Don't use strings for time management - use Date objects
π References
- TypeORM Documentation - Connection Options
- MySQL Timezone Documentation
- JavaScript Date and Time Best Practices
π€ Contributing
Found this helpful? Please star β and share with others who might face similar timezone issues!
If you have additional solutions or improvements, feel free to submit a PR or open an issue.
π License
This documentation is provided under MIT License. Feel free to use and modify as needed.
Top comments (1)
THANK YOU! THANK YOU!!!
To anyone else struggling. In my case, setting
timezone: 'Z'
didn't help because I was using asqlserver
as a driver.For the
sqlserver
driver, you have to add the following to your TypeORM DataSource configuration: