์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ |
---|---|---|---|---|---|---|
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 |
- TMDB
- CSS
- API
- Database
- Github Pages
- Protocol
- nosql
- data
- JavaScript
- supabase
- Cloud
- Boostrap
- firestoredatabase
- bootstrap
- W
- db
- REACT
- web
- Fetch
- useEffect
- til
- HTML
- http
- jQuery
- url
- IntersectionObserver
- ๋ฐฐํฌ
- github
- SQL
- this
- Today
- Total
072DATA
๋ ์ง ์ฒ๋ฆฌ ํธ๋ฌ๋ธ ์ํ (Timezone ์ด์) ๋ณธ๋ฌธ
๋ ์ง ์ฒ๋ฆฌ ํธ๋ฌ๋ธ ์ํ (Timezone ์ด์)
0720 2024. 11. 7. 03:11๐ ๋ฌธ์ ์ํฉ
์ฌ์ฉ์๊ฐ ํ๋ฃจ์ ํ ๋ฒ๋ง ์ฑ๋ฆฐ์ง๋ฅผ ์ ์ถํ ์ ์์ด์ผ ํ๋๋ฐ ์ค๋ณต ์ ์ถ์ด ๊ฐ๋ฅํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค.
๋ฐ์ํ ๊ตฌ์ฒด์ ์ธ ๋ฌธ์ ์
- ๊ฐ์ ๋ ์ง์ ์ฌ๋ฌ ๋ฒ ์ฑ๋ฆฐ์ง ์ ์ถ์ด ๊ฐ๋ฅ
- ์๊ฐ๋(Timezone) ์ฒ๋ฆฌ๋ก ์ธํ ๋ ์ง ๊ณ์ฐ ์ค๋ฅ
- DB์ timestamptz ํ์ ๊ณผ ํ๋ก ํธ์๋์ ๋ ์ง ์ฒ๋ฆฌ ๋ถ์ผ์น
- ์บ๋ฆฐ๋ ๋ฐ์ดํฐ ํ์ ๋ถ์ผ์น
- ๋์ผํ ์๊ฐ๋ ์ด์๋ก ์ธํด ์บ๋ฆฐ๋์ ์ฑ๋ฆฐ์ง ๋ฐ์ดํฐ๊ฐ ์๋ชป๋ ๋ ์ง์ ํ์๋๊ฑฐ๋ ๋๋ฝ๋๋ ํ์ ๋ฐ์
- ์๋ณ ํต๊ณ ๋ฐ์ดํฐ ๊ณ์ฐ ์ ๋ ์ง ๊ธฐ์ค์ด ๋ถ๋ช ํํ ๋ฌธ์
๋ฌธ์ ์ ์์ธ
1. ๋ณต์กํ ์๊ฐ ๋ฒ์ ์ฟผ๋ฆฌ ์ ๊ทผ
์ด๊ธฐ ํด๊ฒฐ ์๋์์๋ ๋ค์๊ณผ ๊ฐ์ ๋ณต์กํ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ต๋๋ค...
const { data: todayChallenge } = await supabase
.from("challenges")
.select("created_at")
.eq("user_id", params.userId)
.gte("created_at", today.utc().format())
.lt("created_at", tomorrow.utc().format())
- ์๊ฐ๋ ๋ณํ ๊ณผ์ ์์ ์ค๋ฅ ๋ฐ์ ๊ฐ๋ฅ์ฑ
- ์ฟผ๋ฆฌ ์กฐ๊ฑด์ด ๋ณต์กํด์ ธ ์๋์น ์์ ๊ฒฐ๊ณผ ๋ฐ์
- UTC/KST ๋ณํ ๊ณผ์ ์์ ๊ฒฝ๊ณ ์ผ์ด์ค ์ฒ๋ฆฌ ๋ฏธํก
2. ์๊ฐ๋(Timezone) ์ฒ๋ฆฌ ์ด์
// ์๋ชป๋ ์ ๊ทผ
const today = dayjs().startOf("day");
const tomorrow = today.add(1, "day");
- ์๋ฒ์ ์๊ฐ๋, DB์ ์๊ฐ๋, ํด๋ผ์ด์ธํธ์ ์๊ฐ๋๊ฐ ๊ฐ๊ฐ ๋ฌ๋ผ ํผ์ ๋ฐ์
- ๋ ์ง ๋ณํ ์์ ์ด ๋ถ๋ช ํ
- KST(ํ๊ตญ ์๊ฐ)๋ฅผ ๊ณ ๋ คํ์ง ์์ ๋ ์ง ๊ณ์ฐ
3. ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ์ ๋ถ์ผ์น
created_at TIMESTAMPTZ DEFAULT now()
- DB๋ timestamptz ํ์ ์ ์ฌ์ฉํ์ง๋ง, ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ๋จ์ ๋ฌธ์์ด๋ก ์ฒ๋ฆฌ
- ์๊ฐ๋ ์ ๋ณด๊ฐ ์ ์ฅ/์กฐํ ๊ณผ์ ์์ ์ฌ๋ฐ๋ฅด๊ฒ ์ฒ๋ฆฌ๋์ง ์์
4. ์บ๋ฆฐ๋ ๊ตฌํ์์์ ์๊ฐ๋ ์ฒ๋ฆฌ
// ๊ธฐ์กด ์บ๋ฆฐ๋ ๊ตฌํ์ ๋ฌธ์ ์
const startOfMonth = currentMonth
.startOf("month")
.toISOString(); // UTC ๋ณํ์ผ๋ก ์ธํ ๋ ์ง ๋ถ์ผ์น
// DB ๋ฐ์ดํฐ ๋งคํ ์ ์๊ฐ๋ ๊ณ ๋ ค ๋ถ์ฌ
challengeData.forEach((item) => {
const date = item.created_at.split("T")[0]; // ๋จ์ ๋ฌธ์์ด ๋ถ๋ฆฌ๋ก ์ธํ ์ค๋ฅ
});
โ ํด๊ฒฐ ๊ณผ์
1. ์ ๊ทผ ๋ฐฉ์ ๋จ์ํ
๋ณต์กํ ์๊ฐ ๋ฒ์ ์ฟผ๋ฆฌ ๋์ ๊ฐ์ฅ ์ต๊ทผ ์ฑ๋ฆฐ์ง์ ๋ ์ง๋ง ๋น๊ตํ๋ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝํ์ต๋๋ค
const { data: latestChallenge } = await supabase
.from("challenges")
.select("created_at")
.eq("user_id", params.userId)
.order("created_at", { ascending: false })
.limit(1)
.single();
2. ๋ช ํํ ์๊ฐ๋ ์ฒ๋ฆฌ
dayjs์ timezone ํ๋ฌ๊ทธ์ธ์ ํ์ฉํ์ฌ ๋ช ์์ ์ธ ์๊ฐ๋ ์ฒ๋ฆฌ
const lastChallengeDate = dayjs(latestChallenge.created_at)
.tz("Asia/Seoul")
.format("YYYY-MM-DD");
const todayDate = dayjs()
.tz("Asia/Seoul")
.format("YYYY-MM-DD");
3. ๋ ์ง ๋น๊ต ๋ก์ง ๋จ์ํ
๋ ์ง ๋ฌธ์์ด ๋น๊ต๋ก ๋จ์ํํ์ฌ ์๊ฐ๋ ๊ด๋ จ ๋ณต์ก์ฑ ์ ๊ฑฐ
if (lastChallengeDate === todayDate) {
throw new Error("์ด๋ฏธ ์ค๋์ ์ฑ๋ฆฐ์ง๋ฅผ ์ ์ถํ์
จ์ต๋๋ค.");
}
4. ์บ๋ฆฐ๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๊ฐ์
// ๊ฐ์ ๋ ์บ๋ฆฐ๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
const fetchMonthlyData = async () => {
if (!user.id) return;
try {
const startOfMonth = currentMonth
.tz("Asia/Seoul")
.startOf("month")
.format("YYYY-MM-DD");
const endOfMonth = currentMonth
.tz("Asia/Seoul")
.endOf("month")
.format("YYYY-MM-DD");
const challengeData = await calendarApi.getByDateRange(
startOfMonth,
endOfMonth,
user.id
);
const dataByDate: MonthlyData = {};
challengeData.forEach((item) => {
const date = dayjs(item.created_at)
.tz("Asia/Seoul")
.format("YYYY-MM-DD");
dataByDate[date] = {
co2: item.co2 || 0,
point: item.point || 0,
challenge_count: 1
};
});
// ์๊ฐ ํต๊ณ ๊ณ์ฐ
const monthlyStats = {
totalCo2: challengeData.reduce((sum, item) => sum + (item.co2 || 0), 0),
totalPoints: challengeData.reduce((sum, item) => sum + (item.point || 0), 0),
totalChallenges: challengeData.length
};
setMonthlyData(dataByDate);
setMonthlyStats(monthlyStats);
} catch (error) {
console.error("์บ๋ฆฐ๋ ๋ฐ์ดํฐ ์์ฒญ ์คํจ:", error);
}
};
ํด๊ฒฐ๋ด์ฉ ํน์ง
- ๋จ์์ฑ
- ๋ณต์กํ ์๊ฐ ๋ฒ์ ์ฟผ๋ฆฌ ์ ๊ฑฐ
- ๋ช ํํ ๋ ์ง ๋น๊ต ๋ก์ง
- ์ ์ง๋ณด์๊ฐ ์ฉ์ดํ ์ฝ๋ ๊ตฌ์กฐ
- ์ ๋ขฐ์ฑ
- ์๊ฐ๋ ์ฒ๋ฆฌ์ ์ผ๊ด์ฑ ํ๋ณด
- ๊ฒฝ๊ณ ์ผ์ด์ค์์๋ ์์ ์ ์ธ ๋์
- ์์ธก ๊ฐ๋ฅํ ๋์ ๋ฐฉ์
- ์ฑ๋ฅ
- ์ต์ํ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ
- ๋ถํ์ํ ๋ฐ์ดํฐ ๋ณํ ์ต์ํ
- ๋จ์ํ ๋ฌธ์์ด ๋น๊ต๋ก ์ฒ๋ฆฌ ์๋ ํฅ์
ํ์ต๋ ์
- ์๊ฐ๋ ์ฒ๋ฆฌ
- ํญ์ ๋ช ์์ ์ธ ์๊ฐ๋ ์ง์
- ์ผ๊ด๋ ์๊ฐ๋ ์ฒ๋ฆฌ ๋ฐฉ์ ์ฌ์ฉ
- ์๊ฐ๋ ๋ณํ์ ์ต๋ํ ๋ฆ๊ฒ ์ํ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ
- ๋ณต์กํ ์๊ฐ ๊ด๋ จ ์ฟผ๋ฆฌ๋ ๊ฐ๋ฅํ ํผํ๊ธฐ
- ๋ช ํํ ์ธ๋ฑ์ค ํ์ฉ์ด ๊ฐ๋ฅํ ์ฟผ๋ฆฌ ์ค๊ณ
- ์ต์ํ์ ํ์ํ ๋ฐ์ดํฐ๋ง ์กฐํ
- ์๋ฌ ์ฒ๋ฆฌ
- ๊ตฌ์ฒด์ ์ธ ์๋ฌ ๋ฉ์์ง ์ ๊ณต
- ์์ธ ์ํฉ์ ๋ํ ๋ช ํํ ์ฒ๋ฆฌ
- ๋ก๊น ์ ํตํ ๋ฌธ์ ์ถ์ ์ฉ์ด์ฑ ํ๋ณด
์ถ๊ฐ ๊ณ ๋ ค์ฌํญ
- ํ์ฅ์ฑ
- ๋ค๋ฅธ ์๊ฐ๋ ์ง์ ํ์์ ์ฝ๊ฒ ์์ ๊ฐ๋ฅ
- ๋น์ฆ๋์ค ๋ก์ง ๋ณ๊ฒฝ์ ์ ์ฐํ๊ฒ ๋์ ๊ฐ๋ฅ
- ํ ์คํธ ์ฉ์ด์ฑ ํ๋ณด
- ๋ชจ๋ํฐ๋ง
- ๋ ์ง ๊ด๋ จ ์ค๋ฅ ๋ชจ๋ํฐ๋ง ์ฒด๊ณ ๊ตฌ์ถ
- ์ฌ์ฉ์ ํผ๋๋ฐฑ ์์ง ๋ฐ ๋ถ์
- ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง ํฌ์ธํธ ์ค์
๐ ์ฐธ๊ณ ์๋ฃ
- PostgreSQL Timestamptz: https://www.postgresql.org/docs/current/datatype-datetime.html
- Supabase Date Handling: https://supabase.com/docs/guides/database/timeouts
๊ฒฐ๋ก
๋ณต์กํ ์๊ฐ ์ฒ๋ฆฌ ๋ก์ง์ ๋จ์ํํ๊ณ ๋ช ํํ ์๊ฐ๋ ์ฒ๋ฆฌ๋ฅผ ํตํด ์์ ์ ์ธ ์ฑ๋ฆฐ์ง ์ ์ถ ์์คํ ์ ๊ตฌ์ถํ์ต๋๋ค
์ฝ๋์ ๋ณต์ก์ฑ์ ์ค์ด๊ณ ์ ์ง๋ณด์์ฑ์ ํฅ์์ํค๋ ๋์์ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์ ใ ์ผฏ์ต๋๋ค
์ด๋ฌํ ๊ฒฝํ์ ํตํด ์๊ฐ/๋ ์ง ๊ด๋ จ ๊ธฐ๋ฅ ๊ตฌํ ์ ์ด๊ธฐ๋ถํฐ ์ ์คํ ์ค๊ณ์
๋ช ํํ ์ฒ๋ฆฌ ๋ฐฉ์ ์ ํ์ ์ค์์ฑ์ ํ์ธํ ์ ์์์ต๋๋ค!!!