Skip to content

Commit 880490b

Browse files
committed
Fix utc timezone cross compatibility
1 parent 5ce5bca commit 880490b

File tree

4 files changed

+57
-49
lines changed

4 files changed

+57
-49
lines changed

.github/workflows/build-and-release.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,6 @@ jobs:
155155
cpack -C Release -G RPM
156156
cpack -C Release -G TGZ
157157
158-
- name: List artifacts
159-
run: Get-ChildItem ${{ github.workspace }}/build/ -Force | Format-List
160-
161158
- name: Upload artifacts
162159
uses: actions/upload-artifact@v4
163160
with:

include/QTradingView/renderer/AxisRenderer.h

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
#include <QPainter>
2727
#include <QRectF>
2828
#include <QColor>
29-
#include <memory>
3029
#include <QDateTime>
3130

3231
#include "QTradingView/ViewPort.h"
@@ -124,16 +123,14 @@ class QTRADINGVIEW_EXPORT AxisRenderer
124123
* @param series The data series being displayed.
125124
* @return A vector of TimeLabel structures containing label information.
126125
*/
127-
std::vector<TimeLabel> calculateXAxisLabels(const ViewPort& viewport, const Series* series) const;
126+
static std::vector<TimeLabel> calculateXAxisLabels(const ViewPort& viewport, const Series* series);
128127

129128
private:
130129
QColor m_textColor;
131130
QColor m_tickColor;
132131
QColor m_borderColor;
133132
QColor m_backgroundColor;
134133
int m_axisWidth;
135-
136-
QString formatYAxisLabel(double value, double minValue, double maxValue, int availableWidth, const QFont& font) const;
137134
};
138135

139136
} // namespace QTradingView

