katja's git: oeffisearch

fast and simple tripplanner

commit 9233fe93199b2a4c6a81029db07778ac92ecd881
parent 7750f9cb6df31717dfec5b0419911f9f514ce850
Author: Katja (ctucx) <git@ctu.cx>
Date: Wed, 16 Apr 2025 23:09:48 +0200

use CustomDate class for date/time formating
7 files changed, 82 insertions(+), 108 deletions(-)
M
src/app_functions.js
|
49
++++++++++++++++++++++++++++++++++++++++++++++---
M
src/formatters.js
|
3
---
M
src/helpers.js
|
84
+++++++++++++++++++++----------------------------------------------------------
M
src/journeyView.js
|
4
++--
M
src/journeysCanvas.js
|
10
+++++-----
M
src/searchView.js
|
31
+++----------------------------
M
src/templates.js
|
9
++++-----
diff --git a/src/app_functions.js b/src/app_functions.js
@@ -1,8 +1,51 @@
 import { db } from './dataStorage.js';
 import { settingsState } from './settings.js';
-import { generateSlug, loyaltyCardToString, loyaltyCardFromString } from './helpers.js';
 import { getHafasClient, client } from './hafasClient.js';
 import { trainsearchToHafas, hafasToTrainsearch } from './refresh_token/index.js';
