chore: release v0.23.2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ulas Kalayci
2026-04-22 07:34:23 +02:00
parent 8d92b59c73
commit 6a4ef09912
4 changed files with 35 additions and 14 deletions
+5
View File
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.23.2] - 2026-04-22
### Fixed
- Calendar: ICS-synced events now render at the correct local hour and day in week/day/month/agenda views; day-matching and hour-positioning previously used raw string slices which returned UTC values instead of browser-local time for events stored with a `Z` suffix
## [0.23.1] - 2026-04-22
### Security
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "oikos",
"version": "0.23.1",
"version": "0.23.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "oikos",
"version": "0.23.1",
"version": "0.23.2",
"license": "MIT",
"dependencies": {
"bcrypt": "^6.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "oikos",
"version": "0.23.1",
"version": "0.23.2",
"description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.",
"main": "server/index.js",
"type": "module",
+27 -11
View File
@@ -86,6 +86,22 @@ let _container = null;
function pad(n) { return String(n).padStart(2, '0'); }
function isoDate(d) { return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; }
// Extract YYYY-MM-DD in the browser's local timezone from any datetime string.
// For date-only strings (≤10 chars) slicing is safe; for datetime strings with an
// explicit UTC offset or 'Z' suffix, new Date() converts to local before extraction.
function localDate(str) {
if (!str || str.length <= 10) return (str || '').slice(0, 10);
const d = new Date(str);
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
}
// Extract HH:MM in the browser's local timezone from a datetime string.
function localTime(str) {
if (!str || str.length <= 10) return '00:00';
const d = new Date(str);
return `${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
function addMonths(dateStr, n) {
const d = new Date(dateStr + 'T00:00:00');
d.setMonth(d.getMonth() + n);
@@ -119,9 +135,9 @@ function formatDate(dateStr, { long = false, weekday = false } = {}) {
function formatDateTime(datetimeStr) {
if (!datetimeStr) return '';
const date = datetimeStr.slice(0, 10);
const hasTime = datetimeStr.length > 10 && datetimeStr.slice(11, 16).trim() !== '';
const time = hasTime ? formatTime(datetimeStr) : '';
const date = localDate(datetimeStr);
const hasTime = datetimeStr.length > 10;
const time = hasTime ? formatTime(datetimeStr) : '';
return time ? `${formatDate(date)} ${time} ${t('calendar.timeSuffix')}`.trimEnd() : formatDate(date);
}
@@ -146,8 +162,8 @@ function getAgendaRange(dateStr) {
function eventsOnDay(dateStr) {
return state.events.filter((e) => {
const start = e.start_datetime.slice(0, 10);
const end = e.end_datetime ? e.end_datetime.slice(0, 10) : start;
const start = localDate(e.start_datetime);
const end = e.end_datetime ? localDate(e.end_datetime) : start;
return start <= dateStr && end >= dateStr;
});
}
@@ -512,9 +528,9 @@ function renderWeekView(container) {
}
function renderWeekEvent(ev) {
const start = timeToMinutes(ev.start_datetime.slice(11, 16));
const start = timeToMinutes(localTime(ev.start_datetime));
const end = ev.end_datetime
? timeToMinutes(ev.end_datetime.slice(11, 16))
? timeToMinutes(localTime(ev.end_datetime))
: start + 60;
const duration = Math.max(end - start, 30);
@@ -866,12 +882,12 @@ function buildEventModalContent({ mode, event, date, reminder = null }) {
const isEdit = mode === 'edit';
const today = date || state.today;
const startDate = isEdit ? event.start_datetime.slice(0, 10) : today;
const startDate = isEdit ? localDate(event.start_datetime) : today;
const startTime = isEdit && event.start_datetime.length > 10
? event.start_datetime.slice(11, 16) : '09:00';
const endDate = isEdit && event.end_datetime ? event.end_datetime.slice(0, 10) : startDate;
? localTime(event.start_datetime) : '09:00';
const endDate = isEdit && event.end_datetime ? localDate(event.end_datetime) : startDate;
const endTime = isEdit && event.end_datetime && event.end_datetime.length > 10
? event.end_datetime.slice(11, 16) : '10:00';
? localTime(event.end_datetime) : '10:00';
const userOpts = [
`<option value="">${t('calendar.assignedNobody')}</option>`,