π 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 (0)