/
vshmidt
/
streamlit
Обзор
Документация
Войти
/
vshmidt
/
streamlit
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
frontend/lib/src/components/widgets/DataFrame/columns/DateTimeColumn.ts
390 строк
12 KB
dependabot[bot]
Bump the eslint group in /frontend with 5 updates (#13562)
12 янв 2026, 19:52
Не верифицирован
12 янв 2026, 19:52
fba12bc
Код
Авторство
О чём код?
/** * Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { GridCell, GridCellKind } from "@glideapps/glide-data-grid" import { DatePickerType } from "@glideapps/glide-data-grid-cells" import moment, { Moment } from "moment-timezone" import { getTimezone } from "~lib/dataframes/arrowTypeUtils" import { formatMoment } from "~lib/util/formatMoment" import { isNullOrUndefined, notNullOrUndefined } from "~lib/util/utils" import { BaseColumn, BaseColumnProps, getErrorCell, mergeColumnParameters, toSafeDate, toSafeString, } from "./utils" /** * Apply a timezone to a MomentJS date. * * @param momentDate The date to apply the timezone to * @param timezone The timezone to apply. This can be a timezone name * (e.g. "America/New_York" or "UTC") or a UTC offset (e.g. "+05:00" or "-08:00") * @returns The date with the timezone applied */ function applyTimezone(momentDate: Moment, timezone: string): Moment { if (timezone.startsWith("+") || timezone.startsWith("-")) { // Timezone is a UTC offset (e.g. "+05:00" or "-08:00") momentDate = momentDate.utcOffset(timezone, false) } else { // Timezone is a timezone name (e.g. "America/New_York" or "UTC") momentDate = momentDate.tz(timezone) } return momentDate } export interface DateTimeColumnParams { /** * A momentJS formatting syntax to format the display value. */ readonly format?: string /** * Specifies the granularity that the value must adhere. * For time and datetime, this is the number of seconds between each allowed value. * For date, this is the number of days between each allowed value. */ readonly step?: number /** * A timezone identifier, e.g. "America/New_York", "+05:00", or "UTC" */ readonly timezone?: string /** * The minimum allowed value for editing. This needs to be an ISO formatted * datetime/date/time string (UTC). */ readonly min_value?: string /** * The maximum allowed value for editing. This needs to be an ISO formatted * datetime/date/time string (UTC). */ readonly max_value?: string } /** * Base class for datetime columns. This class is not meant to be used directly. * Instead, use the DateColumn, TimeColumn, or DateTimeColumn classes. * * @param kind The kind of column. This should be one of "date", "time", or "datetime". * @param props The column properties * @param defaultFormat The default format to use for rendering and copy data * @param defaultStep The default step to use, can be overridden by the user * @param inputType The type of input to use for editing. This should be one of "datetime-local", "time", or "date". * @param toISOString A function that converts a Date object to an ISO formatted string * @param timezone The timezone to use to make the datetime values timezone aware. * * @returns A BaseColumn object */ function BaseDateTimeColumn( kind: "date" | "time" | "datetime", props: BaseColumnProps, defaultFormat: string, // used for rendering and copy data defaultStep: number, inputType: "datetime-local" | "time" | "date", toISOString: (date: Date) => string, timezone?: string ): BaseColumn { const parameters = mergeColumnParameters( // Default parameters: { format: defaultFormat, step: defaultStep, timezone, }, // User parameters: props.columnTypeOptions ) as DateTimeColumnParams let defaultTimezoneOffset: number | undefined = undefined if (notNullOrUndefined(parameters.timezone)) { // We try to determine the timezone offset based on today's date // This is needed for the date picker to work correctly when the value is null // TODO(lukasmasuch): But this might not be correct for dates in the past or future // since the timezone offset might have changed based on a timezone name. try { defaultTimezoneOffset = applyTimezone(moment(), parameters.timezone)?.utcOffset() || undefined // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { // Do nothing } } let minDate: Date | undefined = undefined if (notNullOrUndefined(parameters.min_value)) { minDate = toSafeDate(parameters.min_value) || undefined } let maxDate: Date | undefined = undefined if (notNullOrUndefined(parameters.max_value)) { maxDate = toSafeDate(parameters.max_value) || undefined } const cellTemplate: DatePickerType = { kind: GridCellKind.Custom, allowOverlay: true, copyData: "", readonly: !props.isEditable, contentAlign: props.contentAlignment, // The text in pinned columns should be faded. style: props.isPinned ? "faded" : "normal", data: { kind: "date-picker-cell", date: undefined, displayDate: "", step: parameters.step?.toString() || "1", format: inputType, min: minDate, max: maxDate, }, } // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO: Replace 'any' with a more specific type. const validateInput = (data?: any): boolean | Date => { const cellData: Date | null | undefined = toSafeDate(data) if (cellData === null) { if (props.isRequired) { return false } return true } if (cellData === undefined) { // Input cannot be interpreted as a date return false } // Apply min_value configuration option: if ( notNullOrUndefined(minDate) && // We compare on a string level so that it also works correctly for time and date values toISOString(cellData) < toISOString(minDate) ) { return false } // Apply min_value configuration option: if ( notNullOrUndefined(maxDate) && toISOString(cellData) > toISOString(maxDate) ) { return false } // TODO: validate step size return true } return { ...props, kind, typeIcon: kind === "date" ? ":material/calendar_month:" : kind === "time" ? ":material/access_time:" : ":material/calendar_today:", sortMode: "default", validateInput, // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO: Replace 'any' with a more specific type. getCell(data?: any, validate?: boolean): GridCell { if (validate === true) { const validationResult = validateInput(data) if (validationResult === false) { // The input is invalid, we return an error cell which will // prevent this cell to be inserted into the table. return getErrorCell(toSafeString(data), "Invalid input.") } else if (validationResult instanceof Date) { // Apply corrections: data = validationResult } } const cellData = toSafeDate(data) let copyData = "" let displayDate = "" // Initialize with default offset base on today's date let timezoneOffset = defaultTimezoneOffset if (cellData === undefined) { return getErrorCell( toSafeString(data), "The value cannot be interpreted as a datetime object." ) } if (cellData !== null) { // Convert to moment object let momentDate = moment.utc(cellData) if (!momentDate.isValid()) { // The moment date should never be invalid here. return getErrorCell( toSafeString(cellData), `Invalid moment date. This should never happen. Please report this bug. \nError: ${momentDate.toString()}` ) } if (parameters.timezone) { try { momentDate = applyTimezone(momentDate, parameters.timezone) } catch (error) { return getErrorCell( momentDate.toISOString(), // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Failed to adjust to the provided timezone: ${parameters.timezone}. \nError: ${error}` ) } timezoneOffset = momentDate.utcOffset() } try { displayDate = formatMoment( momentDate, parameters.format || defaultFormat, kind ) } catch (error) { return getErrorCell( momentDate.toISOString(), // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Failed to format the date for rendering with: ${parameters.format}. \nError: ${error}` ) } // Copy data should always use the default format copyData = formatMoment(momentDate, defaultFormat, kind) } return { ...cellTemplate, copyData, isMissingValue: isNullOrUndefined(cellData), data: { ...cellTemplate.data, date: cellData, displayDate, timezoneOffset, }, } as DatePickerType }, getCellValue(cell: DatePickerType): string | null { return isNullOrUndefined(cell?.data?.date) ? null : toISOString(cell.data.date) }, } } /** * Creates a new datetime column. * A datetime column supports optimized rendering and editing for datetime values. * * @param props The column properties. * @returns The new column. */ export default function DateTimeColumn(props: BaseColumnProps): BaseColumn { // Do a smart selection of the default format based on the step size let defaultFormat = "YYYY-MM-DD HH:mm:ss" if (props.columnTypeOptions?.step >= 60) { defaultFormat = "YYYY-MM-DD HH:mm" } else if (props.columnTypeOptions?.step < 1) { defaultFormat = "YYYY-MM-DD HH:mm:ss.SSS" } const timezone = getTimezone(props.arrowType) const hasTimezone: boolean = notNullOrUndefined(timezone) || // Timezone can also be configure by the user: notNullOrUndefined(props?.columnTypeOptions?.timezone) return BaseDateTimeColumn( "datetime", props, hasTimezone ? defaultFormat + "Z" : defaultFormat, 1, "datetime-local", (date: Date): string => { if (hasTimezone) { return date.toISOString() } return date.toISOString().replace("Z", "") }, timezone ) } DateTimeColumn.isEditableType = true /** * Creates a new time column. * A time column supports optimized rendering and editing for time values. * * @param props The column properties. * @returns The new column. */ export function TimeColumn(props: BaseColumnProps): BaseColumn { // Do a smart selection of the default format based on the step size let defaultFormat = "HH:mm:ss" if (props.columnTypeOptions?.step >= 60) { defaultFormat = "HH:mm" } else if (props.columnTypeOptions?.step < 1) { defaultFormat = "HH:mm:ss.SSS" } return BaseDateTimeColumn( "time", props, defaultFormat, 1, "time", (date: Date): string => { // Only return the time part of the ISO string: return date.toISOString().split("T")[1].replace("Z", "") } ) } TimeColumn.isEditableType = true /** * Creates a new date column. * A date column supports optimized rendering and editing for date values. * * @param props The column properties. * @returns The new column. */ export function DateColumn(props: BaseColumnProps): BaseColumn { return BaseDateTimeColumn( "date", props, "YYYY-MM-DD", 1, "date", (date: Date): string => { // Only return the date part of the ISO string: return date.toISOString().split("T")[0] } ) } DateColumn.isEditableType = true