Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import io.github.inductiveautomation.kindling.utils.PathSerializer
import io.github.inductiveautomation.kindling.utils.PathSerializer.serializedForm
import io.github.inductiveautomation.kindling.utils.ThemeSerializer
import io.github.inductiveautomation.kindling.utils.ToolSerializer
import io.github.inductiveautomation.kindling.utils.ZoneIdSerializer
import io.github.inductiveautomation.kindling.utils.configureCellRenderer
import io.github.inductiveautomation.kindling.utils.debounce
import io.github.inductiveautomation.kindling.utils.render
Expand All @@ -28,6 +29,8 @@ import java.awt.Image
import java.net.URI
import java.nio.charset.Charset
import java.nio.file.Path
import java.time.ZoneId
import java.time.zone.ZoneRulesProvider
import java.util.Vector
import javax.swing.JComboBox
import javax.swing.JSpinner
Expand Down Expand Up @@ -153,6 +156,21 @@ data object Kindling {
},
)

val SelectedTimeZone = preference(
name = "Timezone",
description = "Timezone to use when displaying timestamps",
default = ZoneId.systemDefault(),
serializer = ZoneIdSerializer,
editor = {
JComboBox(Vector(ZoneRulesProvider.getAvailableZoneIds().sorted())).apply {
selectedItem = currentValue.id
addActionListener {
currentValue = ZoneId.of(selectedItem as String)
}
}
},
)