src/renderer/AxisRenderer.cpp

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
namespace {
2727
// Constants for time calculations (milliseconds)
2828
constexpr qint64 MS_PER_DAY = 86400000LL;
29-
constexpr qint64 MS_PER_HOUR = 3600000LL;
3029

3130
// Cache for date components to avoid repeated QDateTime creation
3231
struct DateComponents {
@@ -61,8 +60,8 @@ namespace {
6160
return {year, month, day};
6261
}
6362

64-
// Fast check if day matches specific day of month
65-
inline bool isDayOfMonth(qint64 timestampMs, int targetDay) {
63+
// Fast check if a timestamp is the first day of a month
64+
inline bool isFirstOfMonth(qint64 timestampMs) {
6665
qint64 days = timestampMs / MS_PER_DAY;
6766
qint64 z = days + 719468;
6867
qint64 era = (z >= 0 ? z : z - 146096) / 146097;
@@ -71,12 +70,15 @@ namespace {
7170
qint64 doy = doe - (365*yoe + yoe/4 - yoe/100);
7271
qint64 mp = (5*doy + 2)/153;
7372
int day = static_cast<int>(doy - (153*mp+2)/5 + 1);
74-
return day == targetDay;
73+
return day == 1;
7574
}
7675

7776
// Cache QTimeZone::utc() result
78-
static const QTimeZone& utcZone() {
79-
static const QTimeZone tz = QTimeZone::UTC;
77+
const QTimeZone& utcZone() {
78+
// Construct from the "UTC" ID which works across Qt versions and
79+
// platforms (avoids referencing QTimeZone::UTC or QTimeZone::utc()
80+
// directly, which differ between builds).
81+
static const QTimeZone tz = QTimeZone(QByteArrayLiteral("UTC"));
8082
return tz;
8183
}
8284

@@ -87,6 +89,35 @@ namespace {
8789
dt.setTimeZone(utcZone());
8890
return dt.toString(format);
8991
}
92+
93+
// Format Y-axis label (translation-unit helper)
94+
static QString formatYAxisLabel(double value, double minValue, double maxValue, int availableWidth,
95+
const QFont &font) {
96+
double range = maxValue - minValue;
97+
98+
if (std::abs(value) >= 1000000) {
99+
return QString::number(value / 1000000.0, 'f', 1) + "M";
100+
} else if (std::abs(value) >= 1000) {
101+
return QString::number(value / 1000.0, 'f', 1) + "K";
102+
}
103+
104+
int precision = 2;
105+
if (range < 1.0) precision = 4;
106+
else if (range < 10.0) precision = 3;
107+
108+
QString label = QString::number(value, 'f', precision);
109+
110+
QFontMetrics fm(font);
111+
int textWidth = fm.horizontalAdvance(label);
112+
113+
while (textWidth > availableWidth && precision > 0) {
114+
precision--;
115+
label = QString::number(value, 'f', precision);
116+
textWidth = fm.horizontalAdvance(label);
117+
}
118+
119+
return label;
120+
}
90121
}
91122

92123
namespace QTradingView {
@@ -170,7 +201,6 @@ void AxisRenderer::drawYAxis(QPainter* painter, const QRectF& leftAxisRect,
170201
painter->fillRect(leftAxisRect, m_backgroundColor);
171202
painter->fillRect(rightAxisRect, m_backgroundColor);
172203

173-
QRectF paneRect = pane->rect();
174204
double minValue = pane->minValue();
175205
double maxValue = pane->maxValue();
176206

@@ -227,7 +257,7 @@ void AxisRenderer::drawYAxis(QPainter* painter, const QRectF& leftAxisRect,
227257
}
228258

229259
// TODO: We assume dataProvider provides daily data. Adjust logic for different timeframes if needed.
230-
std::vector<TimeLabel> AxisRenderer::calculateXAxisLabels(const ViewPort& viewport, const Series* series) const {
260+
std::vector<TimeLabel> AxisRenderer::calculateXAxisLabels(const ViewPort& viewport, const Series* series) {
231261
std::vector<TimeLabel> labels;
232262
if (!series || series->dataCount() == 0) {
233263
return labels; // No data, no labels
@@ -279,7 +309,7 @@ std::vector<TimeLabel> AxisRenderer::calculateXAxisLabels(const ViewPort& viewpo
279309
}
280310
} else if (visibleCount <= 90) {
281311
// Show month starts
282-
if (isDayOfMonth(timestamp, 1)) {
312+
if (isFirstOfMonth(timestamp)) {
283313
label = formatDate(timestamp, "MMM");
284314
shouldLabel = true;
285315
}
@@ -321,31 +351,4 @@ std::vector<TimeLabel> AxisRenderer::calculateXAxisLabels(const ViewPort& viewpo
321351
return labels;
322352
}
323353

324-
QString AxisRenderer::formatYAxisLabel(double value, double minValue, double maxValue, int availableWidth,
325-
const QFont &font) const {
326-
double range = maxValue - minValue;
327-
328-
if (std::abs(value) >= 1000000) {
329-
return QString::number(value / 1000000.0, 'f', 1) + "M";
330-
} else if (std::abs(value) >= 1000) {
331-
return QString::number(value / 1000.0, 'f', 1) + "K";
332-
}
333-
334-
int precision = 2;
335-
if (range < 1.0) precision = 4;
336-
else if (range < 10.0) precision = 3;
337-
338-
QString label = QString::number(value, 'f', precision);
339-
340-
QFontMetrics fm(font);
341-
int textWidth = fm.horizontalAdvance(label);
342-
343-
while (textWidth > availableWidth && precision > 0) {
344-
precision--;
345-
label = QString::number(value, 'f', precision);
346-
textWidth = fm.horizontalAdvance(label);
347-
}
348-
349-
return label;
350-
}
351354
} // namespace QTradingView

src/renderer/CrosshairRenderer.cpp

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@
2828
#include <QDateTime>
2929
#include <QTimeZone>
3030

31+
namespace {
32+
// Portable UTC QTimeZone helper
33+
const QTimeZone& utcZone() {
34+
static const QTimeZone tz = QTimeZone(QByteArrayLiteral("UTC"));
35+
return tz;
36+
}
37+
}
38+
3139
namespace QTradingView {
3240

3341
CrosshairRenderer::CrosshairRenderer()
@@ -94,12 +102,15 @@ void CrosshairRenderer::render(QPainter* painter, const QPointF& position, const
94102
if (series) {
95103
if (dataIndex >= 0 && dataIndex < series->dataCount()) {
96104
qint64 timestamp = series->timestampAt(dataIndex);
97-
QString timeStr = QDateTime::fromMSecsSinceEpoch(timestamp, QTimeZone::UTC).toString("yyyy-MM-dd");
98-
// Use the provided xAxisY position if valid, otherwise use pane bottom
99-
double labelY = (xAxisY >= 0) ? xAxisY : paneRect.bottom();
100-
drawTimeLabel(painter, snappedX, timeStr, paneRect, labelY);
101-
}
102-
}
105+
QDateTime dt;
106+
dt.setMSecsSinceEpoch(timestamp);
107+
dt.setTimeZone(utcZone());
108+
QString timeStr = dt.toString("yyyy-MM-dd");
109+
// Use the provided xAxisY position if valid, otherwise use pane bottom
110+
double labelY = (xAxisY >= 0) ? xAxisY : paneRect.bottom();
111+
drawTimeLabel(painter, snappedX, timeStr, paneRect, labelY);
112+
}
113+
}
103114

104115
painter->restore();
105116
}

0 commit comments

Comments
 (0)