refactor progress
This commit is contained in:
59
packages/date_utils/src/DateComparison.tsx
Normal file
59
packages/date_utils/src/DateComparison.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {MS_PER_DAY, MS_PER_HOUR, MS_PER_MINUTE, MS_PER_SECOND} from '@fluxer/date_utils/src/DateConstants';
|
||||
import {parseDate} from '@fluxer/date_utils/src/DateParsing';
|
||||
import type {DateInput} from '@fluxer/date_utils/src/DateTypes';
|
||||
|
||||
export function isSameDay(date1: DateInput, date2?: DateInput): boolean {
|
||||
const d1 = parseDate(date1);
|
||||
const d2 = date2 != null ? parseDate(date2) : new Date();
|
||||
return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate();
|
||||
}
|
||||
|
||||
export function isYesterday(date: DateInput, now?: Date): boolean {
|
||||
const nowDate = now ?? new Date();
|
||||
const yesterday = new Date(nowDate);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
return isSameDay(date, yesterday);
|
||||
}
|
||||
|
||||
export function getDaysBetween(date1: DateInput, date2: DateInput): number {
|
||||
const d1 = parseDate(date1);
|
||||
const d2 = parseDate(date2);
|
||||
const d1Start = new Date(d1.getFullYear(), d1.getMonth(), d1.getDate());
|
||||
const d2Start = new Date(d2.getFullYear(), d2.getMonth(), d2.getDate());
|
||||
return Math.round((d1Start.getTime() - d2Start.getTime()) / MS_PER_DAY);
|
||||
}
|
||||
|
||||
export function getDaysDiff(date1: DateInput, date2: DateInput): number {
|
||||
return Math.floor((parseDate(date1).getTime() - parseDate(date2).getTime()) / MS_PER_DAY);
|
||||
}
|
||||
|
||||
export function getHoursDiff(date1: DateInput, date2: DateInput): number {
|
||||
return Math.floor((parseDate(date1).getTime() - parseDate(date2).getTime()) / MS_PER_HOUR);
|
||||
}
|
||||
|
||||
export function getMinutesDiff(date1: DateInput, date2: DateInput): number {
|
||||
return Math.floor((parseDate(date1).getTime() - parseDate(date2).getTime()) / MS_PER_MINUTE);
|
||||
}
|
||||
|
||||
export function getSecondsDiff(date1: DateInput, date2: DateInput): number {
|
||||
return Math.floor((parseDate(date1).getTime() - parseDate(date2).getTime()) / MS_PER_SECOND);
|
||||
}
|
||||
36
packages/date_utils/src/DateConstants.tsx
Normal file
36
packages/date_utils/src/DateConstants.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Locales} from '@fluxer/constants/src/Locales';
|
||||
|
||||
export const DEFAULT_LOCALE = Locales.EN_US;
|
||||
|
||||
export const MS_PER_SECOND = 1000;
|
||||
export const SECONDS_PER_MINUTE = 60;
|
||||
export const MINUTES_PER_HOUR = 60;
|
||||
export const HOURS_PER_DAY = 24;
|
||||
export const DAYS_PER_WEEK = 7;
|
||||
export const DAYS_PER_MONTH = 30;
|
||||
export const DAYS_PER_YEAR = 365;
|
||||
|
||||
export const MS_PER_MINUTE = SECONDS_PER_MINUTE * MS_PER_SECOND;
|
||||
export const MS_PER_HOUR = MINUTES_PER_HOUR * MS_PER_MINUTE;
|
||||
export const MS_PER_DAY = HOURS_PER_DAY * MS_PER_HOUR;
|
||||
export const SECONDS_PER_HOUR = MINUTES_PER_HOUR * SECONDS_PER_MINUTE;
|
||||
export const SECONDS_PER_DAY = HOURS_PER_DAY * SECONDS_PER_HOUR;
|
||||
76
packages/date_utils/src/DateDuration.tsx
Normal file
76
packages/date_utils/src/DateDuration.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
DAYS_PER_MONTH,
|
||||
DAYS_PER_WEEK,
|
||||
DAYS_PER_YEAR,
|
||||
HOURS_PER_DAY,
|
||||
MINUTES_PER_HOUR,
|
||||
SECONDS_PER_HOUR,
|
||||
SECONDS_PER_MINUTE,
|
||||
} from '@fluxer/date_utils/src/DateConstants';
|
||||
import {parseDate} from '@fluxer/date_utils/src/DateParsing';
|
||||
import type {DateInput} from '@fluxer/date_utils/src/DateTypes';
|
||||
|
||||
export function formatDuration(seconds: number): string {
|
||||
if (!Number.isFinite(seconds) || seconds < 0) {
|
||||
return '0:00';
|
||||
}
|
||||
|
||||
const hours = Math.floor(seconds / SECONDS_PER_HOUR);
|
||||
const minutes = Math.floor((seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE);
|
||||
const secs = Math.floor(seconds % SECONDS_PER_MINUTE);
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
export function formatShortRelativeTime(timestamp: DateInput, minUnit: '1m' | 'now' = 'now'): string {
|
||||
const date = parseDate(timestamp);
|
||||
const now = new Date();
|
||||
const diffMs = date.getTime() - now.getTime();
|
||||
const diffSeconds = Math.floor(diffMs / 1000);
|
||||
const diffMinutes = Math.floor(diffSeconds / SECONDS_PER_MINUTE);
|
||||
const diffHours = Math.floor(diffMinutes / MINUTES_PER_HOUR);
|
||||
const diffDays = Math.floor(diffHours / HOURS_PER_DAY);
|
||||
|
||||
if (Math.abs(diffSeconds) < SECONDS_PER_MINUTE) {
|
||||
return minUnit === '1m' ? '1m' : 'now';
|
||||
}
|
||||
if (Math.abs(diffMinutes) < MINUTES_PER_HOUR) {
|
||||
return `${Math.abs(diffMinutes)}m`;
|
||||
}
|
||||
if (Math.abs(diffHours) < HOURS_PER_DAY) {
|
||||
return `${Math.abs(diffHours)}h`;
|
||||
}
|
||||
if (Math.abs(diffDays) < DAYS_PER_WEEK) {
|
||||
return `${Math.abs(diffDays)}d`;
|
||||
}
|
||||
if (Math.abs(diffDays) < DAYS_PER_MONTH) {
|
||||
return `${Math.floor(Math.abs(diffDays) / DAYS_PER_WEEK)}w`;
|
||||
}
|
||||
if (Math.abs(diffDays) < DAYS_PER_YEAR) {
|
||||
return `${Math.floor(Math.abs(diffDays) / DAYS_PER_MONTH)}mo`;
|
||||
}
|
||||
return `${Math.floor(Math.abs(diffDays) / DAYS_PER_YEAR)}y`;
|
||||
}
|
||||
35
packages/date_utils/src/DateFormatterCache.tsx
Normal file
35
packages/date_utils/src/DateFormatterCache.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const formatterCache = new Map<string, Intl.DateTimeFormat>();
|
||||
|
||||
function buildCacheKey(locale: string, options: Intl.DateTimeFormatOptions): string {
|
||||
return `${locale}:${JSON.stringify(options)}`;
|
||||
}
|
||||
|
||||
export function getDateFormatter(locale: string, options: Intl.DateTimeFormatOptions = {}): Intl.DateTimeFormat {
|
||||
const key = buildCacheKey(locale, options);
|
||||
const cached = formatterCache.get(key);
|
||||
if (cached !== undefined) {
|
||||
return cached;
|
||||
}
|
||||
const formatter = new Intl.DateTimeFormat(locale, options);
|
||||
formatterCache.set(key, formatter);
|
||||
return formatter;
|
||||
}
|
||||
215
packages/date_utils/src/DateFormatting.tsx
Normal file
215
packages/date_utils/src/DateFormatting.tsx
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {isSameDay} from '@fluxer/date_utils/src/DateComparison';
|
||||
import {DEFAULT_LOCALE} from '@fluxer/date_utils/src/DateConstants';
|
||||
import {getDateFormatter} from '@fluxer/date_utils/src/DateFormatterCache';
|
||||
import {localeUses12Hour} from '@fluxer/date_utils/src/DateHourCycle';
|
||||
import {parseDate} from '@fluxer/date_utils/src/DateParsing';
|
||||
import type {DateInput} from '@fluxer/date_utils/src/DateTypes';
|
||||
|
||||
function resolveHour12(locale: string, hour12?: boolean): boolean {
|
||||
return hour12 ?? localeUses12Hour(locale);
|
||||
}
|
||||
|
||||
export function formatDate(
|
||||
date: DateInput,
|
||||
options: Intl.DateTimeFormatOptions = {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
},
|
||||
locale: string = DEFAULT_LOCALE,
|
||||
): string {
|
||||
const dateObj = parseDate(date);
|
||||
if (Number.isNaN(dateObj.getTime())) {
|
||||
return String(date);
|
||||
}
|
||||
return getDateFormatter(locale, options).format(dateObj);
|
||||
}
|
||||
|
||||
export function formatTimestamp(
|
||||
timestamp: string,
|
||||
locale: string = DEFAULT_LOCALE,
|
||||
options: Intl.DateTimeFormatOptions = {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
},
|
||||
): string {
|
||||
const date = parseDate(timestamp);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return timestamp;
|
||||
}
|
||||
return getDateFormatter(locale, options).format(date);
|
||||
}
|
||||
|
||||
export function getFormattedDateTime(timestamp: DateInput, locale: string = DEFAULT_LOCALE, hour12?: boolean): string {
|
||||
const date = parseDate(timestamp);
|
||||
return getDateFormatter(locale, {
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: resolveHour12(locale, hour12),
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
export function getFormattedDateTimeInZone(
|
||||
isoString: string,
|
||||
timezone: string,
|
||||
locale: string = DEFAULT_LOCALE,
|
||||
hour12?: boolean,
|
||||
): string {
|
||||
try {
|
||||
const date = new Date(isoString);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return isoString;
|
||||
}
|
||||
return getDateFormatter(locale, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: resolveHour12(locale, hour12),
|
||||
timeZone: timezone,
|
||||
}).format(date);
|
||||
} catch {
|
||||
return isoString;
|
||||
}
|
||||
}
|
||||
|
||||
export function getFormattedShortDate(timestamp: DateInput, locale: string = DEFAULT_LOCALE): string {
|
||||
return getDateFormatter(locale, {month: 'short', day: 'numeric', year: 'numeric'}).format(parseDate(timestamp));
|
||||
}
|
||||
|
||||
export function getFormattedLongDate(timestamp: DateInput, locale: string = DEFAULT_LOCALE): string {
|
||||
return getDateFormatter(locale, {month: 'long', day: 'numeric', year: 'numeric'}).format(parseDate(timestamp));
|
||||
}
|
||||
|
||||
export function getFormattedTime(timestamp: DateInput, locale: string = DEFAULT_LOCALE, hour12?: boolean): string {
|
||||
return getDateFormatter(locale, {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: resolveHour12(locale, hour12),
|
||||
}).format(parseDate(timestamp));
|
||||
}
|
||||
|
||||
export function getFormattedCompactDateTime(
|
||||
timestamp: DateInput,
|
||||
locale: string = DEFAULT_LOCALE,
|
||||
hour12?: boolean,
|
||||
): string {
|
||||
const date = parseDate(timestamp);
|
||||
const datePart = getDateFormatter('en-US', {month: 'numeric', day: 'numeric', year: '2-digit'}).format(date);
|
||||
const timePart = getDateFormatter(locale, {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: resolveHour12(locale, hour12),
|
||||
}).format(date);
|
||||
return `${datePart}, ${timePart}`;
|
||||
}
|
||||
|
||||
export function getFormattedFullDate(timestamp: DateInput, locale: string = DEFAULT_LOCALE): string {
|
||||
return getDateFormatter(locale, {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
}).format(parseDate(timestamp));
|
||||
}
|
||||
|
||||
export function getFormattedDateTimeWithSeconds(
|
||||
timestamp: DateInput,
|
||||
locale: string = DEFAULT_LOCALE,
|
||||
hour12?: boolean,
|
||||
): string {
|
||||
const date = parseDate(timestamp);
|
||||
const use12Hour = resolveHour12(locale, hour12);
|
||||
const datePart = getDateFormatter(locale, {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
}).format(date);
|
||||
const timePart = getDateFormatter(locale, {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: use12Hour,
|
||||
}).format(date);
|
||||
return `${datePart} ${timePart}`;
|
||||
}
|
||||
|
||||
export function getRelativeDateString(
|
||||
timestamp: DateInput,
|
||||
locale: string = DEFAULT_LOCALE,
|
||||
hour12?: boolean,
|
||||
now?: Date,
|
||||
): string {
|
||||
const date = parseDate(timestamp);
|
||||
const nowDate = now ?? new Date();
|
||||
const use12Hour = resolveHour12(locale, hour12);
|
||||
|
||||
const timeString = getDateFormatter(locale, {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: use12Hour,
|
||||
}).format(date);
|
||||
|
||||
if (isSameDay(date, nowDate)) {
|
||||
return `Today at ${timeString}`;
|
||||
}
|
||||
const yesterday = new Date(nowDate);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
if (isSameDay(date, yesterday)) {
|
||||
return `Yesterday at ${timeString}`;
|
||||
}
|
||||
|
||||
return getDateFormatter(locale, {
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: use12Hour,
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
export function formatLastActive(date: DateInput, locale: string = DEFAULT_LOCALE): string {
|
||||
const dateObj = parseDate(date);
|
||||
if (Number.isNaN(dateObj.getTime())) {
|
||||
return String(date);
|
||||
}
|
||||
return getDateFormatter(locale, {dateStyle: 'medium', timeStyle: 'short'}).format(dateObj);
|
||||
}
|
||||
|
||||
export function formatScheduledMessage(date: DateInput, locale: string = DEFAULT_LOCALE, timeZone?: string): string {
|
||||
const dateObj = parseDate(date);
|
||||
if (Number.isNaN(dateObj.getTime())) {
|
||||
return String(date);
|
||||
}
|
||||
return getDateFormatter(locale, {dateStyle: 'medium', timeStyle: 'short', timeZone}).format(dateObj);
|
||||
}
|
||||
43
packages/date_utils/src/DateHourCycle.tsx
Normal file
43
packages/date_utils/src/DateHourCycle.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const TWELVE_HOUR_LOCALES = [
|
||||
'en-us',
|
||||
'en-ca',
|
||||
'en-au',
|
||||
'en-nz',
|
||||
'en-ph',
|
||||
'en-in',
|
||||
'en-pk',
|
||||
'en-bd',
|
||||
'en-za',
|
||||
'es-mx',
|
||||
'es-co',
|
||||
'ar',
|
||||
'hi',
|
||||
'bn',
|
||||
'ur',
|
||||
'fil',
|
||||
'tl',
|
||||
];
|
||||
|
||||
export function localeUses12Hour(locale: string): boolean {
|
||||
const lang = locale.toLowerCase();
|
||||
return TWELVE_HOUR_LOCALES.some((l) => lang.startsWith(l));
|
||||
}
|
||||
55
packages/date_utils/src/DateIntrospection.tsx
Normal file
55
packages/date_utils/src/DateIntrospection.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {getDateFormatter} from '@fluxer/date_utils/src/DateFormatterCache';
|
||||
import type {DateFieldType} from '@fluxer/date_utils/src/DateTypes';
|
||||
|
||||
export function getSystemTimeZone(): string {
|
||||
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
}
|
||||
|
||||
export function getDateFieldOrder(locale: string): Array<DateFieldType> {
|
||||
const formatter = getDateFormatter(locale, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
});
|
||||
|
||||
const parts = formatter.formatToParts(new Date(2000, 0, 1));
|
||||
const order: Array<DateFieldType> = [];
|
||||
|
||||
for (const part of parts) {
|
||||
if (part.type === 'month' && !order.includes('month')) {
|
||||
order.push('month');
|
||||
} else if (part.type === 'day' && !order.includes('day')) {
|
||||
order.push('day');
|
||||
} else if (part.type === 'year' && !order.includes('year')) {
|
||||
order.push('year');
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
export function getMonthNames(locale: string, format: 'long' | 'short' = 'long'): Array<string> {
|
||||
return Array.from({length: 12}, (_, index) => {
|
||||
const monthDate = new Date(2000, index, 1);
|
||||
return getDateFormatter(locale, {month: format}).format(monthDate);
|
||||
});
|
||||
}
|
||||
84
packages/date_utils/src/DateParsing.tsx
Normal file
84
packages/date_utils/src/DateParsing.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type {DateInput} from '@fluxer/date_utils/src/DateTypes';
|
||||
|
||||
export function parseDate(input: DateInput): Date {
|
||||
if (input instanceof Date) {
|
||||
if (Number.isNaN(input.getTime())) {
|
||||
return new Date();
|
||||
}
|
||||
return input;
|
||||
}
|
||||
if (typeof input === 'string') {
|
||||
const date = new Date(input);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return new Date();
|
||||
}
|
||||
return date;
|
||||
}
|
||||
if (input == null || Number.isNaN(input)) {
|
||||
return new Date();
|
||||
}
|
||||
return new Date(input);
|
||||
}
|
||||
|
||||
export function isValidDate(input: DateInput): boolean {
|
||||
if (input instanceof Date) {
|
||||
return !Number.isNaN(input.getTime());
|
||||
}
|
||||
if (typeof input === 'string') {
|
||||
return !Number.isNaN(new Date(input).getTime());
|
||||
}
|
||||
if (typeof input === 'number') {
|
||||
return !Number.isNaN(input) && Number.isFinite(input);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function extractDatePart(isoString: string): string {
|
||||
const parts = isoString.split('T');
|
||||
const datePart = parts[0];
|
||||
if (datePart) {
|
||||
return datePart;
|
||||
}
|
||||
return isoString;
|
||||
}
|
||||
|
||||
export function extractTimePart(isoString: string): string {
|
||||
const parts = isoString.split('T');
|
||||
if (parts.length !== 2) {
|
||||
return isoString;
|
||||
}
|
||||
|
||||
const timePart = parts[1];
|
||||
if (!timePart) {
|
||||
return isoString;
|
||||
}
|
||||
let timeClean = timePart.split('.')[0] ?? timePart;
|
||||
timeClean = timeClean.replace('Z', '');
|
||||
|
||||
const timeParts = timeClean.split(':');
|
||||
if (timeParts.length >= 2) {
|
||||
const [hour, minute] = timeParts;
|
||||
return `${hour}:${minute}`;
|
||||
}
|
||||
|
||||
return isoString;
|
||||
}
|
||||
79
packages/date_utils/src/DateTimestampStyle.tsx
Normal file
79
packages/date_utils/src/DateTimestampStyle.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {getDateFormatter} from '@fluxer/date_utils/src/DateFormatterCache';
|
||||
import {localeUses12Hour} from '@fluxer/date_utils/src/DateHourCycle';
|
||||
import {TimestampStyle} from '@fluxer/markdown_parser/src/types/Enums';
|
||||
|
||||
const TIMESTAMP_STYLE_OPTIONS: Record<string, Intl.DateTimeFormatOptions> = {
|
||||
[TimestampStyle.ShortTime]: {hour: 'numeric', minute: 'numeric'},
|
||||
[TimestampStyle.LongTime]: {hour: 'numeric', minute: 'numeric', second: 'numeric'},
|
||||
[TimestampStyle.ShortDate]: {year: 'numeric', month: 'numeric', day: 'numeric'},
|
||||
[TimestampStyle.LongDate]: {month: 'long', day: 'numeric', year: 'numeric'},
|
||||
[TimestampStyle.ShortDateTime]: {month: 'long', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric'},
|
||||
[TimestampStyle.LongDateTime]: {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
},
|
||||
[TimestampStyle.ShortDateShortTime]: {
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
},
|
||||
[TimestampStyle.ShortDateMediumTime]: {
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
},
|
||||
};
|
||||
|
||||
const DEFAULT_STYLE_OPTIONS: Intl.DateTimeFormatOptions = {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
};
|
||||
|
||||
const STYLES_WITHOUT_HOUR_CYCLE: ReadonlySet<TimestampStyle> = new Set([
|
||||
TimestampStyle.ShortDate,
|
||||
TimestampStyle.LongDate,
|
||||
]);
|
||||
|
||||
export function formatTimestampWithStyle(
|
||||
timestamp: number,
|
||||
style: TimestampStyle,
|
||||
locale: string,
|
||||
hour12?: boolean,
|
||||
): string {
|
||||
const date = new Date(timestamp * 1000);
|
||||
const baseOptions = TIMESTAMP_STYLE_OPTIONS[style] ?? DEFAULT_STYLE_OPTIONS;
|
||||
const needsHourCycle = !STYLES_WITHOUT_HOUR_CYCLE.has(style);
|
||||
const options = needsHourCycle ? {...baseOptions, hour12: hour12 ?? localeUses12Hour(locale)} : baseOptions;
|
||||
return getDateFormatter(locale, options).format(date);
|
||||
}
|
||||
28
packages/date_utils/src/DateTypes.tsx
Normal file
28
packages/date_utils/src/DateTypes.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export type DateInput = Date | number | string;
|
||||
|
||||
export interface DateFormatOptions {
|
||||
locale?: string;
|
||||
hour12?: boolean;
|
||||
timeZone?: string;
|
||||
}
|
||||
|
||||
export type DateFieldType = 'month' | 'day' | 'year';
|
||||
Reference in New Issue
Block a user