override val displayName: String = "General"
override val serialKey: String = "general"
override val preferences: List<Preference<*>> = listOf(
Expand All @@ -162,6 +180,7 @@ data object Kindling {
ShowLogTree,
UseHyperlinks,
HighlightByDefault,
SelectedTimeZone,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.github.inductiveautomation.kindling.core

import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.temporal.TemporalAccessor
import java.util.Date

object TimePreferences {
val SelectedTimeZone = Kindling.Preferences.General.SelectedTimeZone

private var formatter = createFormatter(SelectedTimeZone.currentValue)

init {
Kindling.Preferences.General.SelectedTimeZone.addChangeListener { newValue ->
formatter = createFormatter(newValue)
}
}
private val listeners = mutableListOf<() -> Unit>()

fun addChangeListener(listener: () -> Unit) {
listeners += listener
}

init {
Kindling.Preferences.General.SelectedTimeZone.addChangeListener { newValue ->
formatter = createFormatter(newValue)
listeners.forEach { it() } // notify listeners when timezone changes
}
}

private fun createFormatter(id: ZoneId): DateTimeFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss:SSS")
.withZone(id)

/**
* Format [time] using an internal [DateTimeFormatter] that is in [SelectedTimeZone] automatically.
*
* This function is overloaded to also accept [Date] types, including [java.sql.Date] and [java.sql.Timestamp].
* - [java.sql.Date] is converted via [toLocalDate] at the start of the day in the selected timezone.
* - [java.sql.Timestamp] and [java.util.Date] preserve full time-of-day precision.
*/

fun format(time: TemporalAccessor): String = formatter.format(time)

fun format(date: Date): String = when (date) {
is java.sql.Date -> formatter.format(date.toLocalDate().atStartOfDay(SelectedTimeZone.currentValue))
is java.sql.Timestamp -> formatter.format(date.toInstant())
else -> formatter.format(date.toInstant())
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.inductiveautomation.kindling.idb.metrics

import io.github.inductiveautomation.kindling.core.TimePreferences
import io.github.inductiveautomation.kindling.idb.metrics.MetricCard.Companion.MetricPresentation.Cpu
import io.github.inductiveautomation.kindling.idb.metrics.MetricCard.Companion.MetricPresentation.Default
import io.github.inductiveautomation.kindling.idb.metrics.MetricCard.Companion.MetricPresentation.Heap
Expand All @@ -17,7 +18,6 @@ import java.text.DecimalFormat
import java.text.FieldPosition
import java.text.NumberFormat
import java.text.ParsePosition
import java.text.SimpleDateFormat
import javax.swing.JLabel
import javax.swing.JPanel
import javax.swing.SwingConstants.CENTER
Expand Down Expand Up @@ -90,13 +90,18 @@ class MetricCard(val metric: Metric, data: List<MetricData>) : JPanel(MigLayout(
}

add(sparkLine, "span, w 300, h 170, pushx, growx")
add(JLabel("${DATE_FORMAT.format(minTimestamp)} - ${DATE_FORMAT.format(maxTimestamp)}", CENTER), "pushx, growx, span")

val timeLabel = JLabel("${TimePreferences.format(minTimestamp)} - ${TimePreferences.format(maxTimestamp)}", CENTER)
add(timeLabel, "pushx, growx, span")

TimePreferences.addChangeListener {
timeLabel.text = "${TimePreferences.format(minTimestamp)} - ${TimePreferences.format(maxTimestamp)}"
}

border = LineBorder(UIManager.getColor("Component.borderColor"), 3, true)
}

companion object {
val DATE_FORMAT = SimpleDateFormat("MM/dd/yy HH:mm:ss")

private val mbFormatter = DecimalFormat("0.0 'mB'")
private val heapFormatter = object : NumberFormat() {
Expand All @@ -117,7 +122,7 @@ class MetricCard(val metric: Metric, data: List<MetricData>) : JPanel(MigLayout(
},
true,
),
Default(NumberFormat.getInstance(), false)
Default(NumberFormat.getInstance(), false),
}

private val Metric.presentation: MetricPresentation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class MetricsView(connection: Connection) : ToolPanel("ins 0, fill, hidemode 3")
}
.executeQuery()
.toList { rs ->
MetricData(rs.getDouble(1), rs.getDate(2))
MetricData(rs.getDouble(1), rs.getTimestamp(2))
}

MetricCard(metric, metricData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.github.inductiveautomation.kindling.idb.metrics

import io.github.inductiveautomation.kindling.core.Kindling.Preferences.UI.Theme
import io.github.inductiveautomation.kindling.core.Theme.Companion.theme
import io.github.inductiveautomation.kindling.idb.metrics.MetricCard.Companion.DATE_FORMAT
import io.github.inductiveautomation.kindling.core.TimePreferences
import org.jfree.chart.ChartFactory
import org.jfree.chart.JFreeChart
import org.jfree.chart.axis.NumberAxis
Expand All @@ -11,44 +11,59 @@ import org.jfree.data.time.FixedMillisecond
import org.jfree.data.time.TimeSeries
import org.jfree.data.time.TimeSeriesCollection
import java.text.NumberFormat
import java.time.Instant

fun sparkline(data: List<MetricData>, formatter: NumberFormat): JFreeChart {
return ChartFactory.createTimeSeriesChart(
/* title = */ null,
/* timeAxisLabel = */ null,
/* valueAxisLabel = */ null,
/* dataset = */
TimeSeriesCollection(
TimeSeries("Series").apply {
for ((value, timestamp) in data) {
add(FixedMillisecond(timestamp), value, false)
}
},
),
/* legend = */ false,
/* tooltips = */ true,
/* urls = */ false,
).apply {
xyPlot.apply {
domainAxis.isPositiveArrowVisible = true
rangeAxis.apply {
isPositiveArrowVisible = true
(this as NumberAxis).numberFormatOverride = formatter
fun sparkline(data: List<MetricData>, formatter: NumberFormat): JFreeChart = ChartFactory.createTimeSeriesChart(
/* title = */
null,
/* timeAxisLabel = */
null,
/* valueAxisLabel = */
null,
/* dataset = */
TimeSeriesCollection(
TimeSeries("Series").apply {
for ((value, timestamp) in data) {
add(FixedMillisecond(timestamp), value, false)
}
},
),
/* legend = */
false,
/* tooltips = */
true,
/* urls = */
false,
).apply {
xyPlot.apply {
domainAxis.isPositiveArrowVisible = true
rangeAxis.apply {
isPositiveArrowVisible = true
(this as NumberAxis).numberFormatOverride = formatter
}
val updateTooltipGenerator = {
renderer.setDefaultToolTipGenerator { dataset, series, item ->
"${DATE_FORMAT.format(dataset.getXValue(series, item))} - ${formatter.format(dataset.getYValue(series, item))}"
val time = Instant.ofEpochMilli(dataset.getXValue(series, item).toLong())
"${TimePreferences.format(time)} - ${formatter.format(dataset.getYValue(series, item))}"
}
isDomainGridlinesVisible = false
isRangeGridlinesVisible = false
isOutlineVisible = false
}

padding = RectangleInsets(10.0, 10.0, 10.0, 10.0)
isBorderVisible = false
updateTooltipGenerator()

theme = Theme.currentValue
Theme.addChangeListener { newTheme ->
theme = newTheme
TimePreferences.addChangeListener {
updateTooltipGenerator()
}

isDomainGridlinesVisible = false
isRangeGridlinesVisible = false
isOutlineVisible = false
}

padding = RectangleInsets(10.0, 10.0, 10.0, 10.0)
isBorderVisible = false

theme = Theme.currentValue
Theme.addChangeListener { newTheme ->
theme = newTheme
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import io.github.inductiveautomation.kindling.core.Kindling.Preferences.Advanced
import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.ShowFullLoggerNames
import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.UseHyperlinks
import io.github.inductiveautomation.kindling.core.LinkHandlingStrategy
import io.github.inductiveautomation.kindling.core.TimePreferences
import io.github.inductiveautomation.kindling.core.TimePreferences.SelectedTimeZone
import io.github.inductiveautomation.kindling.core.ToolOpeningException
import io.github.inductiveautomation.kindling.core.ToolPanel
import io.github.inductiveautomation.kindling.utils.Action
Expand Down Expand Up @@ -313,7 +315,7 @@ sealed class LogPanel<T : LogEvent>(
table.selectionModel.updateDetails()
}

LogViewer.SelectedTimeZone.addChangeListener {
SelectedTimeZone.addChangeListener {
table.model.fireTableDataChanged()
}
}
Expand All @@ -332,8 +334,8 @@ sealed class LogPanel<T : LogEvent>(
.map { event ->
DetailEvent(
title = when (event) {
is SystemLogEvent -> "${LogViewer.format(event.timestamp)} ${event.thread}"
else -> LogViewer.format(event.timestamp)
is SystemLogEvent -> "${TimePreferences.format(event.timestamp)} ${event.thread}"
else -> TimePreferences.format(event.timestamp)
},
message = event.message,
body = event.stacktrace.map { element ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.inductiveautomation.kindling.log

import com.jidesoft.comparator.AlphanumComparator
import io.github.inductiveautomation.kindling.core.Kindling.Preferences.General.ShowFullLoggerNames
import io.github.inductiveautomation.kindling.core.TimePreferences
import io.github.inductiveautomation.kindling.utils.Column
import io.github.inductiveautomation.kindling.utils.ColumnList
import io.github.inductiveautomation.kindling.utils.FlatActionIcon
Expand Down Expand Up @@ -81,7 +82,7 @@ sealed class LogColumnList<T : LogEvent> : ColumnList<T>() {
minWidth = 155
maxWidth = 155
cellRenderer = DefaultTableRenderer {
(it as? Instant)?.let(LogViewer::format)
(it as? Instant)?.let(TimePreferences::format)
}
},
getValue = LogEvent::timestamp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import com.formdev.flatlaf.extras.components.FlatButton
import io.github.inductiveautomation.kindling.core.FilterChangeListener
import io.github.inductiveautomation.kindling.core.FilterPanel
import io.github.inductiveautomation.kindling.core.Kindling.Preferences.UI.Theme
import io.github.inductiveautomation.kindling.core.TimePreferences
import io.github.inductiveautomation.kindling.core.TimePreferences.SelectedTimeZone
import io.github.inductiveautomation.kindling.utils.Action
import io.github.inductiveautomation.kindling.utils.Column
import io.github.inductiveautomation.kindling.utils.ColumnList
Expand Down Expand Up @@ -264,12 +266,12 @@ internal class TimePanel<T : LogEvent>(
) {
if (column == WrapperLogColumns.Timestamp || column == SystemLogColumns.Timestamp) {
menu.add(
Action("Show only events after ${LogViewer.format(event.timestamp)}") {
Action("Show only events after ${TimePreferences.format(event.timestamp)}") {
startSelector.time = event.timestamp
},
)
menu.add(
Action("Show only events before ${LogViewer.format(event.timestamp)}") {
Action("Show only events before ${TimePreferences.format(event.timestamp)}") {
endSelector.time = event.timestamp
},
)
Expand All @@ -284,11 +286,11 @@ internal class TimePanel<T : LogEvent>(
}

private var JXDatePicker.localDate: LocalDate?
get() = date?.toInstant()?.let { LocalDate.ofInstant(it, LogViewer.SelectedTimeZone.currentValue) }
get() = date?.toInstant()?.let { LocalDate.ofInstant(it, SelectedTimeZone.currentValue) }
set(value) {
date =
value?.atStartOfDay()
?.atOffset(LogViewer.SelectedTimeZone.currentValue.rules.getOffset(value.atStartOfDay()))
?.atOffset(SelectedTimeZone.currentValue.rules.getOffset(value.atStartOfDay()))
?.toInstant()
.let(Date::from)
}
Expand All @@ -307,7 +309,7 @@ class DateTimeSelector(
}

private val initialZonedTime: ZonedDateTime
get() = defaultValue.atZone(LogViewer.SelectedTimeZone.currentValue)
get() = defaultValue.atZone(SelectedTimeZone.currentValue)

private val datePicker =
JXDatePicker().apply {
Expand Down Expand Up @@ -347,12 +349,12 @@ class DateTimeSelector(
ZonedDateTime.of(
localDate,
timeSelector.localTime,
LogViewer.SelectedTimeZone.currentValue,
SelectedTimeZone.currentValue,
).toInstant() ?: defaultValue
}
}
set(value) {
val zonedDateTime = value.atZone(LogViewer.SelectedTimeZone.currentValue)
val zonedDateTime = value.atZone(SelectedTimeZone.currentValue)
datePicker.localDate = zonedDateTime.toLocalDate()
timeSelector.localTime = zonedDateTime.toLocalTime()
}
Expand Down Expand Up @@ -511,10 +513,10 @@ private object DensityColumns : ColumnList<DenseTime>() {
get() {
if (!this::_formatter.isInitialized) {
_formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm")
.withZone(LogViewer.SelectedTimeZone.currentValue)
.withZone(SelectedTimeZone.currentValue)
}
if (_formatter.zone != LogViewer.SelectedTimeZone.currentValue) {
_formatter = _formatter.withZone(LogViewer.SelectedTimeZone.currentValue)
if (_formatter.zone != SelectedTimeZone.currentValue) {
_formatter = _formatter.withZone(SelectedTimeZone.currentValue)
}
return _formatter
}
Expand Down
Loading