Skip to content

Commit a0e211a

Browse files
authored
Merge pull request #66 from pineforge-4pass/fix/time-session-timezone-arg
Honor a timezone passed in the session slot of 2-arg time()/time_close()
2 parents d2ea2ce + 302a605 commit a0e211a

2 files changed

Lines changed: 90 additions & 4 deletions

File tree

src/session_time.cpp

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,40 @@ bool pine_session_ispostmarket(const std::string& session,
418418
// Public functions
419419
// =========================================================================
420420

421+
// A 2-argument time()/time_close() call binds its second string to the
422+
// `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.
429+
static bool session_arg_is_timezone(const std::string& s) {
430+
if (s.empty())
431+
return false;
432+
if (s.find('/') != std::string::npos) // IANA e.g. America/New_York
433+
return true;
434+
if (s.rfind("GMT", 0) == 0 || s.rfind("UTC", 0) == 0) // GMT / UTC / ±offset
435+
return true;
436+
return false;
437+
}
438+
439+
// Resolve the (session, tz) pair for a time()/time_close() call, applying the
440+
// 2-arg timezone-in-session-slot reinterpretation above.
441+
static void resolve_session_tz(const std::string& session,
442+
const std::string& tz_in,
443+
std::string& sess_out,
444+
std::string& tz_out) {
445+
sess_out = session;
446+
tz_out = tz_in;
447+
if (tz_out.empty() && session_arg_is_timezone(sess_out)) {
448+
tz_out = sess_out;
449+
sess_out.clear();
450+
}
451+
if (tz_out.empty())
452+
tz_out = "UTC";
453+
}
454+
421455
int64_t pine_time(int64_t bar_ms,
422456
const std::string& tf_in,
423457
const std::string& session,
@@ -427,9 +461,10 @@ int64_t pine_time(int64_t bar_ms,
427461
if (tf.empty())
428462
tf = "1";
429463

430-
std::string tz = tz_in.empty() ? "UTC" : tz_in;
464+
std::string sess, tz;
465+
resolve_session_tz(session, tz_in, sess, tz);
431466

432-
if (!session.empty() && !passes_session_filter(session, tz, bar_ms))
467+
if (!sess.empty() && !passes_session_filter(sess, tz, bar_ms))
433468
return na<int64_t>();
434469

435470
return compute_tf_open_ms(bar_ms, tf, tz);
@@ -444,9 +479,10 @@ int64_t pine_time_close(int64_t bar_ms,
444479
if (tf.empty())
445480
tf = "1";
446481

447-
std::string tz = tz_in.empty() ? "UTC" : tz_in;
482+
std::string sess, tz;
483+
resolve_session_tz(session, tz_in, sess, tz);
448484

449-
if (!session.empty() && !passes_session_filter(session, tz, bar_ms))
485+
if (!sess.empty() && !passes_session_filter(sess, tz, bar_ms))
450486
return na<int64_t>();
451487

452488
int64_t t_open = compute_tf_open_ms(bar_ms, tf, tz);

tests/test_session_time.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,62 @@ static void test_time_close_hourly() {
5959
CHECK(tc == to + 3600000 - 1);
6060
}
6161

62+
// --- 2-arg time(tf, tz): a timezone passed in the session slot ---
63+
64+
static void test_time_tz_in_session_slot_equiv() {
65+
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.
69+
int64_t bar = 1775572200000LL; // 2026-04-07 14:30 UTC = 10:30 NY (EDT)
70+
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
74+
}
75+
76+
static void test_time_tz_in_session_daily_change() {
77+
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 —
79+
// 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
83+
int64_t ta = pine_time(bar_a, "D", "America/New_York", "", "15");
84+
int64_t tb = pine_time(bar_b, "D", "America/New_York", "", "15");
85+
int64_t tn = pine_time(bar_next, "D", "America/New_York", "", "15");
86+
CHECK(ta == tb); // same day: no change
87+
CHECK(ta != tn); // new day: change fires
88+
}
89+
90+
static void test_time_real_session_2arg_still_filters() {
91+
std::printf("test_time_real_session_2arg_still_filters\n");
92+
// A genuine 2-arg session (no tz) must still filter — the reinterpretation
93+
// only triggers for tz-looking strings, never "0800-1600".
94+
int64_t bar_in = 1775572200000LL; // 14:30 UTC — inside 0800-1600 UTC
95+
int64_t bar_out = 1775601000000LL; // 22:30 UTC — outside
96+
CHECK(!is_na(pine_time(bar_in, "60", "0800-1600", "", "60")));
97+
CHECK( is_na(pine_time(bar_out, "60", "0800-1600", "", "60")));
98+
}
99+
100+
static void test_time_gmt_in_session_slot() {
101+
std::printf("test_time_gmt_in_session_slot\n");
102+
// GMT/UTC specifiers in the session slot are also recognized as timezones.
103+
int64_t bar = 1775572200000LL;
104+
CHECK(!is_na(pine_time(bar, "D", "GMT+0", "", "15")));
105+
CHECK(!is_na(pine_time(bar, "D", "UTC", "", "15")));
106+
}
107+
62108
int main() {
63109
test_time_hourly_bucket_utc();
64110
test_time_session_ny_inside();
65111
test_time_session_ny_outside();
66112
test_time_weekday_filter_mon_fri_only();
67113
test_time_close_hourly();
114+
test_time_tz_in_session_slot_equiv();
115+
test_time_tz_in_session_daily_change();
116+
test_time_real_session_2arg_still_filters();
117+
test_time_gmt_in_session_slot();
68118

69119
std::printf("session_time: %d passed, %d failed\n", tests_passed, tests_failed);
70120
return tests_failed > 0 ? 1 : 0;

0 commit comments

Comments
 (0)