From ef9aa3f53a3dab76731a7bb635b7a81d84b0c2b8 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Thu, 25 Jul 2024 09:51:05 -0700 Subject: [PATCH 1/8] add time picker --- panel/tests/widgets/test_input.py | 20 +++++++++++-- panel/widgets/__init__.py | 2 ++ panel/widgets/input.py | 50 +++++++++++++++++++++++++++++-- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/panel/tests/widgets/test_input.py b/panel/tests/widgets/test_input.py index a45e486a0d..76a1897cff 100644 --- a/panel/tests/widgets/test_input.py +++ b/panel/tests/widgets/test_input.py @@ -1,4 +1,4 @@ -from datetime import date, datetime +from datetime import date, datetime, time as dt_time from pathlib import Path import numpy as np @@ -10,7 +10,7 @@ from panel.widgets import ( ArrayInput, Checkbox, DatePicker, DateRangePicker, DatetimeInput, DatetimePicker, DatetimeRangeInput, DatetimeRangePicker, FileInput, - FloatInput, IntInput, LiteralInput, StaticText, TextInput, + FloatInput, IntInput, LiteralInput, StaticText, TextInput, TimePicker, ) @@ -185,6 +185,22 @@ def test_datetime_range_picker(document, comm): datetime_range_picker._process_events({'value': '2018-09-10 00:00:01'}) +def test_time_picker(document, comm): + time_picker = TimePicker(name='Time Picker', value=dt_time(hour=18), format='H:i K') + assert time_picker.value == dt_time(hour=18) + assert time_picker.format == 'H:i K' + assert time_picker.start is None + assert time_picker.end is None + + +def test_time_picker_str(document, comm): + time_picker = TimePicker(name='Time Picker', value="08:28", start='00:00', end='12:00') + assert time_picker.value == "08:28" + assert time_picker.format == 'H:i' + assert time_picker.start == "00:00" + assert time_picker.end == "12:00" + + def test_file_input(document, comm): file_input = FileInput(accept='.txt') diff --git a/panel/widgets/__init__.py b/panel/widgets/__init__.py index 5a8c08d459..02d8180038 100644 --- a/panel/widgets/__init__.py +++ b/panel/widgets/__init__.py @@ -47,6 +47,7 @@ DatetimeInput, DatetimePicker, DatetimeRangeInput, DatetimeRangePicker, FileDropper, FileInput, FloatInput, IntInput, LiteralInput, NumberInput, PasswordInput, Spinner, StaticText, Switch, TextAreaInput, TextInput, + TimePicker, ) from .misc import FileDownload, JSONEditor, VideoStream # noqa from .player import DiscretePlayer, Player # noqa @@ -136,6 +137,7 @@ "TextEditor", "TextInput", "TextToSpeech", + "TimePicker", "Toggle", "ToggleGroup", "ToggleIcon", diff --git a/panel/widgets/input.py b/panel/widgets/input.py index bed0f658b0..94f1b3df3f 100644 --- a/panel/widgets/input.py +++ b/panel/widgets/input.py @@ -8,7 +8,7 @@ import json from base64 import b64decode -from datetime import date, datetime +from datetime import date, datetime, time as dt_time from typing import ( TYPE_CHECKING, Any, ClassVar, Iterable, Mapping, Optional, ) @@ -22,7 +22,7 @@ DatePicker as _BkDatePicker, DateRangePicker as _BkDateRangePicker, Div as _BkDiv, FileInput as _BkFileInput, NumericInput as _BkNumericInput, PasswordInput as _BkPasswordInput, Spinner as _BkSpinner, - Switch as _BkSwitch, + Switch as _BkSwitch, TimePicker as _BkTimePicker, ) from bokeh.models.widgets.inputs import ClearInput from pyviz_comms import JupyterComm @@ -793,6 +793,52 @@ def _deserialize_value(self, value): return value +class TimePicker(Widget): + """ + The `TimePicker` allows selecting a `time` value using a text box + and a time-picking utility. + + Reference: https://panel.holoviz.org/reference/widgets/TimePicker.html + + :Example: + + >>> TimePicker( + ... value="12:59:31", start="09:00:00", end="18:00:00", name="Time" + ... ) + """ + + value = param.ClassSelector(default=None, class_=(dt_time, str), doc=""" + The current value""") + + start = param.ClassSelector(default=None, class_=(dt_time, str), doc=""" + Inclusive lower bound of the allowed time selection""") + + end = param.ClassSelector(default=None, class_=(dt_time, str), doc=""" + Inclusive upper bound of the allowed time selection""") + + format = param.String(default='H:i', doc=""" + Formatting specification for the display of the picked date. + + +---+------------------------------------+------------+ + | H | Hours (24 hours) | 00 to 23 | + | h | Hours | 1 to 12 | + | G | Hours, 2 digits with leading zeros | 1 to 12 | + | i | Minutes | 00 to 59 | + | S | Seconds, 2 digits | 00 to 59 | + | s | Seconds | 0, 1 to 59 | + | K | AM/PM | AM or PM | + +---+------------------------------------+------------+ + + See also https://flatpickr.js.org/formatting/#date-formatting-tokens. + """) + + _rename: ClassVar[Mapping[str, str | None]] = { + 'start': 'min_time', 'end': 'max_time', 'format': 'time_format' + } + + _widget_type: ClassVar[type[Model]] = _BkTimePicker + + class ColorPicker(Widget): """ The `ColorPicker` widget allows selecting a hexadecimal RGB color value From 1cbe46f83a9a73ca24017dcb9295beef53e494a2 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Thu, 25 Jul 2024 10:00:26 -0700 Subject: [PATCH 2/8] Add doc --- examples/reference/widgets/TimePicker.ipynb | 98 +++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 examples/reference/widgets/TimePicker.ipynb diff --git a/examples/reference/widgets/TimePicker.ipynb b/examples/reference/widgets/TimePicker.ipynb new file mode 100644 index 0000000000..fa5ba57b5c --- /dev/null +++ b/examples/reference/widgets/TimePicker.ipynb @@ -0,0 +1,98 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import datetime as dt\n", + "import panel as pn\n", + "\n", + "pn.extension()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The ``TimePicker`` widget allows entering a time value as text or `datetime.time`. \n", + "\n", + "Discover more on using widgets to add interactivity to your applications in the [how-to guides on interactivity](../how_to/interactivity/index.md). Alternatively, learn [how to set up callbacks and (JS-)links between parameters](../../how_to/links/index.md) or [how to use them as part of declarative UIs with Param](../../how_to/param/index.html).\n", + "\n", + "#### Parameters:\n", + "\n", + "For details on other options for customizing the component see the [layout](../../how_to/layout/index.md) and [styling](../../how_to/styling/index.md) how-to guides.\n", + "\n", + "##### Core\n", + "\n", + "* **``value``** (str | datetime.time): The current value\n", + "* **``start``** (str | datetime.time): Inclusive lower bound of the allowed time selection\n", + "* **``end``** (str | datetime.time): Inclusive upper bound of the allowed time selection\n", + "* **``format``** (str): Formatting specification for the display of the picked date.\n", + "\n", + " ```\n", + " +---+------------------------------------+------------+\n", + " | H | Hours (24 hours) | 00 to 23 |\n", + " | h | Hours | 1 to 12 |\n", + " | G | Hours, 2 digits with leading zeros | 1 to 12 |\n", + " | i | Minutes | 00 to 59 |\n", + " | S | Seconds, 2 digits | 00 to 59 |\n", + " | s | Seconds | 0, 1 to 59 |\n", + " | K | AM/PM | AM or PM |\n", + " +---+------------------------------------+------------+\n", + " ```\n", + " See also https://flatpickr.js.org/formatting/#date-formatting-tokens.\n", + "\n", + "##### Display\n", + "\n", + "* **``disabled``** (boolean): Whether the widget is editable\n", + "* **``name``** (str): The title of the widget\n", + "\n", + "___" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `TimePicker` widget allows selecting a time of day." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "time_picker = pn.widgets.TimePicker(name='Time Picker', value=dt.datetime.now().time(), format='H:i K')\n", + "time_picker" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Either `datetime.time` or `str` can be used as input and `TimePicker` can be bounded by a start and end time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "time_picker = pn.widgets.TimePicker(name='Time Picker', value=\"08:28\", start='00:00', end='12:00')\n", + "time_picker" + ] + } + ], + "metadata": { + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 0a6924071f78bc9147bd07daa4f6af7b2da6c8cf Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Thu, 25 Jul 2024 11:08:55 -0700 Subject: [PATCH 3/8] add more params --- examples/reference/widgets/TimePicker.ipynb | 9 +++++- panel/widgets/input.py | 36 ++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/examples/reference/widgets/TimePicker.ipynb b/examples/reference/widgets/TimePicker.ipynb index fa5ba57b5c..fccc736411 100644 --- a/examples/reference/widgets/TimePicker.ipynb +++ b/examples/reference/widgets/TimePicker.ipynb @@ -29,7 +29,6 @@ "* **``value``** (str | datetime.time): The current value\n", "* **``start``** (str | datetime.time): Inclusive lower bound of the allowed time selection\n", "* **``end``** (str | datetime.time): Inclusive upper bound of the allowed time selection\n", - "* **``format``** (str): Formatting specification for the display of the picked date.\n", "\n", " ```\n", " +---+------------------------------------+------------+\n", @@ -44,10 +43,18 @@ " ```\n", " See also https://flatpickr.js.org/formatting/#date-formatting-tokens.\n", "\n", + "\n", + "\n", "##### Display\n", "\n", "* **``disabled``** (boolean): Whether the widget is editable\n", "* **``name``** (str): The title of the widget\n", + "* **``format``** (str): Formatting specification for the display of the picked date.\n", + "* **``hour_increment``** (int): Defines the granularity of hour value increments in the UI. Default is 1.\n", + "* **``minute_increment``** (int): Defines the granularity of minute value increments in the UI. Default is 1.\n", + "* **``second_increment``** (int): Defines the granularity of second value increments in the UI. Default is 1.\n", + "* **``seconds``** (bool): Allows to select seconds. By default, only hours and minutes are selectable, and AM/PM depending on the `clock` option. Default is False.\n", + "* **``military_time``** (bool): Whether to display time in 24-hour format. Default is True.\n", "\n", "___" ] diff --git a/panel/widgets/input.py b/panel/widgets/input.py index 94f1b3df3f..b4d54e8a71 100644 --- a/panel/widgets/input.py +++ b/panel/widgets/input.py @@ -793,7 +793,41 @@ def _deserialize_value(self, value): return value -class TimePicker(Widget): +class _TimeCommon(Widget): + + hour_increment = param.Integer(default=1, doc=""" + Defines the granularity of hour value increments in the UI. + """) + + minute_increment = param.Integer(default=1, doc=""" + Defines the granularity of minute value increments in the UI. + """) + + second_increment = param.Integer(default=1, doc=""" + Defines the granularity of second value increments in the UI. + """) + + seconds = param.Boolean(default=False, doc=""" + Allows to select seconds. By default only hours and minutes are + selectable, and AM/PM depending on the `clock` option. + """) + + military_time = param.Boolean(default=True, doc=""" + Whether to display time in 24 hour format. + """) + + _rename: ClassVar[Mapping[str, str | None]] = { + 'military_time': 'clock' + } + + __abstract = True + + def _process_param_change(self, msg): + msg["clock"] = "24h" if msg.pop('military_time', None) else "12h" + return super()._process_param_change(msg) + + +class TimePicker(_TimeCommon): """ The `TimePicker` allows selecting a `time` value using a text box and a time-picking utility. From 3aa91520a688d82a176e30fa6a5e55f5e6b743db Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Mon, 5 Aug 2024 15:59:08 -0700 Subject: [PATCH 4/8] add custom version --- panel/models/__init__.py | 1 + panel/models/index.ts | 1 + panel/models/time_picker.py | 7 +++++ panel/models/time_picker.ts | 52 +++++++++++++++++++++++++++++++++++++ panel/widgets/input.py | 4 +-- 5 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 panel/models/time_picker.py create mode 100644 panel/models/time_picker.ts diff --git a/panel/models/__init__.py b/panel/models/__init__.py index c00045f5bd..a5616ffbfb 100644 --- a/panel/models/__init__.py +++ b/panel/models/__init__.py @@ -15,6 +15,7 @@ from .markup import HTML, JSON, PDF # noqa from .reactive_html import ReactiveHTML # noqa from .state import State # noqa +from .time_picker import TimePicker # noqa from .trend import TrendIndicator # noqa from .widgets import ( # noqa Audio, Button, CheckboxButtonGroup, CustomMultiSelect, CustomSelect, diff --git a/panel/models/index.ts b/panel/models/index.ts index f6e656899c..73b1da3770 100644 --- a/panel/models/index.ts +++ b/panel/models/index.ts @@ -44,6 +44,7 @@ export {Terminal} from "./terminal" export {TextAreaInput} from "./textarea_input" export {TextInput} from "./text_input" export {TextToSpeech} from "./text_to_speech" +export {TimePicker} from "./time_picker" export {ToggleIcon} from "./toggle_icon" export {TooltipIcon} from "./tooltip_icon" export {TrendIndicator} from "./trend" diff --git a/panel/models/time_picker.py b/panel/models/time_picker.py new file mode 100644 index 0000000000..d211dc5e61 --- /dev/null +++ b/panel/models/time_picker.py @@ -0,0 +1,7 @@ +from bokeh.models import TimePicker as BkTimePicker + + +class TimePicker(BkTimePicker): + """ + A custom Panel version of the Bokeh TimePicker model which fixes timezones. + """ diff --git a/panel/models/time_picker.ts b/panel/models/time_picker.ts new file mode 100644 index 0000000000..8a5da87eaa --- /dev/null +++ b/panel/models/time_picker.ts @@ -0,0 +1,52 @@ +import {TimePicker as BkTimePicker, TimePickerView as BkTimePickerView} from "@bokehjs/models/widgets/time_picker" +import type * as p from "@bokehjs/core/properties" + + + +export class TimePickerView extends BkTimePickerView { + declare model: TimePicker + + // Override the render method or any method where time rendering is done + override render(): void { + // Fix time zone difference; add back 8 hours + const date = this.model.value ? new Date(this.model.value) : new Date(); + date.setHours(date.getHours() + 8); + + console.log(date); + + // get type of date + console.log(this.model.value) + console.log("Type of Date:", typeof this.model.value); + + console.log("Converted Date:", date.toISOString()); + console.log("Converted Time:", date.getTime()); + this.model.value = 72000000 + + super.render(); + } + } + + export namespace TimePicker { + export type Attrs = p.AttrsOf + export type Props = BkTimePicker.Props & { + } + } + + export interface TimePicker extends TimePicker.Attrs { } + + export class TimePicker extends BkTimePicker { + declare properties: TimePicker.Props + + constructor(attrs?: Partial) { + super(attrs) + } + + static override __module__ = "panel.models.time_picker" + + static { + this.prototype.default_view = TimePickerView + + this.define(({}) => ({ + })) + } + } diff --git a/panel/widgets/input.py b/panel/widgets/input.py index b4d54e8a71..3b6fd91a61 100644 --- a/panel/widgets/input.py +++ b/panel/widgets/input.py @@ -22,7 +22,7 @@ DatePicker as _BkDatePicker, DateRangePicker as _BkDateRangePicker, Div as _BkDiv, FileInput as _BkFileInput, NumericInput as _BkNumericInput, PasswordInput as _BkPasswordInput, Spinner as _BkSpinner, - Switch as _BkSwitch, TimePicker as _BkTimePicker, + Switch as _BkSwitch, ) from bokeh.models.widgets.inputs import ClearInput from pyviz_comms import JupyterComm @@ -31,7 +31,7 @@ from ..layout import Column, Panel from ..models import ( DatetimePicker as _bkDatetimePicker, TextAreaInput as _bkTextAreaInput, - TextInput as _BkTextInput, + TextInput as _BkTextInput, TimePicker as _BkTimePicker, ) from ..util import lazy_load, param_reprs, try_datetime64_to_datetime from .base import CompositeWidget, Widget From a6e2df79985dec141ad0d2669d1ed94720642339 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Tue, 6 Aug 2024 14:33:36 -0700 Subject: [PATCH 5/8] cover all cases --- panel/models/time_picker.ts | 82 +++++++++++++--------- panel/tests/ui/widgets/test_time_picker.py | 40 +++++++++++ 2 files changed, 90 insertions(+), 32 deletions(-) create mode 100644 panel/tests/ui/widgets/test_time_picker.py diff --git a/panel/models/time_picker.ts b/panel/models/time_picker.ts index 8a5da87eaa..c12250f12e 100644 --- a/panel/models/time_picker.ts +++ b/panel/models/time_picker.ts @@ -1,52 +1,70 @@ -import {TimePicker as BkTimePicker, TimePickerView as BkTimePickerView} from "@bokehjs/models/widgets/time_picker" +import { TimePicker as BkTimePicker, TimePickerView as BkTimePickerView } from "@bokehjs/models/widgets/time_picker" import type * as p from "@bokehjs/core/properties" +import type flatpickr from "flatpickr" export class TimePickerView extends BkTimePickerView { - declare model: TimePicker + declare model: TimePicker - // Override the render method or any method where time rendering is done - override render(): void { - // Fix time zone difference; add back 8 hours - const date = this.model.value ? new Date(this.model.value) : new Date(); - date.setHours(date.getHours() + 8); + private _offset_time(value: string | number): number { + const baseDate = new Date(value) + const timeZoneOffset = baseDate.getTimezoneOffset() * 60 * 1000 + return baseDate.getTime() + timeZoneOffset + } - console.log(date); + private _setDate(date: string | number): void { + date = this._offset_time(date) + this.picker.setDate(date) + } - // get type of date - console.log(this.model.value) - console.log("Type of Date:", typeof this.model.value); + protected override get flatpickr_options(): flatpickr.Options.Options { + // on init + const options = super.flatpickr_options + if (options.defaultDate != null) + options.defaultDate = this._offset_time(options.defaultDate as string) + return options + } - console.log("Converted Date:", date.toISOString()); - console.log("Converted Time:", date.getTime()); - this.model.value = 72000000 + override connect_signals(): void { + super.connect_signals() - super.render(); - } + const { value } = this.model.properties + this.connect(value.change, () => { + const { value } = this.model + if (value != null && typeof value === "number") { + // we need to handle it when programmatically changed thru Python, e.g. + // time_picker.value = "4:08" or time_picker.value = "datetime.time(4, 8)" + // else, when changed in the UI, e.g. by typing in the input field + // no special handling is needed + this._setDate(value) + } + }) } - export namespace TimePicker { - export type Attrs = p.AttrsOf - export type Props = BkTimePicker.Props & { - } +} + +export namespace TimePicker { + export type Attrs = p.AttrsOf + export type Props = BkTimePicker.Props & { } +} - export interface TimePicker extends TimePicker.Attrs { } +export interface TimePicker extends TimePicker.Attrs { } - export class TimePicker extends BkTimePicker { - declare properties: TimePicker.Props +export class TimePicker extends BkTimePicker { + declare properties: TimePicker.Props - constructor(attrs?: Partial) { - super(attrs) - } + constructor(attrs?: Partial) { + super(attrs) + } - static override __module__ = "panel.models.time_picker" + static override __module__ = "panel.models.time_picker" - static { - this.prototype.default_view = TimePickerView + static { + this.prototype.default_view = TimePickerView - this.define(({}) => ({ - })) - } + this.define(({ }) => ({ + })) } +} diff --git a/panel/tests/ui/widgets/test_time_picker.py b/panel/tests/ui/widgets/test_time_picker.py new file mode 100644 index 0000000000..30885d9023 --- /dev/null +++ b/panel/tests/ui/widgets/test_time_picker.py @@ -0,0 +1,40 @@ +import datetime + +import pytest + +from panel.tests.util import serve_component, wait_until +from panel.widgets import TimePicker + +pytestmark = pytest.mark.ui + + +def test_time_picker(page): + + time_picker = TimePicker(value="18:08", format="H:i") + + serve_component(page, time_picker) + + # test init corrected timezone + locator = page.locator("#input") + assert locator.get_attribute("value") == "18:08:00" + + # test UI change + locator = page.locator("input.bk-input.form-control.input") + locator.click() + wait_until(lambda: page.locator("input.numInput.flatpickr-hour").is_visible()) + locator = page.locator("input.numInput.flatpickr-hour") + locator.press("ArrowDown") + locator.press("Enter") + wait_until(lambda: time_picker.value == datetime.time(17, 8)) + + # test str value change + time_picker.value = "04:08" + wait_until(lambda: time_picker.value == "04:08") + locator = page.locator("#input") + assert locator.get_attribute("value") == "04:08:00" + + # test datetime.time value change + time_picker.value = datetime.time(18, 8) + wait_until(lambda: time_picker.value == datetime.time(18, 8)) + locator = page.locator("#input") + assert locator.get_attribute("value") == "18:08:00" From 873cfb8f160b1b63dde5689ca22e4bb5168fe6c3 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Tue, 6 Aug 2024 14:35:42 -0700 Subject: [PATCH 6/8] pre-commit --- panel/models/time_picker.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/panel/models/time_picker.ts b/panel/models/time_picker.ts index c12250f12e..3ee91e473c 100644 --- a/panel/models/time_picker.ts +++ b/panel/models/time_picker.ts @@ -1,9 +1,7 @@ -import { TimePicker as BkTimePicker, TimePickerView as BkTimePickerView } from "@bokehjs/models/widgets/time_picker" +import {TimePicker as BkTimePicker, TimePickerView as BkTimePickerView} from "@bokehjs/models/widgets/time_picker" import type * as p from "@bokehjs/core/properties" import type flatpickr from "flatpickr" - - export class TimePickerView extends BkTimePickerView { declare model: TimePicker @@ -21,17 +19,16 @@ export class TimePickerView extends BkTimePickerView { protected override get flatpickr_options(): flatpickr.Options.Options { // on init const options = super.flatpickr_options - if (options.defaultDate != null) - options.defaultDate = this._offset_time(options.defaultDate as string) + if (options.defaultDate != null) { options.defaultDate = this._offset_time(options.defaultDate as string) } return options } override connect_signals(): void { super.connect_signals() - const { value } = this.model.properties + const {value} = this.model.properties this.connect(value.change, () => { - const { value } = this.model + const {value} = this.model if (value != null && typeof value === "number") { // we need to handle it when programmatically changed thru Python, e.g. // time_picker.value = "4:08" or time_picker.value = "datetime.time(4, 8)" From 3dec99fad1bfbf6cb07dd6b57261d045b96aff8c Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Tue, 6 Aug 2024 14:43:35 -0700 Subject: [PATCH 7/8] change to clock --- examples/reference/widgets/TimePicker.ipynb | 3 +-- panel/widgets/input.py | 13 ++----------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/examples/reference/widgets/TimePicker.ipynb b/examples/reference/widgets/TimePicker.ipynb index fccc736411..b4d0f66a14 100644 --- a/examples/reference/widgets/TimePicker.ipynb +++ b/examples/reference/widgets/TimePicker.ipynb @@ -54,8 +54,7 @@ "* **``minute_increment``** (int): Defines the granularity of minute value increments in the UI. Default is 1.\n", "* **``second_increment``** (int): Defines the granularity of second value increments in the UI. Default is 1.\n", "* **``seconds``** (bool): Allows to select seconds. By default, only hours and minutes are selectable, and AM/PM depending on the `clock` option. Default is False.\n", - "* **``military_time``** (bool): Whether to display time in 24-hour format. Default is True.\n", - "\n", + "* **``clock``** (bool): Whether to use 12 hour or 24 hour clock.\n", "___" ] }, diff --git a/panel/widgets/input.py b/panel/widgets/input.py index 3b6fd91a61..b461c45fe3 100644 --- a/panel/widgets/input.py +++ b/panel/widgets/input.py @@ -812,20 +812,11 @@ class _TimeCommon(Widget): selectable, and AM/PM depending on the `clock` option. """) - military_time = param.Boolean(default=True, doc=""" - Whether to display time in 24 hour format. - """) - - _rename: ClassVar[Mapping[str, str | None]] = { - 'military_time': 'clock' - } + clock = param.String(default='12h', doc=""" + Whether to use 12 hour or 24 hour clock.""") __abstract = True - def _process_param_change(self, msg): - msg["clock"] = "24h" if msg.pop('military_time', None) else "12h" - return super()._process_param_change(msg) - class TimePicker(_TimeCommon): """ From 0427bfb8c13e8c39262dd41be7436e32ac0eb933 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Tue, 20 Aug 2024 11:29:45 -0700 Subject: [PATCH 8/8] add tz test --- panel/tests/ui/widgets/test_time_picker.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/panel/tests/ui/widgets/test_time_picker.py b/panel/tests/ui/widgets/test_time_picker.py index 30885d9023..b8688958ae 100644 --- a/panel/tests/ui/widgets/test_time_picker.py +++ b/panel/tests/ui/widgets/test_time_picker.py @@ -38,3 +38,21 @@ def test_time_picker(page): wait_until(lambda: time_picker.value == datetime.time(18, 8)) locator = page.locator("#input") assert locator.get_attribute("value") == "18:08:00" + + +@pytest.mark.parametrize("timezone_id", [ + "America/New_York", + "Europe/Berlin", + "UTC", +]) +def test_time_picker_timezone_different(page, timezone_id): + context = page.context.browser.new_context( + timezone_id=timezone_id, + ) + page = context.new_page() + + time_picker = TimePicker(value="18:08", format="H:i") + serve_component(page, time_picker) + + locator = page.locator("#input") + assert locator.get_attribute("value") == "18:08:00"