Skip to content

Commit ed3d630

Browse files
authored
Merge pull request #67 from pineforge-4pass/fix/time-invalid-session-uses-chart-tz
Roll the daily boundary at chart/UTC for a 2-arg time() timezone-in-session-slot
2 parents a0e211a + 6446253 commit ed3d630

2 files changed

Lines changed: 30 additions & 19 deletions

File tree

src/session_time.cpp

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -420,12 +420,15 @@ bool pine_session_ispostmarket(const std::string& session,
420420

421421
// A 2-argument time()/time_close() call binds its second string to the
422422
// `session` parameter, but scripts commonly pass a TIMEZONE there — e.g.
423-
// `time("D", "America/New_York")` to detect a day-change in that zone.
424-
// TradingView honors this idiom. When no explicit timezone was supplied and
425-
// the session slot instead holds an unambiguous timezone (IANA "Area/Location"
426-
// or a GMT/UTC specifier), reinterpret it as the timezone with no session
427-
// filter. A real session window ("0930-1600") never contains '/' nor starts
428-
// with GMT/UTC, and a 3-arg call supplies tz_in, so neither is affected.
423+
// `time("D", "America/New_York")`. TradingView does NOT reinterpret that
424+
// string as the timezone: a value that is not a valid session spec (IANA
425+
// "Area/Location" or a GMT/UTC specifier) is an invalid session that TV
426+
// ignores entirely, rolling the daily boundary at the chart/exchange
427+
// (UTC in the engine's model) timezone — NOT at the string's tz. We detect
428+
// such a string here only so we can drop it (no session filter, tz left at
429+
// the chart/UTC default). A real session window ("0930-1600") never contains
430+
// '/' nor starts with GMT/UTC, and a 3-arg call supplies tz_in, so neither is
431+
// affected.
429432
static bool session_arg_is_timezone(const std::string& s) {
430433
if (s.empty())
431434
return false;
@@ -436,16 +439,19 @@ static bool session_arg_is_timezone(const std::string& s) {
436439
return false;
437440
}
438441

439-
// Resolve the (session, tz) pair for a time()/time_close() call, applying the
440-
// 2-arg timezone-in-session-slot reinterpretation above.
442+
// Resolve the (session, tz) pair for a time()/time_close() call. When a 2-arg
443+
// call passes a timezone-looking string in the session slot (and no explicit
444+
// tz), TV treats it as an invalid session and ignores it: drop the session
445+
// (no filter) but leave tz at the chart/UTC default — do NOT adopt the string
446+
// as the timezone. The daily boundary then rolls at the chart/exchange (UTC)
447+
// timezone, matching TradingView.
441448
static void resolve_session_tz(const std::string& session,
442449
const std::string& tz_in,
443450
std::string& sess_out,
444451
std::string& tz_out) {
445452
sess_out = session;
446453
tz_out = tz_in;
447454
if (tz_out.empty() && session_arg_is_timezone(sess_out)) {
448-
tz_out = sess_out;
449455
sess_out.clear();
450456
}
451457
if (tz_out.empty())

tests/test_session_time.cpp

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,23 +63,28 @@ static void test_time_close_hourly() {
6363

6464
static void test_time_tz_in_session_slot_equiv() {
6565
std::printf("test_time_tz_in_session_slot_equiv\n");
66-
// time("D", "America/New_York") binds the tz into the session slot. The
67-
// engine must reinterpret it as the timezone (not a session) — previously
68-
// it treated it as a session, matched no HHMM window, and returned na.
66+
// time("D", "America/New_York") binds the tz into the session slot. That is
67+
// an invalid session, which TV ignores entirely — it does NOT adopt the
68+
// string as the timezone. The daily boundary rolls at the chart/exchange
69+
// (UTC) timezone, exactly as a plain time("D") does. It must not be na, and
70+
// must NOT match the 3-arg explicit-tz form (which still rolls at NY).
6971
int64_t bar = 1775572200000LL; // 2026-04-07 14:30 UTC = 10:30 NY (EDT)
7072
int64_t two_arg = pine_time(bar, "D", "America/New_York", "", "15");
71-
int64_t three_arg = pine_time(bar, "D", "", "America/New_York", "15");
72-
CHECK(!is_na(two_arg)); // was na for every bar (the bug)
73-
CHECK(two_arg == three_arg); // reinterpreted identically to the 3-arg form
73+
int64_t plain = pine_time(bar, "D", "", "", "15"); // time("D") → UTC/chart
74+
int64_t three_arg = pine_time(bar, "D", "", "America/New_York", "15"); // explicit tz → NY
75+
CHECK(!is_na(two_arg)); // was na before PR#66; still not na
76+
CHECK(two_arg == plain); // invalid session tz-string ignored → rolls at UTC/chart
77+
CHECK(two_arg != three_arg); // explicit-tz 3-arg form still uses the given tz
7478
}
7579

7680
static void test_time_tz_in_session_daily_change() {
7781
std::printf("test_time_tz_in_session_daily_change\n");
78-
// Bars in the same NY-day share the daily time; the next NY-day differs —
82+
// The tz-string session is ignored, so the daily boundary rolls at UTC.
83+
// Bars in the same UTC-day share the daily time; the next UTC-day differs —
7984
// this is what makes ta.change(time("D", tz)) fire once per day.
80-
int64_t bar_a = 1775572200000LL; // 2026-04-07 10:30 NY
81-
int64_t bar_b = bar_a + 3600000LL; // +1h, same NY-day
82-
int64_t bar_next = bar_a + 24LL * 3600000LL; // +24h, next NY-day
85+
int64_t bar_a = 1775572200000LL; // 2026-04-07 14:30 UTC
86+
int64_t bar_b = bar_a + 3600000LL; // +1h → 15:30 UTC, same UTC-day
87+
int64_t bar_next = bar_a + 24LL * 3600000LL; // +24h, next UTC-day
8388
int64_t ta = pine_time(bar_a, "D", "America/New_York", "", "15");
8489
int64_t tb = pine_time(bar_b, "D", "America/New_York", "", "15");
8590
int64_t tn = pine_time(bar_next, "D", "America/New_York", "", "15");

0 commit comments

Comments
 (0)