Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
Tags
- 앱심사
- abap
- JQ
- firebase
- printf
- JS
- 자바 포맷 출력
- java
- Flutter
- riverpod
- react
- 단축키
- 자바 출력 방식
- java 출력
- UI/UX
- java 콘솔 출력 차이
- unity
- lifecycle
- develop
- println
- 배포
- LLM
- npm
- Clean Architecture
- scss
- nodejs
- DART
- 엡
- 자바스크립트
- ListView
Archives
- Today
- Total
guricode
[flutter] 자취의 정석 -9: Firebase 서버시간을 한국시간(KST)으로 정확히 보여주기, FieldValue.serverTimestamp() UTC표준 변환, “N분 전” 구현 본문
앱/Flutter&Dart
[flutter] 자취의 정석 -9: Firebase 서버시간을 한국시간(KST)으로 정확히 보여주기, FieldValue.serverTimestamp() UTC표준 변환, “N분 전” 구현
agentrakugaki 2025. 10. 2. 15:43게시글과 댓글 생성시간을 FieldValue.serverTimestamp()로 사용하고있는데
URC기준이라 한국 서울 표준시간과는 9시간 차이가 나는 문제가 있다.
FieldValue.serverTimestamp()는 서버가 기록시점의 시간을 넣도록 지시하는 자리표시자다.
값 자체가 아니라 서버가 채워줄것이라는 토큰이다.
따라서 글 등록 직후는 null일수가 있는 값이다. 비동기 처리가 필요한 장치다.
이걸 화면에서 로컬로 변환하지 않으면 계속 문제가 생긴다.
해결방법으로는 이 UTC시간을 받아서 표시할때 타임존으로 변환해주는 방법이있다.
pubspec.yaml
dependencies:
intl: any
timezone: any
main.dart 한 번만 초기화
import 'package:flutter/widgets.dart';
import 'package:timezone/data/latest.dart' as tz;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
tz.initializeTimeZones(); // ← 필수. 앱 시작 시 1회만
runApp(const MyApp());
}
공통으로 쓸 서울 타임존(파일 상단에 선언)
import 'package:timezone/timezone.dart' as tz;
final tz.Location _seoul = tz.getLocation('Asia/Seoul');
게시글 헤더 시간: KST로 포맷해 표시
적용 전
final created = st.post == null
? '09.17 17:47'
: DateFormat('MM.dd HH:mm').format(st.post!.communityCreateDate); // 로컬/UTC 혼재 가능
적용 후
import 'package:intl/intl.dart';
import 'package:timezone/timezone.dart' as tz;
final tz.Location _seoul = tz.getLocation('Asia/Seoul');
final created = st.post == null
? '09.17 17:47'
: DateFormat('MM.dd HH:mm').format(
tz.TZDateTime.from(st.post!.communityCreateDate.toUtc(), _seoul),
);
// String 그대로 헤더에 전달
_HeaderRow(
author: author,
createdAt: created,
authorImg: authorImg.isEmpty
? 'assets/images/m_profile/m_black.png'
: authorImg,
);
엔티티에는 UTC DateTime을 보관. 화면에서만 TZDateTime.from(utc, _seoul)로 변환.
댓글 “N분 전”을 KST 기준으로 표시
리스트에 UTC 전달
CommentList(
// ...
createdAtOf: (i) => st.comments[i].createAt.toUtc(), // ← 반드시 UTC 보장
)
KST 상대시각 위젯(1분마다 갱신)
comment_list.dart에 추가:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:timezone/timezone.dart' as tz;
final tz.Location _seoul = tz.getLocation('Asia/Seoul');
class RelativeTimeTextKst extends StatefulWidget {
final DateTime createdAtUtc; // UTC 전제
final TextStyle? style;
const RelativeTimeTextKst({super.key, required this.createdAtUtc, this.style});
@override
State<RelativeTimeTextKst> createState() => _RelativeTimeTextKstState();
}
class _RelativeTimeTextKstState extends State<RelativeTimeTextKst> {
Timer? _t;
String _text = '';
String _format() {
final nowKst = tz.TZDateTime.from(DateTime.now().toUtc(), _seoul);
final kst = tz.TZDateTime.from(widget.createdAtUtc, _seoul);
var diff = nowKst.difference(kst);
if (diff.isNegative) diff = Duration.zero;
if (diff.inSeconds < 5) return '방금 전';
if (diff.inSeconds < 60) return '${diff.inSeconds}초 전';
if (diff.inMinutes < 60) return '${diff.inMinutes}분 전';
if (diff.inHours < 24) return '${diff.inHours}시간 전';
if (diff.inDays < 7) return '${diff.inDays}일 전';
final w = (diff.inDays / 7).floor();
if (diff.inDays < 30) return '${w}주 전';
final m = (diff.inDays / 30).floor();
if (diff.inDays < 365) return '${m}개월 전';
return '${(diff.inDays / 365).floor()}년 전';
}
void _tick() {
_text = _format();
if (mounted) setState(() {});
}
@override
void initState() {
super.initState();
_tick();
_t = Timer.periodic(const Duration(minutes: 1), (_) => _tick());
}
@override
void dispose() {
_t?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) => Text(_text, style: widget.style);
}
닉네임 옆에 배치
Row(
children: [
// 닉네임 ...
const SizedBox(width: 8),
RelativeTimeTextKst(
createdAtUtc: createdAtOf(i), // 위에서 toUtc()로 넘겼음
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
],
)
저장 로직은 그대로 UTC
await ref.set({
'comment_create_date': FieldValue.serverTimestamp(), // 저장=서버 UTC
});
CommunityDetailScreen
// 1) imports
import 'package:intl/intl.dart';
import 'package:timezone/timezone.dart' as tz;
final tz.Location _seoul = tz.getLocation('Asia/Seoul');
// 2) 헤더 표기
final created = st.post == null
? '09.17 17:47'
: DateFormat('MM.dd HH:mm').format(
tz.TZDateTime.from(st.post!.communityCreateDate.toUtc(), _seoul),
);
_HeaderRow(author: author, createdAt: created, authorImg: authorImg);
// 3) 댓글 리스트로 UTC 전달
CommentList(
// ...
createdAtOf: (i) => st.comments[i].createAt.toUtc(),
);
comment_list.dart
// 닉네임 옆에 상대시각
Row(
children: [
// nickname ...
const SizedBox(width: 8),
RelativeTimeTextKst(
createdAtUtc: createdAtOf(i),
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
],
);
시도했던 방법들
- print(FieldValue.serverTimestamp())로 값 확인 시도 → 불가. 자리표시자일 뿐.
- 저장 때 DateTime.now() 사용 → 기기 시계 오차 반영.
- UTC와 로컬 DateTime 섞어 차이 계산 → 상대시각 오차 발생.
- 항상 UTC→KST로 양쪽을 동일 타임존으로 맞춘 뒤 차이를 구해야한다.
추가팁)
기기 시계 오차까지 제거하고 싶으면 RTDB .info/serverTimeOffset으로 서버-클라 오프셋을 받아 now에 더해 사용.
KST 기준 “하루치 조회”가 필요하면 Cloud Functions로 createdAtKstYmd = 20251002 같은 파생 필드를 추가해 where로 필터링.
'앱 > Flutter&Dart' 카테고리의 다른 글
| Riverpod ref.listen 라이프사이클 에러 트러블슈팅 (0) | 2025.10.09 |
|---|---|
| [트러블슈팅] iOS와 Android 동시 배포 완전 정리 (0) | 2025.10.08 |
| [flutter]자취의 정석 -8 게시글, 댓글 페이지네이션 microtask (0) | 2025.10.02 |
| [flutter]자취의 정석 -7 커뮤니티 목록 안 뜸 / ref dispose 오류 (0) | 2025.09.29 |
| [flutter]자취의 정석 -6 댓글 꾹~ 눌렀을때 메뉴 펼치기(신고하기,차단하기 등등) (0) | 2025.09.22 |