{/* Small */}
- {formatDate(date, 'short')}
+ {formatDate(date, 'short', timezone, showPlaceholderContent)}
{/* Medium */}
- {formatDate(date, 'medium')}
+ {formatDate(date, 'medium', timezone,showPlaceholderContent)}
{/* Large */}
-
- {formatDate(date)}
+
+ {formatDate(date, undefined, timezone, showPlaceholderContent)}
);
diff --git a/src/components/cmdk/CommandKClient.tsx b/src/components/cmdk/CommandKClient.tsx
index e73ac731..bd598885 100644
--- a/src/components/cmdk/CommandKClient.tsx
+++ b/src/components/cmdk/CommandKClient.tsx
@@ -166,7 +166,7 @@ export default function CommandKClient({
items: photos.map(photo => ({
label: titleForPhoto(photo),
keywords: getKeywordsForPhoto(photo),
- annotation: ,
+ annotation: ,
accessory: ,
path: pathForPhoto({ photo }),
})),
diff --git a/src/photo/PhotoDate.tsx b/src/photo/PhotoDate.tsx
index a8c3efb4..96449f0f 100644
--- a/src/photo/PhotoDate.tsx
+++ b/src/photo/PhotoDate.tsx
@@ -1,15 +1,18 @@
import ResponsiveDate from '@/components/ResponsiveDate';
import { Photo } from '.';
import { useMemo } from 'react';
+import { Timezone } from '@/utility/timezone';
export default function PhotoDate({
photo,
className,
dateType = 'takenAt',
+ timezone,
}: {
photo: Photo
className?: string
dateType?: 'takenAt' | 'createdAt' | 'updatedAt'
+ timezone: Timezone
}) {
const date = useMemo(() => {
const date = new Date(dateType === 'takenAt'
@@ -41,6 +44,7 @@ export default function PhotoDate({
date,
className,
titleLabel: getTitleLabel(),
+ timezone,
}} />
);
}
diff --git a/src/photo/PhotoLarge.tsx b/src/photo/PhotoLarge.tsx
index 668298f4..32befc1b 100644
--- a/src/photo/PhotoLarge.tsx
+++ b/src/photo/PhotoLarge.tsx
@@ -247,6 +247,9 @@ export default function PhotoLarge({
// Prevent collision with admin button
!hasNonDateContent && isUserSignedIn && 'md:pr-7',
)}
+ // Created at is a naive datetime which
+ // does not require a timezone
+ timezone={null}
/>
{
setHasLoaded?.(true);
+ storeTimezoneCookie();
}, []);
return (
diff --git a/src/utility/cookie.ts b/src/utility/cookie.ts
new file mode 100644
index 00000000..c69c64de
--- /dev/null
+++ b/src/utility/cookie.ts
@@ -0,0 +1,21 @@
+export const storeCookie = (
+ name: string,
+ value: string,
+ path= '/',
+ maxAge = 63158400,
+) => {
+ document.cookie = `${name}=${value};Path=${path};Max-Age=${maxAge}`;
+};
+
+export const getCookie = (name: string) => {
+ const cookie: Record = {};
+ document.cookie.split(';').forEach(function(el) {
+ const split = el.split('=');
+ cookie[split[0].trim()] = split.slice(1).join('=');
+ });
+ return cookie[name];
+};
+
+export const deleteCookie = (name: string) => {
+ document.cookie = `${name}=;Max-Age=0`;
+};
diff --git a/src/utility/date.ts b/src/utility/date.ts
index fdbe6454..30662c76 100644
--- a/src/utility/date.ts
+++ b/src/utility/date.ts
@@ -1,25 +1,52 @@
-import { format, parseISO, parse } from 'date-fns';
+import { parseISO, parse, format } from 'date-fns';
+import { formatInTimeZone } from 'date-fns-tz';
+import { Timezone } from './timezone';
-const DATE_STRING_FORMAT_TINY = 'dd MMM yy';
-const DATE_STRING_FORMAT_SHORT = 'dd MMM yyyy';
-const DATE_STRING_FORMAT_MEDIUM = 'dd MMM yy h:mma';
-const DATE_STRING_FORMAT = 'dd MMM yyyy h:mma';
-const DATE_STRING_FORMAT_POSTGRES = 'yyyy-MM-dd HH:mm:ss';
+const DATE_STRING_FORMAT_TINY = 'dd MMM yy';
+const DATE_STRING_FORMAT_TINY_PLACEHOLDER = '00 000 00';
+
+const DATE_STRING_FORMAT_SHORT = 'dd MMM yyyy';
+const DATE_STRING_FORMAT_SHORT_PLACEHOLDER = '00 000 0000';
+
+const DATE_STRING_FORMAT_MEDIUM = 'dd MMM yy h:mma';
+const DATE_STRING_FORMAT_MEDIUM_PLACEHOLDER = '00 000 00 00:0000';
+
+const DATE_STRING_FORMAT_LONG = 'dd MMM yyyy h:mma';
+const DATE_STRING_FORMAT_LONG_PLACEHOLDER = '00 000 0000 00:0000';
+
+const DATE_STRING_FORMAT_POSTGRES = 'yyyy-MM-dd HH:mm:ss';
type AmbiguousTimestamp = number | string;
type Length = 'tiny' | 'short' | 'medium' | 'long';
-export const formatDate = (date: Date, length: Length = 'long') => {
+export const formatDate = (
+ date: Date,
+ length: Length = 'long',
+ timezone?: Timezone,
+ showPlaceholder?: boolean,
+) => {
switch (length) {
- case 'tiny':
- return format(date, DATE_STRING_FORMAT_TINY);
- case 'short':
- return format(date, DATE_STRING_FORMAT_SHORT);
- case 'medium':
- return format(date, DATE_STRING_FORMAT_MEDIUM);
- default:
- return format(date, DATE_STRING_FORMAT);
+ case 'tiny': return showPlaceholder
+ ? DATE_STRING_FORMAT_TINY_PLACEHOLDER
+ : timezone
+ ? formatInTimeZone(date, timezone, DATE_STRING_FORMAT_TINY)
+ : format(date, DATE_STRING_FORMAT_TINY);
+ case 'short': return showPlaceholder
+ ? DATE_STRING_FORMAT_SHORT_PLACEHOLDER
+ : timezone
+ ? formatInTimeZone(date, timezone, DATE_STRING_FORMAT_SHORT)
+ : format(date, DATE_STRING_FORMAT_SHORT);
+ case 'medium': return showPlaceholder
+ ? DATE_STRING_FORMAT_MEDIUM_PLACEHOLDER
+ : timezone
+ ? formatInTimeZone(date, timezone, DATE_STRING_FORMAT_MEDIUM)
+ : format(date, DATE_STRING_FORMAT_MEDIUM);
+ default: return showPlaceholder
+ ? DATE_STRING_FORMAT_LONG_PLACEHOLDER
+ : timezone
+ ? formatInTimeZone(date, timezone, DATE_STRING_FORMAT_LONG)
+ : format(date, DATE_STRING_FORMAT_LONG);
}
};
diff --git a/src/utility/timezone.ts b/src/utility/timezone.ts
new file mode 100644
index 00000000..ba2b689b
--- /dev/null
+++ b/src/utility/timezone.ts
@@ -0,0 +1,18 @@
+import { getCookie, storeCookie } from './cookie';
+
+// Timezone
+// string: timezone
+// undefined: timezone must be resolved on the client
+// null: timezone not required
+export type Timezone = string | undefined | null;
+
+export const TIMEZONE_COOKIE_NAME = 'timezone-client';
+
+export const storeTimezoneCookie = () =>
+ storeCookie(
+ TIMEZONE_COOKIE_NAME,
+ Intl.DateTimeFormat().resolvedOptions().timeZone,
+ );
+
+export const getTimezoneCookie = () =>
+ getCookie(TIMEZONE_COOKIE_NAME);