Skip to content

Commit 4f910af

Browse files
authored
Merge pull request #84 from cniajp/feature/device-adjust-time
時間同期機能を追加し、正確な時間取得を実装
2 parents f4694ce + fa5e2f8 commit 4f910af

File tree

4 files changed

+160
-4
lines changed

4 files changed

+160
-4
lines changed

functions/api/time.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export const onRequest = async () => {
2+
const now = new Date()
3+
4+
return new Response(
5+
JSON.stringify({
6+
timestamp: now.toISOString(),
7+
timezone: 'Asia/Tokyo',
8+
unix: Math.floor(now.getTime() / 1000),
9+
milliseconds: now.getTime(),
10+
}),
11+
{
12+
headers: {
13+
'Content-Type': 'application/json',
14+
'Access-Control-Allow-Origin': '*',
15+
'Access-Control-Allow-Methods': 'GET, OPTIONS',
16+
'Access-Control-Allow-Headers': 'Content-Type',
17+
},
18+
}
19+
)
20+
}

src/components/models/pageContext.tsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { now } from '@/utils/time'
1+
import {
2+
now,
3+
nowAccurate,
4+
hasSignificantDrift,
5+
startTimeSync,
6+
} from '@/utils/time'
27
import { Dayjs } from 'dayjs'
38
import {
49
PropsWithChildren,
@@ -14,6 +19,7 @@ type PageCtxType = {
1419
goNextPage: () => void
1520
setTotalPage: (totalPage: number) => void
1621
now: Dayjs
22+
hasTimeDrift: boolean
1723
}
1824

1925
export const PageCtx = createContext<PageCtxType>({
@@ -22,21 +28,35 @@ export const PageCtx = createContext<PageCtxType>({
2228
goNextPage: () => {},
2329
setTotalPage: () => {},
2430
now: now(),
31+
hasTimeDrift: false,
2532
})
2633

2734
export const PageCtxProvider = (props: PropsWithChildren) => {
2835
const [current, setCurrent] = useState<number>(0)
2936
const [totalPage, setTotalPage] = useState<number>(0)
3037
const [currentTime, setCurrentTime] = useState<Dayjs>(now())
38+
const [timeDrift, setTimeDrift] = useState<boolean>(false)
3139

3240
const goNextPage = useCallback(() => {
3341
setCurrent((current + 1) % totalPage)
3442
}, [current, setCurrent, totalPage])
3543

3644
useEffect(() => {
37-
const timer = setInterval(() => {
38-
setCurrentTime(now())
39-
}, 1000)
45+
// Start time synchronization process (5-second retries for 30 seconds)
46+
startTimeSync()
47+
48+
const updateTime = () => {
49+
const accurateTime = nowAccurate()
50+
setCurrentTime(accurateTime)
51+
setTimeDrift(hasSignificantDrift())
52+
}
53+
54+
// Initial time update
55+
updateTime()
56+
57+
// Regular updates every second (using cached/calculated time)
58+
const timer = setInterval(updateTime, 1000)
59+
4060
return () => {
4161
clearInterval(timer)
4262
}
@@ -48,6 +68,7 @@ export const PageCtxProvider = (props: PropsWithChildren) => {
4868
goNextPage,
4969
setTotalPage,
5070
now: currentTime,
71+
hasTimeDrift: timeDrift,
5172
}
5273

5374
return <PageCtx.Provider value={ctx}>{props.children}</PageCtx.Provider>

src/pages/api/time.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { NextRequest } from 'next/server'
2+
3+
export const runtime = 'edge'
4+
5+
export default function handler(_req: NextRequest) {
6+
const now = new Date()
7+
8+
return new Response(
9+
JSON.stringify({
10+
timestamp: now.toISOString(),
11+
timezone: 'Asia/Tokyo',
12+
unix: Math.floor(now.getTime() / 1000),
13+
milliseconds: now.getTime(),
14+
}),
15+
{
16+
status: 200,
17+
headers: {
18+
'Content-Type': 'application/json',
19+
},
20+
}
21+
)
22+
}

src/utils/time.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,95 @@ dayjs.extend(timezone)
66
dayjs.extend(utc)
77
dayjs.tz.setDefault('Asia/Tokyo')
88

9+
interface ServerTimeResponse {
10+
timestamp: string
11+
timezone: string
12+
unix: number
13+
milliseconds: number
14+
}
15+
16+
// Configuration constants - easy to modify
17+
const TIME_SYNC_CONFIG = {
18+
RETRY_INTERVAL: 5000, // 5 seconds between retries
19+
MAX_ATTEMPTS: 6, // Maximum retry attempts (30 seconds total)
20+
SIGNIFICANT_DRIFT_THRESHOLD: 30000, // 30 seconds
21+
} as const
22+
23+
let timeOffset: number = 0
24+
let isSynced: boolean = false
25+
let syncAttempts: number = 0
26+
let syncInterval: NodeJS.Timeout | null = null
27+
28+
async function attemptTimeSync(): Promise<boolean> {
29+
try {
30+
const response = await fetch('/api/time')
31+
if (!response.ok) {
32+
throw new Error(`HTTP ${response.status}`)
33+
}
34+
35+
const data: ServerTimeResponse = await response.json()
36+
const serverTime = dayjs(data.timestamp).tz('Asia/Tokyo')
37+
const localTime = dayjs().tz('Asia/Tokyo')
38+
39+
timeOffset = serverTime.diff(localTime)
40+
isSynced = true
41+
42+
console.log(`Time synced successfully. Offset: ${timeOffset}ms`)
43+
return true
44+
} catch (error) {
45+
console.warn(`Time sync attempt ${syncAttempts + 1} failed:`, error)
46+
return false
47+
}
48+
}
49+
50+
export function startTimeSync(): void {
51+
if (syncInterval) return // Already started
52+
53+
syncInterval = setInterval(async () => {
54+
syncAttempts++
55+
56+
const success = await attemptTimeSync()
57+
58+
if (success || syncAttempts >= TIME_SYNC_CONFIG.MAX_ATTEMPTS) {
59+
if (syncInterval) {
60+
clearInterval(syncInterval)
61+
syncInterval = null
62+
}
63+
64+
if (!success) {
65+
console.warn(
66+
`Time sync failed after ${(TIME_SYNC_CONFIG.MAX_ATTEMPTS * TIME_SYNC_CONFIG.RETRY_INTERVAL) / 1000} seconds, using local time`
67+
)
68+
}
69+
}
70+
}, TIME_SYNC_CONFIG.RETRY_INTERVAL)
71+
72+
// Also try immediately
73+
attemptTimeSync().then((success) => {
74+
if (success && syncInterval) {
75+
clearInterval(syncInterval)
76+
syncInterval = null
77+
}
78+
})
79+
}
80+
81+
export function getAccurateTime(): Dayjs {
82+
const localTime = dayjs().tz('Asia/Tokyo')
83+
return isSynced ? localTime.add(timeOffset, 'millisecond') : localTime
84+
}
85+
86+
export function getTimeDrift(): number {
87+
return timeOffset
88+
}
89+
90+
export function hasSignificantDrift(): boolean {
91+
return Math.abs(timeOffset) > TIME_SYNC_CONFIG.SIGNIFICANT_DRIFT_THRESHOLD
92+
}
93+
94+
export function isTimeSynced(): boolean {
95+
return isSynced
96+
}
97+
998
export function getTimeStr(time: string): string {
1099
return dayjs(time).tz().format('HH:mm')
11100
}
@@ -17,3 +106,7 @@ export function getTime(time: string) {
17106
export function now(): Dayjs {
18107
return dayjs().tz()
19108
}
109+
110+
export function nowAccurate(): Dayjs {
111+
return getAccurateTime()
112+
}

0 commit comments

Comments
 (0)