Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: paramsに日付に対するバリデーションを設定 #35

Merged
merged 5 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"@vanilla-extract/css": "^1.15.5",
"@vanilla-extract/recipes": "^0.5.5",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"date-fns": "^3.6.0",
"date-fns-tz": "^3.1.3",
"lucide-react": "^0.445.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down
6 changes: 4 additions & 2 deletions src/features/weather/components/weather_overview/error.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { FallbackProps } from "react-error-boundary"

import { Alert } from "@/components/ui/Alert"

export const Error = () => {
export const Error = (props: FallbackProps) => {
return (
<Alert>
<pre>Failed to fetch data</pre>
<pre>{props.error.message}</pre>
</Alert>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ import * as WeatherInfo from "@/features/weather/components/weather_info"
import * as WeatherInfoDetail from "@/features/weather/components/weather_info_detail"
import { Day } from "@/features/weather/const/range"
import { useQueryWeatherForecast, useWeatherParams } from "@/features/weather/hooks"
import { transformWeatherInfoForSpecificDay, validateDayRange } from "@/features/weather/utils"
import {
transformWeatherInfoForSpecificDay,
validateDateRange,
validateDayRange,
} from "@/features/weather/utils"
import { convertToTokyoTime } from "@/utils"

import { Presenter, PresenterProps } from "./presenter"

export const Container = () => {
const { location, date } = useWeatherParams({})
validateDateRange(convertToTokyoTime(new Date(date)), convertToTokyoTime(new Date()), Day)
const dateRange = validateDayRange(Day + 1)
const { data } = useQueryWeatherForecast(location, dateRange)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { FallbackProps } from "react-error-boundary"

import { Alert } from "@/components/ui/Alert"

export const Error = () => {
export const Error = (props: FallbackProps) => {
return (
<Alert>
<pre>Failed to fetch data</pre>
<pre>{props.error.message}</pre>
</Alert>
)
}
14 changes: 14 additions & 0 deletions src/features/weather/utils/validate/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DayRange } from "@/features/weather/services/forecast"
import { addDays, isWithinInterval } from "@/utils"

function isDaysRange(value: number): value is DayRange {
return value >= 1 && value <= 14
Expand All @@ -10,3 +11,16 @@ export function validateDayRange(dayRange: number): DayRange {
}
return dayRange
}

function isWithinDaysRange(target: Date, start: Date, range: number): boolean {
return isWithinInterval(target, {
start,
end: addDays(start, range),
})
}

export function validateDateRange(target: Date, start: Date, range: number): void {
if (!isWithinDaysRange(target, start, range)) {
throw new Error(`Date must be within ${range} days`)
}
}
49 changes: 48 additions & 1 deletion src/utils/date/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,59 @@
import { parseISO } from "date-fns"
import { describe, expect, test } from "vitest"

import { format } from "./"
import { addDays, format, isSameDay, isWithinInterval, toZonedTime } from "./"

describe("Date Functions", () => {
test("format: 正常にフォーマットされること", () => {
const date = parseISO("2024-09-30")
const result = format(date, "yyyy-MM-dd")
expect(result).toBe("2024-09-30")
})
test("isSameDay: 同日の比較が正常に行われること", () => {
const date1 = parseISO("2024-09-30")
const date2 = parseISO("2024-09-30")
const result = isSameDay(date1, date2)
expect(result).toBe(true)
})

test("isSameDay: 同日の比較が正常に行われること(異常系)", () => {
const date1 = parseISO("2024-09-30")
const date2 = parseISO("2024-09-31")
const result = isSameDay(date1, date2)
expect(result).toBe(false)
})

test("isWithinInterval: 日付が区間内にあるか正常に判定されること", () => {
const date = parseISO("2024-09-30")
const interval = {
start: parseISO("2024-09-01"),
end: parseISO("2024-09-30"),
}
const result = isWithinInterval(date, interval)
expect(result).toBe(true)
})

test("isWithinInterval: 日付が区間内にあるか正常に判定されること(異常系)", () => {
const date = parseISO("2024-08-31")
const interval = {
start: parseISO("2024-09-01"),
end: parseISO("2024-09-30"),
}
const result = isWithinInterval(date, interval)
expect(result).toBe(false)
})

test("toZonedTime: 指定のタイムゾーンに正常に変換されていること", () => {
const date = parseISO("2024-09-25T00:00:00Z")
const timeZone = "Asia/Tokyo"
const zonedDate = toZonedTime(date, timeZone)
expect(zonedDate.getHours()).toBe(9) // 東京時間で9時
})

test("addDays: 指定日数が正常にプラスされること", () => {
const date = parseISO("2024-09-20")
const result = addDays(date, 5)
const expectedDate = parseISO("2024-09-25")
expect(result).toEqual(expectedDate)
})
})
29 changes: 28 additions & 1 deletion src/utils/date/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
import { format as dateFnsFormat } from "date-fns"
import {
addDays as dateFnsAddDays,
format as dateFnsFormat,
isSameDay as dateFnsIsSameDay,
isWithinInterval as dateFnsIsWithinInterval,
} from "date-fns"
import { toZonedTime as dateFnsTzToZonedTime } from "date-fns-tz"

export function format(...props: Parameters<typeof dateFnsFormat>) {
return dateFnsFormat(...props)
}

export function isSameDay(...props: Parameters<typeof dateFnsIsSameDay>) {
return dateFnsIsSameDay(...props)
}

export function isWithinInterval(...props: Parameters<typeof dateFnsIsWithinInterval>) {
return dateFnsIsWithinInterval(...props)
}

export function toZonedTime(...props: Parameters<typeof dateFnsTzToZonedTime>) {
return dateFnsTzToZonedTime(...props)
}

export function addDays(...props: Parameters<typeof dateFnsAddDays>) {
return dateFnsAddDays(...props)
}

/** Tokyo時間に変換 */
export function convertToTokyoTime(date: Date) {
return toZonedTime(date, "Asia/Tokyo")
}