Fixes timeZone() by no longer needing timeZone() (#646)

* Fixes timeZone() by no longer needing timeZone()

* Added simple testcase to demonstrate the behaviour of parseZone() vs dayjs()

* Fixed eslint errors
This commit is contained in:
Alex van Andel 2021-09-15 03:58:29 +01:00 committed by GitHub
parent eb25ef266a
commit d3fa6cec80
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 69 additions and 28 deletions

View file

@ -18,8 +18,7 @@ export const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps
<DialogPrimitive.Content
{...props}
className="min-w-[360px] fixed left-1/2 top-1/2 p-6 text-left bg-white rounded shadow-xl overflow-hidden -translate-x-1/2 -translate-y-1/2 sm:align-middle sm:w-full sm:max-w-lg"
ref={forwardedRef}
>
ref={forwardedRef}>
{children}
</DialogPrimitive.Content>
)

View file

@ -14,6 +14,7 @@ import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock";
import useTheme from "@lib/hooks/useTheme";
import AvatarGroup from "@components/ui/AvatarGroup";
import { parseZone } from "@lib/parseZone";
const BookingPage = (props: any): JSX.Element => {
const router = useRouter();
@ -183,9 +184,7 @@ const BookingPage = (props: any): JSX.Element => {
)}
<p className="text-green-500 mb-4">
<CalendarIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
{dayjs(date)
.tz(timeZone())
.format(timeFormat + ", dddd DD MMMM YYYY")}
{parseZone(date).format(timeFormat + ", dddd DD MMMM YYYY")}
</p>
<p className="dark:text-white text-gray-600 mb-8">{props.eventType.description}</p>
</div>

45
lib/parseZone.ts Normal file
View file

@ -0,0 +1,45 @@
import dayjs from "dayjs";
const ISO8601_OFFSET_FORMAT = /^(.*)([+-])(\d{2}):(\d{2})|(Z)$/;
// @see https://github.com/iamkun/dayjs/issues/651#issuecomment-763033265
// decorates dayjs in order to keep the utcOffset of the given date string
// ; natively dayjs auto-converts to local time & losing utcOffset info.
export function parseZone(
date?: dayjs.ConfigType,
format?: dayjs.OptionType,
locale?: string,
strict?: boolean
) {
if (typeof date !== "string") {
return dayjs(date, format, locale, strict);
}
const match = date.match(ISO8601_OFFSET_FORMAT);
if (match === null) {
return;
}
if (match[0] === "Z") {
return dayjs(
date,
{
utc: true,
...format,
},
locale,
strict
);
}
const [, dateTime, sign, tzHour, tzMinute] = match;
const uOffset: number = tzHour * 60 + parseInt(tzMinute, 10);
const offset = sign === "+" ? uOffset : -uOffset;
return dayjs(
dateTime,
{
$offset: offset,
...format,
} as dayjs.OptionType,
locale,
strict
);
}

View file

@ -1,7 +1,6 @@
import prisma from "@lib/prisma";
import { getIntegrationName, getIntegrationType } from "@lib/integrations";
import Shell from "@components/Shell";
import { useState } from "react";
import { useRouter } from "next/router";
import { useSession } from "next-auth/client";
import Loader from "@components/Loader";
@ -12,16 +11,10 @@ export default function Integration(props) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession();
const [showAPIKey, setShowAPIKey] = useState(false);
if (loading) {
return <Loader />;
}
function toggleShowAPIKey() {
setShowAPIKey(!showAPIKey);
}
async function deleteIntegrationHandler(event) {
event.preventDefault();

View file

@ -170,8 +170,7 @@ export default function Home({ integrations }: Props) {
<div className="w-2/12 text-right pt-2">
<button
onClick={() => integrationHandler(integration.type)}
className="font-medium text-neutral-900 hover:text-neutral-500"
>
className="font-medium text-neutral-900 hover:text-neutral-500">
Add
</button>
</div>
@ -276,8 +275,7 @@ export default function Home({ integrations }: Props) {
return (
<Dialog
open={isAddCalDavIntegrationDialogOpen}
onOpenChange={(isOpen) => setIsAddCalDavIntegrationDialogOpen(isOpen)}
>
onOpenChange={(isOpen) => setIsAddCalDavIntegrationDialogOpen(isOpen)}>
<DialogContent>
<DialogHeader
title="Connect to CalDav Server"
@ -299,16 +297,14 @@ export default function Home({ integrations }: Props) {
<Button
type="submit"
form={ADD_CALDAV_INTEGRATION_FORM_TITLE}
className="flex justify-center py-2 px-4 border border-transparent rounded-sm shadow-sm text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900"
>
className="flex justify-center py-2 px-4 border border-transparent rounded-sm shadow-sm text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
Save
</Button>
<DialogClose
onClick={() => {
setIsAddCalDavIntegrationDialogOpen(false);
}}
asChild
>
asChild>
<Button color="secondary">Cancel</Button>
</DialogClose>
</div>
@ -321,8 +317,7 @@ export default function Home({ integrations }: Props) {
return (
<Dialog
open={isAddAppleIntegrationDialogOpen}
onOpenChange={(isOpen) => setIsAddAppleIntegrationDialogOpen(isOpen)}
>
onOpenChange={(isOpen) => setIsAddAppleIntegrationDialogOpen(isOpen)}>
<DialogContent>
<DialogHeader
title="Connect to Apple Server"
@ -333,8 +328,7 @@ export default function Home({ integrations }: Props) {
className="text-indigo-400"
href="https://appleid.apple.com/account/manage"
target="_blank"
rel="noopener noreferrer"
>
rel="noopener noreferrer">
https://appleid.apple.com/account/manage
</a>
. Your credentials will be stored and encrypted.
@ -357,16 +351,14 @@ export default function Home({ integrations }: Props) {
<button
type="submit"
form={ADD_APPLE_INTEGRATION_FORM_TITLE}
className="flex justify-center py-2 px-4 border border-transparent rounded-sm shadow-sm text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900"
>
className="flex justify-center py-2 px-4 border border-transparent rounded-sm shadow-sm text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
Save
</button>
<DialogClose
onClick={() => {
setIsAddAppleIntegrationDialogOpen(false);
}}
asChild
>
asChild>
<Button color="secondary">Cancel</Button>
</DialogClose>
</div>

View file

@ -0,0 +1,13 @@
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import { parseZone } from "@lib/parseZone";
dayjs.extend(utc);
const EXPECTED_DATE_STRING = "2021-06-20T11:59:59+02:00";
it("has the right utcOffset regardless of the local timeZone", async () => {
expect(parseZone(EXPECTED_DATE_STRING).utcOffset()).toEqual(120);
expect(parseZone(EXPECTED_DATE_STRING).format()).toEqual(EXPECTED_DATE_STRING);
expect(dayjs(EXPECTED_DATE_STRING).format()).not.toEqual(EXPECTED_DATE_STRING);
});