fix bases metadata timestamp format

This commit is contained in:
callumalpass 2026-05-18 13:14:55 +10:00
parent fdf9e6bd11
commit bb4a2e9d66
19 changed files with 156 additions and 52 deletions

View file

@ -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.

View file

@ -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"

View file

@ -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(

View file

@ -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 &&

View file

@ -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) {

View file

@ -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,

View file

@ -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;

View file

@ -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())

View file

@ -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 {

View file

@ -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(

View file

@ -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,
};

View file

@ -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);

View file

@ -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 || [];

View file

@ -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;

View file

@ -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");

View file

@ -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,

View file

@ -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
*/

View file

@ -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");
});
});

View file

@ -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', () => {
});
});
});
});
});