+import { CustomDate } from './helpers.js';
+
+const loyaltyCards = {
+	NONE:              Symbol('no loyalty card'),
+	BAHNCARD:          Symbol('Bahncard'),
+	VORTEILSCARD:      Symbol('VorteilsCard'),
+	HALBTAXABO:        Symbol('HalbtaxAbo'),
+	VOORDEELURENABO:   Symbol('Voordeelurenabo'),
+	SHCARD:            Symbol('SH-Card'),
+	GENERALABONNEMENT: Symbol('General-Abonnement'),
+};
+
+const loyaltyCardsReverse = {
+	'Symbol(no loyalty card)':    'NONE',
+	'Symbol(Bahncard)':           'BAHNCARD',
+	'Symbol(VorteilsCard)':       'VORTEILSCARD',
+	'Symbol(HalbtaxAbo)':         'HALBTAXABO',
+	'Symbol(Voordeelurenabo)':    'VOORDEELURENABO',
+	'Symbol(SH-Card)':            'SHCARD',
+	'Symbol(General-Abonnement)': 'GENERALABONNEMENT',
+};
+
+export const loyaltyCardToString   = loyaltyCard =>
+	loyaltyCardsReverse[loyaltyCard.type.toString()] !== 'NONE' ?
+	`${loyaltyCardsReverse[loyaltyCard.type.toString()]}-${loyaltyCard.discount}-${loyaltyCard.class}`
+	: 'NONE';
+
+
+export const loyaltyCardFromString = string => {
+	const splitedString = string.split('-');
+	if (splitedString[0] === 'NONE') return { type: loyaltyCards[splitedString[0]] };
+	return { type: loyaltyCards[splitedString[0]], discount: splitedString[1], class: splitedString[2] };
+};
+
+export const generateSlug = () => {
+	const len = 8;
+	let result = '';
+	const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+
+	for (let i = 0; i < len; i++)
+		result += characters.charAt(Math.floor(Math.random() * characters.length));
+
+	return result;
+};
 
 const journeySettings = () => {
 	let params = {

@@ -27,12 +70,12 @@ export const processLeg = leg => {
 	const elements = [ 'plannedDeparture', 'plannedArrival', 'plannedWhen', 'departure', 'arrival', 'when' ];
 
 	elements.forEach(element => {
-		if (leg[element]) leg[element] = new Date(leg[element]);
+		if (leg[element]) leg[element] = new CustomDate(leg[element]);
 	});
 
 	if (leg.stopovers) leg.stopovers.forEach(stopover => {
 		elements.forEach(element => {
-			if (stopover[element]) stopover[element] = new Date(stopover[element]);
+			if (stopover[element]) stopover[element] = new CustomDate(stopover[element]);
 		});	
 	});
 };
diff --git a/src/formatters.js b/src/formatters.js
@@ -24,9 +24,6 @@ export const formatPoint = point => {
 	};
 };
 
-export const formatDate = date => `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}`;
-export const formatTime = date => `${padZeros(date.getHours())}:${padZeros(date.getMinutes())}`;
-
 export const formatDuration = duration => {
 	const mins = duration / 60000;
 	const h    = Math.floor(mins / 60);
diff --git a/src/helpers.js b/src/helpers.js
@@ -1,72 +1,32 @@
-const loyaltyCards = {
-	NONE:              Symbol('no loyalty card'),
-	BAHNCARD:          Symbol('Bahncard'),
-	VORTEILSCARD:      Symbol('VorteilsCard'),
-	HALBTAXABO:        Symbol('HalbtaxAbo'),
-	VOORDEELURENABO:   Symbol('Voordeelurenabo'),
-	SHCARD:            Symbol('SH-Card'),
-	GENERALABONNEMENT: Symbol('General-Abonnement'),
-};
-
-const loyaltyCardsReverse = {
-	'Symbol(no loyalty card)':    'NONE',
-	'Symbol(Bahncard)':           'BAHNCARD',
-	'Symbol(VorteilsCard)':       'VORTEILSCARD',
-	'Symbol(HalbtaxAbo)':         'HALBTAXABO',
-	'Symbol(Voordeelurenabo)':    'VOORDEELURENABO',
-	'Symbol(SH-Card)':            'SHCARD',
-	'Symbol(General-Abonnement)': 'GENERALABONNEMENT',
-};
-
 export const sleep         = delay   => new Promise((resolve) => setTimeout(resolve, delay));
 export const isEmptyObject = obj     => Object.keys(obj).length === 0;
 export const padZeros      = str     => (('00' + str).slice(-2));
-export const ElementById   = id      => document.getElementById(id);
-
-export const showElement   = element => element.classList.remove('hidden');
-export const hideElement   = element => element.classList.add('hidden');
-export const elementHidden = element => element.classList.contains('hidden');
-
-export const unflipElement = element => element.classList.remove('flipped');
-export const flipElement   = element => element.classList.add('flipped');
 
 export const setThemeColor        = color           => document.querySelector('meta[name="theme-color"]').setAttribute('content', color);
 export const queryBackgroundColor = (target, query) => window.getComputedStyle(target.querySelector(query)).getPropertyValue('background-color');
 
-export const isValidDate = date => {
-	const matches = /^(\d{4})[-\/](\d{2})[-\/](\d{2})$/.exec(date);
-	if (matches == null) {
-		return false;
-	}
-
-	const d = matches[3];
-	const m = matches[2]-1;
-	const y = matches[1];
-	const composedDate = new Date(y, m, d);
-	return composedDate.getDate() == d &&
-		   composedDate.getMonth() == m &&
-		   composedDate.getFullYear() == y;
-};
+export class CustomDate extends Date {
+	formatDate()        { return `${this.getDate()}.${this.getMonth() + 1}.${this.getFullYear()}`; }
+	formatISODate()     { return `${this.getFullYear()}-${padZeros(this.getMonth()+1)}-${padZeros(this.getDate())}`; }
+	formatISODateTime() { return `${this.formatISODate()}T${this.formatTime()}`; }
+	formatTime()        { return `${padZeros(this.getHours())}:${padZeros(this.getMinutes())}`; }
 
-export const loyaltyCardToString   = loyaltyCard =>
-	loyaltyCardsReverse[loyaltyCard.type.toString()] !== 'NONE' ?
-	`${loyaltyCardsReverse[loyaltyCard.type.toString()]}-${loyaltyCard.discount}-${loyaltyCard.class}`
-	: 'NONE';
-
-export const loyaltyCardFromString = string => {
-	const splitedString = string.split('-');
-	if (splitedString[0] === 'NONE') return { type: loyaltyCards[splitedString[0]] };
-	return { type: loyaltyCards[splitedString[0]], discount: splitedString[1], class: splitedString[2] };
-};
-
-
-export const generateSlug = () => {
-	const len = 8;
-	let result = '';
-	const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+	setDate(value) {
+		const splitValue = value.split('-');
+		if (splitValue.length !== 3) return false;
+		this.setFullYear(splitValue[0], splitValue[1]-1, splitValue[2]);
+	}
 
-	for (let i = 0; i < len; i++)
-		result += characters.charAt(Math.floor(Math.random() * characters.length));
+	setTime(value) {
+		const splitValue = value.split(':');
+		if (splitValue.length !== 2) return false;
+		this.setHours(splitValue[0], splitValue[1]);
+	}
 
-	return result;
-};
+	setDateTime(value) {
+		const splitValue = value.split('T');
+		if (splitValue.length !== 2) return false;
+		this.setDate(splitValue[0]);
+		this.setTime(splitValue[1]);
+	}
+}
diff --git a/src/journeyView.js b/src/journeyView.js
@@ -8,7 +8,7 @@ import { settings } from './settings.js';
 import { t } from './languages.js';
 import { getJourney, refreshJourney } from './app_functions.js';
 import { remarksModal, platformTemplate, stopTemplate, timeTemplate } from './templates.js';
-import { formatPoint, formatDate, formatDuration, formatPrice, formatTrainTypes, formatLineAdditionalName, formatLineDisplayName } from './formatters.js';
+import { formatPoint, formatDuration, formatPrice, formatTrainTypes, formatLineAdditionalName, formatLineDisplayName } from './formatters.js';
 import { cachedCoachSequence } from './coach-sequence/index.js';
 
 import { baseStyles, helperStyles, flexboxStyles, headerStyles, iconStyles, cardStyles, journeyViewStyles } from './styles.js';

@@ -85,7 +85,7 @@ class JourneyView extends LitOverlay {
 						<a class="icon-reload ${this.isUpdating ? 'spinning' : ''}" title="${t("reload")}" @click=${this.refreshJourney}></a>
 						${this.viewState !== null ? html`
 						<h3>${formatPoint(this.viewState.legs[0].origin)} → ${formatPoint(this.viewState.legs[this.viewState.legs.length - 1].destination)}</h3>
-						<p><b>${t('duration')}: ${formatDuration(this.viewState.duration)} | ${t('changes')}: ${this.viewState.changes-1} | ${t('date')}: ${formatDate(this.viewState.legs[0].plannedDeparture)}${this.settingsState.showPrices && this.viewState.price ? html` | ${t('price')}: <td><span>${formatPrice(this.viewState.price)}</span></td>` : nothing}</b></p>
+						<p><b>${t('duration')}: ${formatDuration(this.viewState.duration)} | ${t('changes')}: ${this.viewState.changes-1} | ${t('date')}: ${this.viewState.legs[0].plannedDeparture.formatDate()}${this.settingsState.showPrices && this.viewState.price ? html` | ${t('price')}: <td><span>${formatPrice(this.viewState.price)}</span></td>` : nothing}</b></p>
 						` : html`
 						<h3>... → ...</h3>
 						<p><b>${t('duration')}: ... | ${t('changes')}: ... | ${t('date')}: ...</b></p>
diff --git a/src/journeysCanvas.js b/src/journeysCanvas.js
@@ -1,8 +1,8 @@
 import { LitElement, html, css } from 'lit';
 import { LitOverlay } from './LitOverlay.js';
 
-import { sleep } from './helpers.js';
-import { formatTrainTypes, formatLineDisplayName, formatTime } from './formatters.js'
+import { CustomDate, sleep } from './helpers.js';
+import { formatTrainTypes, formatLineDisplayName } from './formatters.js'
 import { cachedCoachSequence, coachSequenceCache, coachSequenceCacheKey } from './coach-sequence/index.js';
 
 export class JourneysCanvas extends LitOverlay {

@@ -328,9 +328,9 @@ export class JourneysCanvas extends LitOverlay {
 		this.canvasContext.fillStyle = '#aaa';
 		while (time < this.canvasState.lastArrival) {
 			const y = (time - this.canvasState.firstDeparture) * this.canvasState.scaleFactor + 32;
-			this.canvasContext.fillText(formatTime(time), (window.innerWidth / this.canvasState.dpr) > 600 ? 30 : 10, y);
+			this.canvasContext.fillText(time.formatTime(), (window.innerWidth / this.canvasState.dpr) > 600 ? 30 : 10, y);
 			this.canvasContext.fillRect(0, y, this.canvasElement.width / this.canvasState.dpr, 1);
-			time = new Date(Number(time) + 3600000);//Math.floor(120/scaleFactor));
+			time = new CustomDate(Number(time) + 3600000);//Math.floor(120/scaleFactor));
 		}
 
 		this.canvasContext.fillStyle = '#fa5';

@@ -421,7 +421,7 @@ export class JourneysCanvas extends LitOverlay {
 				if (journey.legs.indexOf(leg) == journey.legs.length - 1) times.push([leg.departure || leg.plannedDeparture, y - 9.5]);
 				if (journey.legs.indexOf(leg) == 0) times.push([leg.arrival || leg.plannedArrival, y + duration + 7.5]);
 				times.forEach(([time, y]) => {
-					preRenderedText = this.getTextCache(formatTime(time), '#fff', 15);
+					preRenderedText = this.getTextCache(time.formatTime(), '#fff', 15);
 					this.canvasContext.scale(1 / this.canvasState.dpr, 1 / this.canvasState.dpr);
 					this.canvasContext.drawImage(preRenderedText, Math.ceil(this.canvasState.dpr * (x + ((this.canvasState.rectWidth - preRenderedText.width/this.canvasState.dpr)) / 2)), this.canvasState.dpr * (y - 7.5));
 					this.canvasContext.scale(this.canvasState.dpr, this.canvasState.dpr);
diff --git a/src/searchView.js b/src/searchView.js
@@ -7,7 +7,7 @@ import { client } from './hafasClient.js';
 import { getIBNRbyDS100 } from './ds100.js';
 import { formatPoint } from './formatters.js';
 import { newJourneys } from './app_functions.js';
-import { padZeros, sleep, queryBackgroundColor, setThemeColor } from './helpers.js';
+import { CustomDate, padZeros, sleep, queryBackgroundColor, setThemeColor } from './helpers.js';
 
 import { baseStyles, helperStyles, flexboxStyles, buttonInputStyles, iconStyles, searchViewStyles } from './styles.js';
 import { settings } from './settings.js';

@@ -158,10 +158,10 @@ class SearchView extends LitOverlay {
 						<div class="button now" title="${t('titleSetDateTimeNow')}" @click=${this.resetDate}>${t('now')}</div>
 						${!this.settingsState.combineDateTime ? html`
 						<input type="time" name="time" title="${t('time')}" class="flex-grow" @change=${this.changeHandler} .value=${this.date.formatTime()} required>
-						<input type="date" name="date" title="${t('date')}" class="flex-grow" @change=${this.changeHandler} .value=${this.date.formatDate()} required>
+						<input type="date" name="date" title="${t('date')}" class="flex-grow" @change=${this.changeHandler} .value=${this.date.formatISODate()} required>
 						` : html`
 						<input type="datetime-local" name="dateTime" title="${t('date')} & ${t('time')}" class="flex-grow"
-						@change=${this.changeHandler} .value=${this.date.formatDateTime()} required>
+						@change=${this.changeHandler} .value=${this.date.formatISODateTime()} required>
 						`}
 					</div>
 

@@ -532,29 +532,4 @@ class SearchView extends LitOverlay {
 	}
 }
 
-class CustomDate extends Date {
-	formatDate()     { return `${this.getFullYear()}-${padZeros(this.getMonth()+1)}-${padZeros(this.getDate())}`; }
-	formatTime()     { return `${padZeros(this.getHours())}:${padZeros(this.getMinutes())}`; }
-	formatDateTime() { return `${this.formatDate()}T${this.formatTime()}`; }
-
-	setDate(value) {
-		const splitValue = value.split('-');
-		if (splitValue.length !== 3) return false;
-		this.setFullYear(splitValue[0], splitValue[1]-1, splitValue[2]);
-	}
-
-	setTime(value) {
-		const splitValue = value.split(':');
-		if (splitValue.length !== 2) return false;
-		this.setHours(splitValue[0], splitValue[1]);
-	}
-
-	setDateTime(value) {
-		const splitValue = value.split('T');
-		if (splitValue.length !== 2) return false;
-		this.setDate(splitValue[0]);
-		this.setTime(splitValue[1]);
-	}
-}
-
 customElements.define('search-view', SearchView);
diff --git a/src/templates.js b/src/templates.js
@@ -1,10 +1,9 @@
 import { html, css, nothing } from 'lit';
 import { unsafeHTML } from 'lit/directives/unsafe-html.js';
 
-import { formatTime } from './formatters.js';
 import { settingsState } from './settings.js';
 import { getDS100byIBNR } from './ds100.js';
-
+import { CustomDate } from './helpers.js';
 
 export const remarksModal = (element, remarks) => element.showDialogOverlay('remarks', [].concat(
 	remarks.map(remark => html`

@@ -88,10 +87,10 @@ export const timeTemplate = (data, mode) => {
 	const dateObj = getField('when') || getField('plannedWhen');
 	if (!dateObj) return '-';
 
-	if (dateObj.toLocaleDateString() !== new Date().toLocaleDateString()) {
-		timeStr = `${formatTime(dateObj)}, ${dateObj.getDate()}.${dateObj.getMonth() + 1}.`;
+	if (dateObj.toLocaleDateString() !== new CustomDate().toLocaleDateString()) {
+		timeStr = `${dateObj.formatTime()}, ${dateObj.getDate()}.${dateObj.getMonth() + 1}.`;
 	} else {
-		timeStr = formatTime(dateObj);
+		timeStr = dateObj.formatTime();
 	}
 
 	const delayMinutes = Math.round(getField('delay') / 60);