mirror of
https://github.com/callumalpass/tasknotes.git
synced 2026-07-22 12:50:26 +00:00
fix bases metadata timestamp format
This commit is contained in:
parent
fdf9e6bd11
commit
bb4a2e9d66
19 changed files with 156 additions and 52 deletions
|
|
@ -125,6 +125,7 @@ Example:
|
|||
## Fixed
|
||||
|
||||
- ([#508](https://github.com/callumalpass/tasknotes/issues/508)) Showed project note names instead of full project paths in grouped Kanban and Task List headings, while keeping the headings linked to the project note. Thanks to @elvarb for reporting this and @dmodify for confirming it in Task List.
|
||||
- ([#466](https://github.com/callumalpass/tasknotes/issues/466)) Stored task created/modified metadata as `YYYY-MM-DDTHH:mm:ss` datetimes so Obsidian Bases can recognize those properties as dates more reliably across regions. Thanks to @Moyf for reporting this.
|
||||
- ([#552](https://github.com/callumalpass/tasknotes/issues/552)) Made instant task convert buttons appear for checkbox tasks inside callouts, and kept converted links inside the callout list. Thanks to @Oblique82 for reporting this and @ksdavidc for the follow-up.
|
||||
- ([#559](https://github.com/callumalpass/tasknotes/issues/559)) Treated notes linked to one loaded recurring calendar event instance as related to the other instances in that series. Thanks to @cathywu for reporting this and @mdbraber for the implementation notes.
|
||||
- ([#685](https://github.com/callumalpass/tasknotes/issues/685)) Made Calendar list views respect the All-day slot option by hiding all-day events from the list when that option is turned off. Thanks to @RumiaKitinari for reporting this.
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ This reference documents the expected data types for each frontmatter property t
|
|||
| due | text (date) | `"2025-01-15"` |
|
||||
| scheduled | text (date) | `"2025-01-10"` |
|
||||
| completedDate | text (date) | `"2025-01-20"` |
|
||||
| dateCreated | text (datetime) | `"2025-01-01T08:00:00Z"` |
|
||||
| dateModified | text (datetime) | `"2025-01-15T10:30:00Z"` |
|
||||
| dateCreated | text (datetime) | `"2025-01-01T08:00:00"` |
|
||||
| dateModified | text (datetime) | `"2025-01-15T10:30:00"` |
|
||||
| tags | list | `["work", "urgent"]` |
|
||||
| contexts | list | `["@office", "@home"]` |
|
||||
| projects | list | `["[[Project A]]"]` |
|
||||
|
|
@ -99,14 +99,14 @@ When in doubt, prefer ISO-style values. They sort correctly as text, travel well
|
|||
- **Type:** text (datetime string)
|
||||
- **Format:** ISO 8601 timestamp
|
||||
- **Description:** When the task was created
|
||||
- **Example:** `dateCreated: "2025-01-01T08:00:00Z"`
|
||||
- **Example:** `dateCreated: "2025-01-01T08:00:00"`
|
||||
|
||||
#### dateModified
|
||||
|
||||
- **Type:** text (datetime string)
|
||||
- **Format:** ISO 8601 timestamp
|
||||
- **Description:** When the task was last modified
|
||||
- **Example:** `dateModified: "2025-01-15T10:30:00Z"`
|
||||
- **Example:** `dateModified: "2025-01-15T10:30:00"`
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -308,8 +308,8 @@ contexts:
|
|||
projects:
|
||||
- "[[Q1 Planning]]"
|
||||
timeEstimate: 240
|
||||
dateCreated: "2025-01-01T08:00:00Z"
|
||||
dateModified: "2025-01-20T14:30:00Z"
|
||||
dateCreated: "2025-01-01T08:00:00"
|
||||
dateModified: "2025-01-20T14:30:00"
|
||||
timeEntries:
|
||||
- startTime: "2025-01-20T10:00:00Z"
|
||||
endTime: "2025-01-20T11:30:00Z"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { renderGroupTitle } from "./groupTitleRenderer";
|
|||
import { type LinkServices } from "../ui/renderers/linkRenderer";
|
||||
import { showConfirmationModal } from "../modals/ConfirmationModal";
|
||||
import { VirtualScroller } from "../utils/VirtualScroller";
|
||||
import { getCurrentTimestamp } from "../utils/dateUtils";
|
||||
import { getCurrentTimestampForStorage } from "../utils/dateUtils";
|
||||
import { getProjectDisplayName } from "../utils/linkUtils";
|
||||
import { stringifyUnknown } from "../utils/stringUtils";
|
||||
import {
|
||||
|
|
@ -3742,7 +3742,7 @@ export class KanbanView extends BasesViewBase {
|
|||
);
|
||||
const dateModifiedField =
|
||||
this.plugin.fieldMapper.toUserField("dateModified");
|
||||
fm[dateModifiedField] = getCurrentTimestamp();
|
||||
fm[dateModifiedField] = getCurrentTimestampForStorage();
|
||||
} else if (needsSwimlaneUpdate && swimlaneTaskProp === "status") {
|
||||
const task = this.taskInfoCache.get(path);
|
||||
const isRecurring = !!task?.recurrence;
|
||||
|
|
@ -3753,7 +3753,7 @@ export class KanbanView extends BasesViewBase {
|
|||
);
|
||||
const dateModifiedField =
|
||||
this.plugin.fieldMapper.toUserField("dateModified");
|
||||
fm[dateModifiedField] = getCurrentTimestamp();
|
||||
fm[dateModifiedField] = getCurrentTimestampForStorage();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -3782,7 +3782,7 @@ export class KanbanView extends BasesViewBase {
|
|||
...originalTask,
|
||||
[changedTaskProp]: newPropValue,
|
||||
};
|
||||
updatedTask.dateModified = getCurrentTimestamp();
|
||||
updatedTask.dateModified = getCurrentTimestampForStorage();
|
||||
if (changedTaskProp === "status" && !originalTask.recurrence) {
|
||||
if (
|
||||
this.plugin.statusManager.isCompletedStatus(
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { ReminderModal } from "../modals/ReminderModal";
|
|||
import {
|
||||
getDatePart,
|
||||
getTimePart,
|
||||
getCurrentTimestamp,
|
||||
getCurrentTimestampForStorage,
|
||||
parseDateToUTC,
|
||||
createUTCDateFromLocalCalendarDate,
|
||||
} from "../utils/dateUtils";
|
||||
|
|
@ -1249,7 +1249,7 @@ export class TaskListView extends BasesViewBase {
|
|||
);
|
||||
const dateModifiedField =
|
||||
this.plugin.fieldMapper.toUserField("dateModified");
|
||||
fm[dateModifiedField] = getCurrentTimestamp();
|
||||
fm[dateModifiedField] = getCurrentTimestampForStorage();
|
||||
}
|
||||
}
|
||||
if (sortOrderPlan.sortOrder !== null) {
|
||||
|
|
@ -1287,7 +1287,7 @@ export class TaskListView extends BasesViewBase {
|
|||
} else {
|
||||
updatedRecord[groupByTaskProp] = normalizedTargetGroupKey;
|
||||
}
|
||||
updatedTask.dateModified = getCurrentTimestamp();
|
||||
updatedTask.dateModified = getCurrentTimestampForStorage();
|
||||
if (groupByTaskProp === "status" && !originalTask.recurrence) {
|
||||
if (
|
||||
normalizedTargetGroupKey !== null &&
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
normalizeDependencyList,
|
||||
serializeDependencies,
|
||||
} from "../utils/dependencyUtils";
|
||||
import { validateCompleteInstances } from "../utils/dateUtils";
|
||||
import { normalizeTimestampForStorage, validateCompleteInstances } from "../utils/dateUtils";
|
||||
import { stringifyUnknown } from "../utils/stringUtils";
|
||||
|
||||
export function toUserField(mapping: FieldMapping, internalName: keyof FieldMapping): string {
|
||||
|
|
@ -354,11 +354,11 @@ export function mapTaskToFrontmatter(
|
|||
}
|
||||
|
||||
if (taskData.dateCreated !== undefined) {
|
||||
frontmatter[mapping.dateCreated] = taskData.dateCreated;
|
||||
frontmatter[mapping.dateCreated] = normalizeTimestampForStorage(taskData.dateCreated);
|
||||
}
|
||||
|
||||
if (taskData.dateModified !== undefined) {
|
||||
frontmatter[mapping.dateModified] = taskData.dateModified;
|
||||
frontmatter[mapping.dateModified] = normalizeTimestampForStorage(taskData.dateModified);
|
||||
}
|
||||
|
||||
if (taskData.sortOrder !== undefined) {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import { openTaskSelector } from "./modals/TaskSelectorWithCreateModal";
|
|||
import { ProjectSelectModal } from "./modals/ProjectSelectModal";
|
||||
import { PomodoroService } from "./services/PomodoroService";
|
||||
import { formatTime, getActiveTimeEntry } from "./utils/helpers";
|
||||
import { convertUTCToLocalCalendarDate, getCurrentTimestamp } from "./utils/dateUtils";
|
||||
import { convertUTCToLocalCalendarDate, getCurrentTimestampForStorage } from "./utils/dateUtils";
|
||||
import { TaskManager } from "./utils/TaskManager";
|
||||
import { DependencyCache } from "./utils/DependencyCache";
|
||||
import { RequestDeduplicator, PredictivePrefetcher } from "./utils/RequestDeduplicator";
|
||||
|
|
@ -1278,7 +1278,7 @@ export default class TaskNotesPlugin extends Plugin {
|
|||
// Build a TaskInfo object from the note's existing data
|
||||
// Use defaults for required fields that don't exist
|
||||
// Use ?? (nullish coalescing) to properly handle empty string defaults
|
||||
const now = getCurrentTimestamp();
|
||||
const now = getCurrentTimestampForStorage();
|
||||
const taskInfo: TaskInfo = {
|
||||
path: activeFile.path,
|
||||
title: frontmatterString(frontmatter.title) || activeFile.basename,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { App, Notice, TFile, TAbstractFile } from "obsidian";
|
|||
import TaskNotesPlugin from "../main";
|
||||
import { TaskModal } from "./TaskModal";
|
||||
import { TaskDependency, TaskInfo } from "../types";
|
||||
import { formatTimestampForDisplay, getCurrentTimestamp } from "../utils/dateUtils";
|
||||
import { formatTimestampForDisplay, getCurrentTimestampForStorage } from "../utils/dateUtils";
|
||||
import {
|
||||
extractTaskInfo,
|
||||
calculateTotalTimeSpent,
|
||||
|
|
@ -595,7 +595,7 @@ export class TaskEditModal extends TaskModal {
|
|||
this.isConvertingNoteToTask &&
|
||||
Object.keys(result.changes).length === 0
|
||||
) {
|
||||
result.changes.dateModified = getCurrentTimestamp();
|
||||
result.changes.dateModified = getCurrentTimestampForStorage();
|
||||
}
|
||||
|
||||
return result.changes;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Reminder, TaskCreationData, TaskDependency } from "../types";
|
||||
import { getCurrentTimestamp } from "../utils/dateUtils";
|
||||
import { getCurrentTimestampForStorage } from "../utils/dateUtils";
|
||||
import { sanitizeTags } from "../utils/helpers";
|
||||
import { splitListPreservingLinksAndQuotes } from "../utils/stringSplit";
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ export interface CreationBlockingUpdates {
|
|||
}
|
||||
|
||||
export function buildTaskCreationData(input: TaskCreationDataInput): TaskCreationData {
|
||||
const now = getCurrentTimestamp();
|
||||
const now = getCurrentTimestampForStorage();
|
||||
const contextList = input.contexts
|
||||
.split(",")
|
||||
.map((context) => context.trim())
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Reminder, TaskDependency, TaskInfo } from "../types";
|
||||
import { HideIdentifyingTagsMode, UserMappedField } from "../types/settings";
|
||||
import { getCurrentTimestamp } from "../utils/dateUtils";
|
||||
import { getCurrentTimestampForStorage } from "../utils/dateUtils";
|
||||
import { updateToNextScheduledOccurrence, sanitizeTags, updateDTSTARTInRecurrenceRule } from "../utils/helpers";
|
||||
import { parseLinkToPath } from "../utils/linkUtils";
|
||||
import { splitListPreservingLinksAndQuotes } from "../utils/stringSplit";
|
||||
|
|
@ -195,7 +195,7 @@ export function buildTaskEditChanges(input: TaskEditChangeInput): TaskEditChange
|
|||
}
|
||||
|
||||
if (Object.keys(changes).length > 0) {
|
||||
changes.dateModified = getCurrentTimestamp();
|
||||
changes.dateModified = getCurrentTimestampForStorage();
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { format } from "date-fns";
|
|||
import { processFolderTemplate } from "../utils/folderTemplateProcessor";
|
||||
import TaskNotesPlugin from "../main";
|
||||
import { ICSEvent, TaskInfo, NoteInfo, TaskCreationData } from "../types";
|
||||
import { getCurrentTimestamp, formatDateForStorage } from "../utils/dateUtils";
|
||||
import { getCurrentTimestampForStorage, formatDateForStorage } from "../utils/dateUtils";
|
||||
import {
|
||||
generateICSNoteFilename,
|
||||
generateUniqueFilename,
|
||||
|
|
@ -251,8 +251,8 @@ export class ICSNoteService {
|
|||
overrides?.details || this.buildICSEventDetails(icsEvent, subscriptionName),
|
||||
icsEventId: [icsEvent.id],
|
||||
creationContext: "ics-event",
|
||||
dateCreated: getCurrentTimestamp(),
|
||||
dateModified: getCurrentTimestamp(),
|
||||
dateCreated: getCurrentTimestampForStorage(),
|
||||
dateModified: getCurrentTimestampForStorage(),
|
||||
// Spread overrides but exclude 'due' since we handle it specially above
|
||||
...Object.fromEntries(
|
||||
Object.entries(overrides || {}).filter(([key]) => key !== "due")
|
||||
|
|
@ -433,8 +433,8 @@ export class ICSNoteService {
|
|||
const dateModifiedField = this.plugin.fieldMapper.toUserField("dateModified");
|
||||
let frontmatter: Record<string, unknown> = {
|
||||
title: noteTitle,
|
||||
[dateCreatedField]: getCurrentTimestamp(),
|
||||
[dateModifiedField]: getCurrentTimestamp(),
|
||||
[dateCreatedField]: getCurrentTimestampForStorage(),
|
||||
[dateModifiedField]: getCurrentTimestampForStorage(),
|
||||
tags: [this.plugin.fieldMapper.toUserField("icsEventTag")],
|
||||
[this.plugin.fieldMapper.toUserField("icsEventId")]: [icsEvent.id],
|
||||
};
|
||||
|
|
@ -600,7 +600,7 @@ export class ICSNoteService {
|
|||
|
||||
frontmatter[icsEventIdField] = existingIds;
|
||||
const dateModifiedField = this.plugin.fieldMapper.toUserField("dateModified");
|
||||
frontmatter[dateModifiedField] = getCurrentTimestamp();
|
||||
frontmatter[dateModifiedField] = getCurrentTimestampForStorage();
|
||||
});
|
||||
|
||||
new Notice(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { TasksPluginParser, ParsedTaskData } from "../utils/TasksPluginParser";
|
|||
import { NaturalLanguageParser } from "./NaturalLanguageParser";
|
||||
import { Reminder, TaskCreationData } from "../types";
|
||||
import {
|
||||
getCurrentTimestamp,
|
||||
getCurrentTimestampForStorage,
|
||||
combineDateAndTime,
|
||||
parseDateToUTC,
|
||||
formatDateForStorage,
|
||||
|
|
@ -773,8 +773,8 @@ export class InstantTaskConvertService {
|
|||
details: enhancedDetails, // Use enhanced details with any overflow from title truncation
|
||||
parentNote: parentNote, // Include parent note for template variable
|
||||
creationContext: "inline-conversion", // Mark as inline conversion for folder logic
|
||||
dateCreated: getCurrentTimestamp(),
|
||||
dateModified: getCurrentTimestamp(),
|
||||
dateCreated: getCurrentTimestampForStorage(),
|
||||
dateModified: getCurrentTimestampForStorage(),
|
||||
customFrontmatter:
|
||||
Object.keys(customFrontmatter).length > 0 ? customFrontmatter : undefined,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import {
|
|||
formatDateForStorage,
|
||||
getDatePart,
|
||||
getCurrentDateString,
|
||||
getCurrentTimestamp,
|
||||
getCurrentTimestampForStorage,
|
||||
getTodayLocal,
|
||||
createUTCDateFromLocalCalendarDate,
|
||||
parseDateToUTC,
|
||||
|
|
@ -530,7 +530,7 @@ export class TaskService {
|
|||
? this.normalizeBlockedByValue(value)
|
||||
: value;
|
||||
updatedTask[property] = normalizedValue;
|
||||
updatedTask.dateModified = getCurrentTimestamp();
|
||||
updatedTask.dateModified = getCurrentTimestampForStorage();
|
||||
|
||||
// Handle derivative changes for status updates
|
||||
if (property === "status" && !freshTask.recurrence) {
|
||||
|
|
@ -792,7 +792,7 @@ export class TaskService {
|
|||
// Step 1: Construct new state in memory
|
||||
const updatedTask = { ...task };
|
||||
updatedTask.archived = !isCurrentlyArchived;
|
||||
updatedTask.dateModified = getCurrentTimestamp();
|
||||
updatedTask.dateModified = getCurrentTimestampForStorage();
|
||||
|
||||
// Update tags array to include/exclude archive tag
|
||||
if (!updatedTask.tags) {
|
||||
|
|
@ -1027,7 +1027,7 @@ export class TaskService {
|
|||
|
||||
// Step 1: Construct new state in memory
|
||||
const updatedTask = { ...task };
|
||||
updatedTask.dateModified = getCurrentTimestamp();
|
||||
updatedTask.dateModified = getCurrentTimestampForStorage();
|
||||
|
||||
if (!updatedTask.timeEntries) {
|
||||
updatedTask.timeEntries = [];
|
||||
|
|
@ -1119,7 +1119,7 @@ export class TaskService {
|
|||
|
||||
// Step 1: Construct new state in memory
|
||||
const updatedTask = { ...task };
|
||||
updatedTask.dateModified = getCurrentTimestamp();
|
||||
updatedTask.dateModified = getCurrentTimestampForStorage();
|
||||
|
||||
if (updatedTask.timeEntries && Array.isArray(updatedTask.timeEntries)) {
|
||||
updatedTask.timeEntries = updatedTask.timeEntries.map((entry) => {
|
||||
|
|
@ -1432,7 +1432,7 @@ export class TaskService {
|
|||
|
||||
// Step 1: Construct new state in memory using fresh data
|
||||
const updatedTask = { ...freshTask };
|
||||
updatedTask.dateModified = getCurrentTimestamp();
|
||||
updatedTask.dateModified = getCurrentTimestampForStorage();
|
||||
|
||||
if (newComplete) {
|
||||
// Add date to completed instances if not already present
|
||||
|
|
@ -1652,7 +1652,7 @@ export class TaskService {
|
|||
|
||||
// Step 1: Construct new state in memory
|
||||
const updatedTask = { ...freshTask };
|
||||
updatedTask.dateModified = getCurrentTimestamp();
|
||||
updatedTask.dateModified = getCurrentTimestampForStorage();
|
||||
|
||||
if (newSkipped) {
|
||||
// Mark as skipped
|
||||
|
|
@ -1779,7 +1779,7 @@ export class TaskService {
|
|||
|
||||
// Step 1: Construct new state in memory
|
||||
const updatedTask = { ...task };
|
||||
updatedTask.dateModified = getCurrentTimestamp();
|
||||
updatedTask.dateModified = getCurrentTimestampForStorage();
|
||||
|
||||
// Remove the time entry at the specified index
|
||||
updatedTask.timeEntries = task.timeEntries.filter((_, index) => index !== timeEntryIndex);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ import {
|
|||
generateUniqueFilename,
|
||||
} from "../../utils/filenameGenerator";
|
||||
import { ensureFolderExists } from "../../utils/helpers";
|
||||
import { getCurrentTimestamp } from "../../utils/dateUtils";
|
||||
import {
|
||||
getCurrentTimestampForStorage,
|
||||
normalizeTimestampForStorage,
|
||||
} from "../../utils/dateUtils";
|
||||
import { stringifyUnknown } from "../../utils/stringUtils";
|
||||
import { mergeTemplateFrontmatter } from "../../utils/templateProcessor";
|
||||
import {
|
||||
|
|
@ -66,8 +69,12 @@ export class TaskCreationService {
|
|||
: title;
|
||||
const priority = taskData.priority || plugin.settings.defaultTaskPriority;
|
||||
const status = taskData.status || plugin.settings.defaultTaskStatus;
|
||||
const dateCreated = taskData.dateCreated || getCurrentTimestamp();
|
||||
const dateModified = taskData.dateModified || getCurrentTimestamp();
|
||||
const dateCreated = taskData.dateCreated
|
||||
? normalizeTimestampForStorage(taskData.dateCreated)
|
||||
: getCurrentTimestampForStorage();
|
||||
const dateModified = taskData.dateModified
|
||||
? normalizeTimestampForStorage(taskData.dateModified)
|
||||
: getCurrentTimestampForStorage();
|
||||
|
||||
const contextsArray = taskData.contexts || [];
|
||||
const projectsArray = taskData.projects || [];
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { EVENT_TASK_UPDATED, IWebhookNotifier, TaskInfo } from "../../types";
|
|||
import { addDTSTARTToRecurrenceRule, updateToNextScheduledOccurrence } from "../../core/recurrence";
|
||||
import { splitFrontmatterAndBody } from "../../utils/helpers";
|
||||
import { generateUniqueFilename } from "../../utils/filenameGenerator";
|
||||
import { getCurrentDateString, getCurrentTimestamp } from "../../utils/dateUtils";
|
||||
import { getCurrentDateString, getCurrentTimestampForStorage } from "../../utils/dateUtils";
|
||||
import {
|
||||
applyPropertyTaskIdentifier,
|
||||
getFrontmatterTags,
|
||||
|
|
@ -84,7 +84,7 @@ export class TaskUpdateService {
|
|||
...originalTask,
|
||||
...updates,
|
||||
...recurrenceUpdates,
|
||||
dateModified: getCurrentTimestamp(),
|
||||
dateModified: getCurrentTimestampForStorage(),
|
||||
};
|
||||
|
||||
const mappedFrontmatter = plugin.fieldMapper.mapToFrontmatter(
|
||||
|
|
@ -178,7 +178,7 @@ export class TaskUpdateService {
|
|||
...updates,
|
||||
...recurrenceUpdates,
|
||||
path: newPath,
|
||||
dateModified: getCurrentTimestamp(),
|
||||
dateModified: getCurrentTimestampForStorage(),
|
||||
};
|
||||
if (finalTags) {
|
||||
updatedTask.tags = finalTags;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { CliData } from "obsidian";
|
|||
import type TaskNotesPlugin from "../main";
|
||||
import type { Reminder, TaskCreationData } from "../types";
|
||||
import { NaturalLanguageParser } from "../services/NaturalLanguageParser";
|
||||
import { getCurrentTimestamp } from "./dateUtils";
|
||||
import { getCurrentTimestampForStorage } from "./dateUtils";
|
||||
import { sanitizeTags } from "./helpers";
|
||||
import { buildTaskCreationDataFromParsed } from "./buildTaskCreationDataFromParsed";
|
||||
|
||||
|
|
@ -150,7 +150,7 @@ export function buildTaskCreationDataFromCli(
|
|||
});
|
||||
usedNlp = true;
|
||||
} else {
|
||||
const now = getCurrentTimestamp();
|
||||
const now = getCurrentTimestampForStorage();
|
||||
const title = titleOverride || text;
|
||||
if (!title) {
|
||||
throw new Error("Either --text or --title is required");
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type TaskNotesPlugin from "../main";
|
||||
import type { ParsedTaskData } from "../services/NaturalLanguageParser";
|
||||
import type { TaskCreationData } from "../types";
|
||||
import { combineDateAndTime, getCurrentTimestamp } from "./dateUtils";
|
||||
import { combineDateAndTime, getCurrentTimestampForStorage } from "./dateUtils";
|
||||
import { sanitizeTags } from "./helpers";
|
||||
|
||||
interface BuildTaskCreationDataOptions {
|
||||
|
|
@ -13,7 +13,7 @@ export function buildTaskCreationDataFromParsed(
|
|||
parsed: ParsedTaskData,
|
||||
options: BuildTaskCreationDataOptions = {}
|
||||
): TaskCreationData {
|
||||
const now = getCurrentTimestamp();
|
||||
const now = getCurrentTimestampForStorage();
|
||||
const taskData: TaskCreationData = {
|
||||
title: parsed.title.trim(),
|
||||
status: parsed.status || plugin.settings.defaultTaskStatus,
|
||||
|
|
|
|||
|
|
@ -680,6 +680,50 @@ export function getCurrentTimestamp(): string {
|
|||
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}${diff}${tzOffsetHours}:${tzOffsetMinutes}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current timestamp in Obsidian/Bases-friendly local datetime format.
|
||||
* Bases date-time fields reliably parse `YYYY-MM-DDTHH:mm:ss`; including
|
||||
* milliseconds and timezone offsets can be interpreted as text in some locales.
|
||||
*/
|
||||
export function getCurrentTimestampForStorage(): string {
|
||||
return formatDateTimeForStorage(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a task metadata timestamp to the storage format expected by Bases.
|
||||
* Invalid or blank values are returned unchanged so imports do not silently
|
||||
* discard user data.
|
||||
*/
|
||||
export function normalizeTimestampForStorage(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return value;
|
||||
|
||||
const isoDateTimeMatch = trimmed.match(
|
||||
/^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}:\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/
|
||||
);
|
||||
if (isoDateTimeMatch) {
|
||||
return `${isoDateTimeMatch[1]}T${isoDateTimeMatch[2]}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = parseDateToLocalInternal(trimmed);
|
||||
if (!isValid(parsed)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return formatDateTimeForStorage(parsed);
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function formatDateTimeForStorage(date: Date): string {
|
||||
const pad = (num: number) => String(num).padStart(2, "0");
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(
|
||||
date.getHours()
|
||||
)}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current date in YYYY-MM-DD format for completion dates
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
import { mapTaskToFrontmatter } from "../../../src/core/fieldMapping";
|
||||
import { DEFAULT_FIELD_MAPPING } from "../../../src/settings/defaults";
|
||||
|
||||
describe("Issue #466: Bases-compatible task metadata timestamps", () => {
|
||||
it("normalizes dateCreated and dateModified to datetime strings Bases can parse", () => {
|
||||
const frontmatter = mapTaskToFrontmatter(DEFAULT_FIELD_MAPPING, {
|
||||
title: "Timestamp metadata",
|
||||
status: "open",
|
||||
dateCreated: "2025-08-15T14:53:20.653+08:00",
|
||||
dateModified: "2025-08-15T14:53:20.653Z",
|
||||
});
|
||||
|
||||
expect(frontmatter.dateCreated).toBe("2025-08-15T14:53:20");
|
||||
expect(frontmatter.dateModified).toBe("2025-08-15T14:53:20");
|
||||
});
|
||||
|
||||
it("preserves already compatible datetime values", () => {
|
||||
const frontmatter = mapTaskToFrontmatter(DEFAULT_FIELD_MAPPING, {
|
||||
title: "Timestamp metadata",
|
||||
status: "open",
|
||||
dateCreated: "2025-08-15T14:53:20",
|
||||
dateModified: "2025-08-15T14:53:21",
|
||||
});
|
||||
|
||||
expect(frontmatter.dateCreated).toBe("2025-08-15T14:53:20");
|
||||
expect(frontmatter.dateModified).toBe("2025-08-15T14:53:21");
|
||||
});
|
||||
});
|
||||
|
|
@ -24,6 +24,8 @@ import {
|
|||
isPastDate,
|
||||
formatDateForDisplay,
|
||||
getCurrentTimestamp,
|
||||
getCurrentTimestampForStorage,
|
||||
normalizeTimestampForStorage,
|
||||
getCurrentDateString,
|
||||
parseTimestamp,
|
||||
formatTimestampForDisplay,
|
||||
|
|
@ -274,6 +276,28 @@ describe('DateUtils', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getCurrentTimestampForStorage', () => {
|
||||
it('should return a Bases-compatible local datetime without milliseconds or offset', () => {
|
||||
const result = getCurrentTimestampForStorage();
|
||||
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeTimestampForStorage', () => {
|
||||
it('should remove milliseconds and timezone offsets from task metadata timestamps', () => {
|
||||
expect(normalizeTimestampForStorage('2025-08-15T14:53:20.653+08:00')).toBe(
|
||||
'2025-08-15T14:53:20'
|
||||
);
|
||||
expect(normalizeTimestampForStorage('2025-08-15T14:53:20')).toBe(
|
||||
'2025-08-15T14:53:20'
|
||||
);
|
||||
});
|
||||
|
||||
it('should preserve invalid timestamp values instead of dropping user data', () => {
|
||||
expect(normalizeTimestampForStorage('not-a-date')).toBe('not-a-date');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCurrentDateString', () => {
|
||||
it('should return current date in YYYY-MM-DD format', () => {
|
||||
const result = getCurrentDateString();
|
||||
|
|
@ -1283,4 +1307,4 @@ describe('DateUtils', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue