Skip to content

Commit

Permalink
feat(web): Improve time point input with ISO8601 (#1292)
Browse files Browse the repository at this point in the history
Co-authored-by: tcsola <ztonci@gmail.com>
Co-authored-by: lby <icesunex@hotmail.com>
  • Loading branch information
3 people authored Nov 29, 2024
1 parent 43cff21 commit f754349
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 6 deletions.
7 changes: 5 additions & 2 deletions web/src/beta/ui/fields/TimePointField/EditPanel/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {
type ParsedDateTime,
getLocalTimezoneOffset,
isValidDateTimeFormat,
isValidTimezone,
parseDateTime,
TimeZoneOffset
} from "@reearth/beta/utils/time";
import { useCallback, useEffect, useMemo, useState } from "react";
Expand Down Expand Up @@ -42,8 +44,9 @@ export default ({ value, onChange, onClose }: Props) => {

useEffect(() => {
if (value && isValidDateTimeFormat(value)) {
const [parsedDate, timeWithOffset] = value.split("T");
const [parsedTime, timezoneOffset] = timeWithOffset.split(/[-+]/);
//Since isValidDateTimeFormat already validates the input, it's safe to assert the type as ParsedDateTime.
const { parsedDate, timeWithOffset, parsedTime, timezoneOffset } =
parseDateTime(value) as ParsedDateTime;

setDate(parsedDate);
setTime(parsedTime);
Expand Down
5 changes: 4 additions & 1 deletion web/src/beta/ui/fields/TimePointField/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Button, Popup, TextInput } from "@reearth/beta/lib/reearth-ui";
import { isValidDateTimeFormat } from "@reearth/beta/utils/time";
import { useT } from "@reearth/services/i18n";
import { styled, useTheme } from "@reearth/services/theme";
import { FC, useCallback, useEffect, useState } from "react";
Expand Down Expand Up @@ -44,7 +45,9 @@ const TimePointField: FC<TimePointFieldProps> = ({
(timeString: string) => {
if (timeString === value) return;
// TODO: validate timeString
onChange?.(timeString);
if (timeString && isValidDateTimeFormat(timeString)) {
onChange?.(timeString);
}
},
[value, onChange]
);
Expand Down
77 changes: 75 additions & 2 deletions web/src/beta/utils/time.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, test, expect } from "vitest";
import { describe, test, expect, it } from "vitest";

import { formatRelativeTime } from "./time";
import { formatRelativeTime, parseDateTime } from "./time";

describe("formatRelativeTime", () => {
const now = new Date();
Expand Down Expand Up @@ -73,3 +73,76 @@ describe("formatRelativeTime", () => {
expect(formatRelativeTime(date, "ja")).toBe("2年前");
});
});

describe("parseDateTime", () => {
it.each([
[
"2024-01-01T12:00Z",
{
parsedDate: "2024-01-01",
timeWithOffset: "12:00Z",
parsedTime: "12:00",
timezoneOffset: "00:00"
}
],
[
"2024-01-01T12:00:00Z",
{
parsedDate: "2024-01-01",
timeWithOffset: "12:00:00Z",
parsedTime: "12:00:00",
timezoneOffset: "00:00"
}
],
[
"2024-01-01T12:00:00.123Z",
{
parsedDate: "2024-01-01",
timeWithOffset: "12:00:00.123Z",
parsedTime: "12:00:00.123",
timezoneOffset: "00:00"
}
],
[
"2024-01-01T12:00+09:00",
{
parsedDate: "2024-01-01",
timeWithOffset: "12:00+09:00",
parsedTime: "12:00",
timezoneOffset: "09:00"
}
],
[
"2024-01-01T12:00:00+09:00",
{
parsedDate: "2024-01-01",
timeWithOffset: "12:00:00+09:00",
parsedTime: "12:00:00",
timezoneOffset: "09:00"
}
],
[
"2024-01-01T12:00:00.123+09:00",
{
parsedDate: "2024-01-01",
timeWithOffset: "12:00:00.123+09:00",
parsedTime: "12:00:00.123",
timezoneOffset: "09:00"
}
]
])("should correctly parse %s", (input, expected) => {
const result = parseDateTime(input);
expect(result).toEqual(expected);
});

it.each([
"invalid",
"2024-01-01",
"2024-01-01T12",
"2024-01-01T12:00+9:00",
""
])("should return null for invalid datetime format %s", (input) => {
const result = parseDateTime(input);
expect(result).toBeNull();
});
});
34 changes: 33 additions & 1 deletion web/src/beta/utils/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,37 @@ export const getTimeZone = (time: string): TimeZoneOffset | undefined => {
};

export const isValidDateTimeFormat = (time: string): boolean => {
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}([-+]\d{2}:\d{2})$/.test(time);
return /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?)(Z|([+-]\d{2}:\d{2}))?$/.test(
time
);
};

export type ParsedDateTime = {
parsedDate: string;
timeWithOffset: string;
parsedTime: string;
timezoneOffset: string;
};

export const parseDateTime = (value: string): ParsedDateTime | null => {
const match = value.match(
/^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?)(Z|([+-]\d{2}:\d{2}))?$/
);

if (!match) {
return null;
}

const parsedDate = match[1];
const timeWithOffset = match[2] + (match[3] || "");
const parsedTime = match[2];
const timezoneOffset =
match[3] === "Z" ? "00:00" : match[3]?.replace("+", "") || "00:00";

return {
parsedDate,
timeWithOffset,
parsedTime,
timezoneOffset
};
};

0 comments on commit f754349

Please sign in to comment.