fix: parse Wikipedia 12h time format and sort upcoming matches with NULLS LAST
Wikipedia stores match times as "6:00 p.m." (1-digit hour) which didn't
match the \d{2}:\d{2} regex, producing NULL for those matches. Introduced
parseTime12h() to handle 1-2 digit hours + AM/PM and convert to 24h.
Also sort upcomingMatches by NULLS LAST so unscheduled games appear after
timed ones rather than first. Dropped "openfootball" data attribution.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -37,7 +37,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
<main className="pt-[60px] min-h-screen">{children}</main>
|
<main className="pt-[60px] min-h-screen">{children}</main>
|
||||||
<footer className="border-t mt-8" style={{ borderColor: 'rgba(34,197,94,0.08)' }}>
|
<footer className="border-t mt-8" style={{ borderColor: 'rgba(34,197,94,0.08)' }}>
|
||||||
<div className="max-w-[1200px] mx-auto px-7 py-6 flex flex-col sm:flex-row items-center justify-between gap-2 text-[11px] text-[#1a3a22]">
|
<div className="max-w-[1200px] mx-auto px-7 py-6 flex flex-col sm:flex-row items-center justify-between gap-2 text-[11px] text-[#1a3a22]">
|
||||||
<span>© {new Date().getFullYear()} World Cup Statistics. Data via openfootball.</span>
|
<span>© {new Date().getFullYear()} World Cup Statistics.</span>
|
||||||
<a href="https://dev.pivoine.art" target="_blank" rel="noopener noreferrer"
|
<a href="https://dev.pivoine.art" target="_blank" rel="noopener noreferrer"
|
||||||
className="text-[#2a5c35] hover:text-[#22c55e] transition-colors">
|
className="text-[#2a5c35] hover:text-[#22c55e] transition-colors">
|
||||||
dev.pivoine.art
|
dev.pivoine.art
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export const resolvers = {
|
|||||||
sql`${matches.scoreFtHome} IS NULL`,
|
sql`${matches.scoreFtHome} IS NULL`,
|
||||||
eq(matches.isQualiPlayoff, false),
|
eq(matches.isQualiPlayoff, false),
|
||||||
))
|
))
|
||||||
.orderBy(asc(matches.date), asc(matches.id))
|
.orderBy(asc(matches.date), sql`${matches.timeLocal} ASC NULLS LAST`, asc(matches.id))
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
return Promise.all(rows.map(hydrateMatch))
|
return Promise.all(rows.map(hydrateMatch))
|
||||||
} catch (e) { if (isMissingTable(e)) return []; throw e }
|
} catch (e) { if (isMissingTable(e)) return []; throw e }
|
||||||
|
|||||||
+12
-1
@@ -154,6 +154,17 @@ function parseGroundParts(ground: string): { name: string; city: string } {
|
|||||||
return { name: ground, city: '' }
|
return { name: ground, city: '' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseTime12h(text: string): string | undefined {
|
||||||
|
const m = text.match(/(\d{1,2}):(\d{2})\s*([ap]\.?m\.?)/i)
|
||||||
|
if (!m) return text.match(/(\d{2}:\d{2})/)?.[1]
|
||||||
|
let h = parseInt(m[1])
|
||||||
|
const min = m[2]
|
||||||
|
const isPm = m[3].toLowerCase().replace(/\./g, '').startsWith('p')
|
||||||
|
if (isPm && h !== 12) h += 12
|
||||||
|
else if (!isPm && h === 12) h = 0
|
||||||
|
return `${String(h).padStart(2, '0')}:${min}`
|
||||||
|
}
|
||||||
|
|
||||||
function parseBox($: CheerioAPI, $box: Cheerio<Element>, round: string, group: string | null): Match | null {
|
function parseBox($: CheerioAPI, $box: Cheerio<Element>, round: string, group: string | null): Match | null {
|
||||||
const team1 = extractTeam($, $box.find('.fhome'))
|
const team1 = extractTeam($, $box.find('.fhome'))
|
||||||
const team2 = extractTeam($, $box.find('.faway'))
|
const team2 = extractTeam($, $box.find('.faway'))
|
||||||
@@ -161,7 +172,7 @@ function parseBox($: CheerioAPI, $box: Cheerio<Element>, round: string, group: s
|
|||||||
|
|
||||||
const dateStr = $box.find('.bday, .dtstart').first().text().trim() || undefined
|
const dateStr = $box.find('.bday, .dtstart').first().text().trim() || undefined
|
||||||
const timeText = $box.find('.ftime').first().text().trim()
|
const timeText = $box.find('.ftime').first().text().trim()
|
||||||
const timeStr = timeText.match(/(\d{2}:\d{2})/)?.[1]
|
const timeStr = parseTime12h(timeText)
|
||||||
const scoreText = $box.find('.fscore').first().text().trim()
|
const scoreText = $box.find('.fscore').first().text().trim()
|
||||||
const hasAET = scoreText.toLowerCase().includes('a.e.t.')
|
const hasAET = scoreText.toLowerCase().includes('a.e.t.')
|
||||||
const scoreArr = parseScoreText(scoreText)
|
const scoreArr = parseScoreText(scoreText)
|
||||||
|
|||||||
Reference in New Issue
Block a user