Skip to content

Commit

Permalink
data pipelines are nicer with pipes
Browse files Browse the repository at this point in the history
  • Loading branch information
kluvin committed Jan 21, 2025
1 parent 34bd386 commit 538a1af
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 106 deletions.
11 changes: 11 additions & 0 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 @@ -13,8 +13,8 @@
"@astrojs/tailwind": "^5.1.4",
"@radix-ui/react-slot": "^1.1.1",
"@tsparticles/engine": "^3.7.1",
"@tsparticles/slim": "^3.7.1",
"@tsparticles/react": "^3.0.0",
"@tsparticles/slim": "^3.7.1",
"@types/react": "^19.0.7",
"@types/react-dom": "^19.0.3",
"astro": "^5.1.7",
Expand All @@ -23,6 +23,7 @@
"lucide-react": "^0.473.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"remeda": "^2.19.2",
"tailwind-merge": "^2.6.0",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
Expand Down
63 changes: 63 additions & 0 deletions src/lib/date-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { pipe } from 'remeda'

export const monthNames = [
'Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni',
'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'
] as const;

type MonthMap = Record<string, number>;

export const NORWEGIAN_MONTHS: MonthMap = {
'jan': 0, 'januar': 0,
'feb': 1, 'februar': 1,
'mar': 2, 'mars': 2,
'apr': 3, 'april': 3,
'mai': 4,
'jun': 5, 'juni': 5,
'jul': 6, 'juli': 6,
'aug': 7, 'august': 7,
'sep': 8, 'september': 8,
'okt': 9, 'oktober': 9,
'nov': 10, 'november': 10,
'des': 11, 'desember': 11
} as const;

const normalizeString = (str: string): string =>
str?.toLowerCase().trim() ?? '';

const parseDate = (str: string): Date => {
if (NORWEGIAN_MONTHS[str] !== undefined) {
return new Date(2024, NORWEGIAN_MONTHS[str], 1)
}

const [day, monthPart] = str.split('.')
const month = monthPart?.trim()

return month && month in NORWEGIAN_MONTHS
? new Date(2024, NORWEGIAN_MONTHS[month], parseInt(day))
: new Date(0)
}

const createDateInfo = (date: Date, originalStr: string) => ({
day: date.getDate(),
month: monthNames[date.getMonth()],
originalStr
})

const formatDate = ({ day, month, originalStr }: { day: number; month: string; originalStr: string }): string =>
day === 1 && !/\d/.test(originalStr)
? month
: `${day}. ${month}`

export const parseNorwegianDate = (dateStr: string): Date =>
pipe(dateStr, normalizeString, parseDate)

export const formatNorwegianDate = (dateStr: string): string =>
dateStr === 'Udatert'
? dateStr
: pipe(
dateStr,
parseNorwegianDate,
(date: Date) => createDateInfo(date, dateStr),
formatDate
)
134 changes: 29 additions & 105 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,121 +4,45 @@ import Layout from '../layouts/Layout.astro';
import '@/styles/globals.css'
import { EventCard } from './EventCard'
import events from '@/assets/events.json'
import { pipe } from 'remeda'
import { parseNorwegianDate, formatNorwegianDate } from '@/lib/date-utils'
const monthNames = [
'Januar',
'Februar',
'Mars',
'April',
'Mai',
'Juni',
'Juli',
'August',
'September',
'Oktober',
'November',
'Desember'
] as const;
type MonthMap = Record<string, number>;
/**
* Be hereby warned, a lot of data cleaning is done here that should not be done here
* We're simply reading the input data, filtering, then grouping by *cleaned* dates
* then we write to loops of info, the dates, and for the inner loop we write events.
* **/
const NORWEGIAN_MONTHS: MonthMap = {
'jan': 0, 'januar': 0,
'feb': 1, 'februar': 1,
'mar': 2, 'mars': 2,
'apr': 3, 'april': 3,
'mai': 4,
'jun': 5, 'juni': 5,
'jul': 6, 'juli': 6,
'aug': 7, 'august': 7,
'sep': 8, 'september': 8,
'okt': 9, 'oktober': 9,
'nov': 10, 'november': 10,
'des': 11, 'desember': 11
} as const;
const cleanDateString = (dateStr: string): string => {
return dateStr.toLowerCase().trim();
};
const isMonthOnly = (dateStr: string): boolean => {
return NORWEGIAN_MONTHS[dateStr] !== undefined;
};
const createDateFromMonthOnly = (month: string): Date => {
return new Date(2024, NORWEGIAN_MONTHS[month], 1);
};
const parseDateParts = (dateStr: string): { day: number; month: string } | null => {
const parts = dateStr.split('.');
const day = parseInt(parts[0]);
const month = parts[1]?.trim();
if (!month || !(month in NORWEGIAN_MONTHS)) {
return null;
}
return { day, month };
};
const parseNorwegianDate = (dateStr: string): Date => {
if (!dateStr) return new Date(0);
const cleanStr = cleanDateString(dateStr);
// Handle full month names like "Februar"
if (isMonthOnly(cleanStr)) {
return createDateFromMonthOnly(cleanStr);
}
type Event = typeof events[number];
type EventGroups = Record<string, Event[]>;
// Handle dates like "14.februar" or "14.februar "
const dateParts = parseDateParts(cleanStr);
if (!dateParts) {
return new Date(0);
}
const filterPublished = (events: Event[]): Event[] =>
events.filter(event => event["Skal ut"] !== "FALSE");
return new Date(2025, NORWEGIAN_MONTHS[dateParts.month], dateParts.day);
};
const groupByDate = (events: Event[]): EventGroups =>
Object.groupBy(events, (event): string => event.Dato || 'Udatert') as EventGroups;
const formatNorwegianDate = (dateStr: string): string => {
if (dateStr === 'Udatert') return dateStr;
const date = parseNorwegianDate(dateStr);
const day = date.getDate();
const month = monthNames[date.getMonth()];
// If it's just a month (day is 1 and original string doesn't contain a number)
if (day === 1 && !/\d/.test(dateStr)) {
return month;
}
return `${day}. ${month}`;
};
const sortByDate = (dates: string[]): string[] =>
dates.sort((a, b) => parseNorwegianDate(a).getTime() - parseNorwegianDate(b).getTime());
type Event = typeof events[number];
const publishedEvents = events.filter(event => event["Skal ut"] !== "FALSE");
const groupedEvents = Object.groupBy(publishedEvents, (event): string => event.Dato || 'Udatert') as Record<string, Event[]>;
const sortedDates = Object.keys(groupedEvents).sort((a, b) =>
parseNorwegianDate(a).getTime() - parseNorwegianDate(b).getTime()
const { groupedEvents, sortedDates } = pipe(
events,
filterPublished,
groupByDate,
(grouped: EventGroups) => ({
groupedEvents: grouped,
sortedDates: sortByDate(Object.keys(grouped))
})
);
---

<Layout>
{sortedDates.map((date) => (
<div class="mb-12">
<h2 class="text-2xl font-bold mb-6 text-center">{formatNorwegianDate(date)}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 place-items-center">
{groupedEvents[date].map((event) => (
<EventCard client:load event={event} />
))}
<div class="max-w-4xl mx-auto p-8">
{sortedDates.map((date) => (
<div class="mb-12">
<h2 class="text-2xl font-bold mb-6 text-center">{formatNorwegianDate(date)}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 place-items-center">
{groupedEvents[date].map((event) => (
<EventCard client:load event={event} />
))}
</div>
</div>
</div>
))}
))}
</div>
</Layout>


0 comments on commit 538a1af

Please sign in to comment.