From 303e275c27c5f7797263f5abee26fb4d24e137cf Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 3 Jun 2024 13:51:26 -0500 Subject: [PATCH] Provide datetime values with a timezone offset specified. Render differently --- .../core/components/DateTimeSelector.js | 88 +++++++++++++------ .../core/components/JSONSchemaInput.js | 19 ++-- src/schemas/base-metadata.schema.ts | 2 +- 3 files changed, 72 insertions(+), 37 deletions(-) diff --git a/src/electron/frontend/core/components/DateTimeSelector.js b/src/electron/frontend/core/components/DateTimeSelector.js index 4da3a5fbee..3a536111a3 100644 --- a/src/electron/frontend/core/components/DateTimeSelector.js +++ b/src/electron/frontend/core/components/DateTimeSelector.js @@ -1,13 +1,38 @@ import { LitElement, css } from "lit"; -const convertToDateTimeLocalString = (date) => { +export function extractISOString(date, { + // timezone = false, + offset = false, +} = {}) { + + // Function to format the GMT offset + function formatOffset(date) { + let offset = -date.getTimezoneOffset(); // getTimezoneOffset returns the difference in minutes from UTC + const sign = offset >= 0 ? "+" : "-"; + offset = Math.abs(offset); + const hours = String(Math.floor(offset / 60)).padStart(2, '0'); + const minutes = String(offset % 60).padStart(2, '0'); + return `${sign}${hours}:${minutes}`; + } + + // Extract the GMT offset + const gmtOffset = formatOffset(date); + + // Format the date back to the original format with GMT offset const year = date.getFullYear(); - const month = (date.getMonth() + 1).toString().padStart(2, "0"); - const day = date.getDate().toString().padStart(2, "0"); - const hours = date.getHours().toString().padStart(2, "0"); - const minutes = date.getMinutes().toString().padStart(2, "0"); - return `${year}-${month}-${day}T${hours}:${minutes}`; -}; + const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are zero-indexed + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + const seconds = String(date.getSeconds()).padStart(2, '0'); + + // Recreate the ISO string with the GMT offset + let formattedDate = `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`; + if (offset) formattedDate += gmtOffset; + + return formattedDate; +} + export class DateTimeSelector extends LitElement { static get styles() { @@ -19,47 +44,52 @@ export class DateTimeSelector extends LitElement { `; } + + // Manually handle value property get value() { - return this.input.value; + const date = new Date(this.input.value); + return extractISOString(date, { offset: true }); } + + // Render the date without timezone offset set value(newValue) { - if (newValue) this.input.value = newValue; + + if (newValue) this.input.value = extractISOString(new Date(newValue)); + else { const d = new Date(); d.setHours(0, 0, 0, 0); - this.input.value = convertToDateTimeLocalString(d); + this.input.value = extractISOString(d); } } - get min() { - return this.input.min; - } - set min(newValue) { - this.input.min = newValue; + static get properties() { + return { + min: { type: String, reflect: true }, + max: { type: String, reflect: true }, + timezone: { type: String, reflect: true }, + }; } - get max() { - return this.input.max; - } - - set max(newValue) { - this.input.max = newValue; - } - - constructor({ value, min, max } = {}) { + constructor({ + value, + min, + max, + } = {}) { super(); this.input = document.createElement("input"); this.input.type = "datetime-local"; - this.input.min = min; - this.input.max = max; + this.min = min; + this.max = max; + this.addEventListener("click", () => { this.input.focus(); this.input.showPicker(); }); - this.value = value ? convertToDateTimeLocalString(value) : value; + this.value = value; } focus() { @@ -71,6 +101,10 @@ export class DateTimeSelector extends LitElement { } render() { + + this.input.min = min; + this.input.max = max; + return this.input; } } diff --git a/src/electron/frontend/core/components/JSONSchemaInput.js b/src/electron/frontend/core/components/JSONSchemaInput.js index 5e65737232..325afa48a0 100644 --- a/src/electron/frontend/core/components/JSONSchemaInput.js +++ b/src/electron/frontend/core/components/JSONSchemaInput.js @@ -16,18 +16,17 @@ import tippy from "tippy.js"; import { merge } from "./pages/utils"; import { OptionalSection } from "./OptionalSection"; import { InspectorListItem } from "./preview/inspector/InspectorList"; +import { extractISOString } from "./DateTimeSelector"; const isDevelopment = !!import.meta.env; -const dateTimeRegex = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/; - function resolveDateTime(value) { - if (typeof value === "string") { - const match = value.match(dateTimeRegex); - if (match) return `${match[1]}-${match[2]}-${match[3]}T${match[4]}:${match[5]}`; - return value; - } + if (typeof value === "string") return extractISOString(new Date(value), { offset: true }) + return value; +} +function renderDateTime(value) { + if (typeof value === "string") return extractISOString(new Date(value)); return value; } @@ -1226,7 +1225,7 @@ export class JSONSchemaInput extends LitElement { ? "datetime-local" : schema.format ?? (schema.type === "string" ? "text" : schema.type); - const value = isDateTime ? resolveDateTime(this.value) : this.value; + const value = isDateTime ? renderDateTime(this.value) : this.value; const { minimum, maximum, exclusiveMax, exclusiveMin } = schema; const min = exclusiveMin ?? minimum; @@ -1242,6 +1241,7 @@ export class JSONSchemaInput extends LitElement { .min="${min}" .max="${max}" @input=${(ev) => { + let value = ev.target.value; let newValue = value; @@ -1249,6 +1249,7 @@ export class JSONSchemaInput extends LitElement { if (isInteger) value = newValue = parseInt(value); else if (isNumber) value = newValue = parseFloat(value); + else if (isDateTime) value = newValue = resolveDateTime(value) if (isNumber) { if ("min" in schema && newValue < schema.min) newValue = schema.min; @@ -1274,7 +1275,7 @@ export class JSONSchemaInput extends LitElement { const nanHandler = ev.target.parentNode.querySelector(".nan-handler"); if (!(newValue && Number.isNaN(newValue))) nanHandler.checked = false; } - + this.#updateData(fullPath, value); }} @change=${(ev) => validateOnChange && this.#triggerValidation(name, path)} diff --git a/src/schemas/base-metadata.schema.ts b/src/schemas/base-metadata.schema.ts index abfaf72b13..83d5ac830b 100644 --- a/src/schemas/base-metadata.schema.ts +++ b/src/schemas/base-metadata.schema.ts @@ -109,7 +109,7 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema, globa copy.order = [ "NWBFile", "Subject" ] const minDate = "1900-01-01T00:00" - const maxDate = getISODateInTimezone().slice(0, -2) + const maxDate = getISODateInTimezone().slice(0, -2) // Restrict date to current date with timezone awareness // Add unit to weight