From 9ab438791e706102b559690b70f2d55fa9fb5a1b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 08:54:13 +0000 Subject: [PATCH 1/7] Refactor DefaultSessionSchedule formatting to java.time Agent-Logs-Url: https://github.com/quickfix-j/quickfixj/sessions/ef6ae2c0-e37a-470b-844b-0b3669bf49e0 Co-authored-by: chrjohn <6644028+chrjohn@users.noreply.github.com> --- .../java/quickfix/DefaultSessionSchedule.java | 31 +++++++++---------- .../quickfix/DefaultSessionScheduleTest.java | 19 ++++++++++++ 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/quickfixj-core/src/main/java/quickfix/DefaultSessionSchedule.java b/quickfixj-core/src/main/java/quickfix/DefaultSessionSchedule.java index c1ec3e5c4f..8f4a158006 100644 --- a/quickfixj-core/src/main/java/quickfix/DefaultSessionSchedule.java +++ b/quickfixj-core/src/main/java/quickfix/DefaultSessionSchedule.java @@ -22,8 +22,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.Calendar; +import java.util.Locale; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -253,7 +257,8 @@ boolean isContainingTime(Calendar t) { } public String toString() { - return start.getTime() + " --> " + end.getTime(); + return Instant.ofEpochMilli(start.getTimeInMillis()) + " --> " + + Instant.ofEpochMilli(end.getTimeInMillis()); } public boolean equals(Object other) { @@ -322,11 +327,7 @@ public boolean isSessionTime() { public String toString() { StringBuilder buf = new StringBuilder(); - SimpleDateFormat dowFormat = new SimpleDateFormat("EEEE"); - dowFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - - SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss-z"); - timeFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HH:mm:ss-z", Locale.getDefault()); TimeInterval ti = theMostRecentIntervalBefore(SystemTime.getUtcCalendar()); @@ -344,7 +345,7 @@ public String toString() { } private void formatTimeInterval(StringBuilder buf, TimeInterval timeInterval, - SimpleDateFormat timeFormat, boolean local) { + DateTimeFormatter timeFormat, boolean local) { if (isNonStopSession) { buf.append("nonstop"); return; @@ -365,10 +366,9 @@ private void formatTimeInterval(StringBuilder buf, TimeInterval timeInterval, buf.append("daily, "); } - if (local) { - timeFormat.setTimeZone(startTime.getTimeZone()); - } - buf.append(timeFormat.format(timeInterval.getStart().getTime())); + ZoneId startZone = local ? startTime.getTimeZone().toZoneId() : ZoneOffset.UTC; + buf.append(timeFormat.format(Instant.ofEpochMilli(timeInterval.getStart().getTimeInMillis()) + .atZone(startZone))); buf.append(" - "); @@ -376,10 +376,9 @@ private void formatTimeInterval(StringBuilder buf, TimeInterval timeInterval, formatDayOfWeek(buf, endTime.getDay()); buf.append(" "); } - if (local) { - timeFormat.setTimeZone(endTime.getTimeZone()); - } - buf.append(timeFormat.format(timeInterval.getEnd().getTime())); + ZoneId endZone = local ? endTime.getTimeZone().toZoneId() : ZoneOffset.UTC; + buf.append(timeFormat.format(Instant.ofEpochMilli(timeInterval.getEnd().getTimeInMillis()) + .atZone(endZone))); } private void formatDayOfWeek(StringBuilder buf, int dayOfWeek) { diff --git a/quickfixj-core/src/test/java/quickfix/DefaultSessionScheduleTest.java b/quickfixj-core/src/test/java/quickfix/DefaultSessionScheduleTest.java index 7ae6919656..77666f0e16 100644 --- a/quickfixj-core/src/test/java/quickfix/DefaultSessionScheduleTest.java +++ b/quickfixj-core/src/test/java/quickfix/DefaultSessionScheduleTest.java @@ -8,6 +8,7 @@ import java.io.ByteArrayInputStream; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; @@ -117,4 +118,22 @@ public void isSessionTime_returns_false_for_time_outside_window() throws FieldCo assertFalse(schedule.isSessionTime()); } + + @Test + public void toString_uses_expected_utc_time_format() throws FieldConvertError, ConfigError { + when(mockTimeSource.getTime()).thenReturn(1L); + String sessionSettingsString = "" + + "[DEFAULT]\n" + + "\n" + + "[SESSION]\n" + + "BeginString=FIX.4.2\n" + + "SenderCompID=A\n" + + "TargetCompID=B\n" + + "StartTime=00:00:00\n" + + "EndTime=00:00:01\n"; + SessionSettings sessionSettings = new SessionSettings(new ByteArrayInputStream(sessionSettingsString.getBytes())); + DefaultSessionSchedule schedule = new DefaultSessionSchedule(sessionSettings, sessionID); + + assertEquals("daily, 00:00:00-UTC - 00:00:01-UTC", schedule.toString()); + } } From b76d537ed30ecee4503a550cca658c01aa64b09b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 08:57:59 +0000 Subject: [PATCH 2/7] Finalize java.time-based session schedule formatting Agent-Logs-Url: https://github.com/quickfix-j/quickfixj/sessions/ef6ae2c0-e37a-470b-844b-0b3669bf49e0 Co-authored-by: chrjohn <6644028+chrjohn@users.noreply.github.com> --- .../src/main/java/quickfix/DefaultSessionSchedule.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/quickfixj-core/src/main/java/quickfix/DefaultSessionSchedule.java b/quickfixj-core/src/main/java/quickfix/DefaultSessionSchedule.java index 8f4a158006..cf13e6fb5d 100644 --- a/quickfixj-core/src/main/java/quickfix/DefaultSessionSchedule.java +++ b/quickfixj-core/src/main/java/quickfix/DefaultSessionSchedule.java @@ -24,7 +24,6 @@ import java.time.Instant; import java.time.ZoneId; -import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.Calendar; import java.util.Locale; @@ -366,7 +365,7 @@ private void formatTimeInterval(StringBuilder buf, TimeInterval timeInterval, buf.append("daily, "); } - ZoneId startZone = local ? startTime.getTimeZone().toZoneId() : ZoneOffset.UTC; + ZoneId startZone = local ? startTime.getTimeZone().toZoneId() : TimeZone.getTimeZone("UTC").toZoneId(); buf.append(timeFormat.format(Instant.ofEpochMilli(timeInterval.getStart().getTimeInMillis()) .atZone(startZone))); @@ -376,7 +375,7 @@ private void formatTimeInterval(StringBuilder buf, TimeInterval timeInterval, formatDayOfWeek(buf, endTime.getDay()); buf.append(" "); } - ZoneId endZone = local ? endTime.getTimeZone().toZoneId() : ZoneOffset.UTC; + ZoneId endZone = local ? endTime.getTimeZone().toZoneId() : TimeZone.getTimeZone("UTC").toZoneId(); buf.append(timeFormat.format(Instant.ofEpochMilli(timeInterval.getEnd().getTimeInMillis()) .atZone(endZone))); } From 054b63e2a7a010b6e5604117a2471ede626ea3f5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 09:39:01 +0000 Subject: [PATCH 3/7] Remove Calendar from session schedule: use ZonedDateTime throughout Agent-Logs-Url: https://github.com/quickfix-j/quickfixj/sessions/fd599c00-e171-4397-ba2a-55d22de77d69 Co-authored-by: chrjohn <6644028+chrjohn@users.noreply.github.com> --- .../java/quickfix/DefaultSessionSchedule.java | 134 +++++++++--------- .../src/main/java/quickfix/Session.java | 5 +- .../main/java/quickfix/SessionSchedule.java | 4 +- .../java/quickfix/SessionScheduleTest.java | 20 ++- 4 files changed, 85 insertions(+), 78 deletions(-) diff --git a/quickfixj-core/src/main/java/quickfix/DefaultSessionSchedule.java b/quickfixj-core/src/main/java/quickfix/DefaultSessionSchedule.java index cf13e6fb5d..51131f2eea 100644 --- a/quickfixj-core/src/main/java/quickfix/DefaultSessionSchedule.java +++ b/quickfixj-core/src/main/java/quickfix/DefaultSessionSchedule.java @@ -24,8 +24,9 @@ import java.time.Instant; import java.time.ZoneId; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.Calendar; +import java.time.temporal.WeekFields; import java.util.Locale; import java.util.TimeZone; import java.util.regex.Matcher; @@ -44,13 +45,11 @@ public class DefaultSessionSchedule implements SessionSchedule { private final int[] weekdayOffsets; protected static final Logger LOG = LoggerFactory.getLogger(DefaultSessionSchedule.class); - //Cache recent time data to reduce creation of calendar objects - private final ThreadLocal threadLocalCalendar; + //Cache recent time data to reduce creation of objects private final ThreadLocal threadLocalRecentTimeInterval; public DefaultSessionSchedule(SessionSettings settings, SessionID sessionID) throws ConfigError, FieldConvertError { - threadLocalCalendar = ThreadLocal.withInitial(SystemTime::getUtcCalendar); threadLocalRecentTimeInterval = new ThreadLocal<>(); isNonStopSession = settings.isSetting(sessionID, Session.SETTING_NON_STOP_SESSION) && settings.getBool(sessionID, Session.SETTING_NON_STOP_SESSION); @@ -190,74 +189,70 @@ TimeZone getTimeZone() { /** * find the most recent session date/time range on or before t * if t is in a session then that session will be returned - * @param t specific date/time + * @param epochMillis specific date/time as epoch milliseconds * @return relevant session date/time range */ - private TimeInterval theMostRecentIntervalBefore(Calendar t) { - TimeInterval timeInterval = new TimeInterval(); - Calendar intervalStart = timeInterval.getStart(); - intervalStart.setTimeZone(startTime.getTimeZone()); - intervalStart.setTimeInMillis(t.getTimeInMillis()); - intervalStart.set(Calendar.HOUR_OF_DAY, startTime.getHour()); - intervalStart.set(Calendar.MINUTE, startTime.getMinute()); - intervalStart.set(Calendar.SECOND, startTime.getSecond()); - intervalStart.set(Calendar.MILLISECOND, 0); - - Calendar intervalEnd = timeInterval.getEnd(); - intervalEnd.setTimeZone(endTime.getTimeZone()); - intervalEnd.setTimeInMillis(t.getTimeInMillis()); - intervalEnd.set(Calendar.HOUR_OF_DAY, endTime.getHour()); - intervalEnd.set(Calendar.MINUTE, endTime.getMinute()); - intervalEnd.set(Calendar.SECOND, endTime.getSecond()); - intervalEnd.set(Calendar.MILLISECOND, 0); + private TimeInterval theMostRecentIntervalBefore(long epochMillis) { + ZonedDateTime intervalStart = ZonedDateTime + .ofInstant(Instant.ofEpochMilli(epochMillis), startTime.getTimeZone().toZoneId()) + .withHour(startTime.getHour()).withMinute(startTime.getMinute()) + .withSecond(startTime.getSecond()).withNano(0); + + ZonedDateTime intervalEnd = ZonedDateTime + .ofInstant(Instant.ofEpochMilli(epochMillis), endTime.getTimeZone().toZoneId()) + .withHour(endTime.getHour()).withMinute(endTime.getMinute()) + .withSecond(endTime.getSecond()).withNano(0); if (isWeekdaySession) { - while (intervalStart.getTimeInMillis() > t.getTimeInMillis() || - !validDayOfWeek(intervalStart)) { - intervalStart.add(Calendar.DAY_OF_WEEK, -1); - intervalEnd.add(Calendar.DAY_OF_WEEK, -1); + while (intervalStart.toInstant().toEpochMilli() > epochMillis || !validDayOfWeek(intervalStart)) { + intervalStart = intervalStart.minusDays(1); + intervalEnd = intervalEnd.minusDays(1); } - - while (intervalEnd.getTimeInMillis() <= intervalStart.getTimeInMillis()) { - intervalEnd.add(Calendar.DAY_OF_WEEK, 1); + while (intervalEnd.toInstant().toEpochMilli() <= intervalStart.toInstant().toEpochMilli()) { + intervalEnd = intervalEnd.plusDays(1); } - } else { if (isSet(startTime.getDay())) { - intervalStart.set(Calendar.DAY_OF_WEEK, startTime.getDay()); - if (intervalStart.getTimeInMillis() > t.getTimeInMillis()) { - intervalStart.add(Calendar.WEEK_OF_YEAR, -1); - intervalEnd.add(Calendar.WEEK_OF_YEAR, -1); + intervalStart = intervalStart.with(WeekFields.SUNDAY_START.dayOfWeek(), startTime.getDay()); + if (intervalStart.toInstant().toEpochMilli() > epochMillis) { + intervalStart = intervalStart.minusWeeks(1); + intervalEnd = intervalEnd.minusWeeks(1); } - } else if (intervalStart.getTimeInMillis() > t.getTimeInMillis()) { - intervalStart.add(Calendar.DAY_OF_YEAR, -1); - intervalEnd.add(Calendar.DAY_OF_YEAR, -1); + } else if (intervalStart.toInstant().toEpochMilli() > epochMillis) { + intervalStart = intervalStart.minusDays(1); + intervalEnd = intervalEnd.minusDays(1); } if (isSet(endTime.getDay())) { - intervalEnd.set(Calendar.DAY_OF_WEEK, endTime.getDay()); - if (intervalEnd.getTimeInMillis() <= intervalStart.getTimeInMillis()) { - intervalEnd.add(Calendar.WEEK_OF_MONTH, 1); + intervalEnd = intervalEnd.with(WeekFields.SUNDAY_START.dayOfWeek(), endTime.getDay()); + if (intervalEnd.toInstant().toEpochMilli() <= intervalStart.toInstant().toEpochMilli()) { + intervalEnd = intervalEnd.plusWeeks(1); } - } else if (intervalEnd.getTimeInMillis() <= intervalStart.getTimeInMillis()) { - intervalEnd.add(Calendar.DAY_OF_WEEK, 1); + } else if (intervalEnd.toInstant().toEpochMilli() <= intervalStart.toInstant().toEpochMilli()) { + intervalEnd = intervalEnd.plusDays(1); } } - return timeInterval; + return new TimeInterval( + intervalStart.toInstant().toEpochMilli(), + intervalEnd.toInstant().toEpochMilli()); } private static class TimeInterval { - private final Calendar start = SystemTime.getUtcCalendar(); - private final Calendar end = SystemTime.getUtcCalendar(); + private final long startMs; + private final long endMs; + + TimeInterval(long startMs, long endMs) { + this.startMs = startMs; + this.endMs = endMs; + } - boolean isContainingTime(Calendar t) { - return t.compareTo(start) >= 0 && t.compareTo(end) <= 0; + boolean isContainingTime(long epochMillis) { + return epochMillis >= startMs && epochMillis <= endMs; } public String toString() { - return Instant.ofEpochMilli(start.getTimeInMillis()) + " --> " - + Instant.ofEpochMilli(end.getTimeInMillis()); + return Instant.ofEpochMilli(startMs) + " --> " + Instant.ofEpochMilli(endMs); } public boolean equals(Object other) { @@ -268,7 +263,7 @@ public boolean equals(Object other) { return false; } TimeInterval otherInterval = (TimeInterval) other; - return start.equals(otherInterval.start) && end.equals(otherInterval.end); + return startMs == otherInterval.startMs && endMs == otherInterval.endMs; } public int hashCode() { @@ -276,25 +271,27 @@ public int hashCode() { return 0; } - Calendar getStart() { - return start; + long getStartMs() { + return startMs; } - Calendar getEnd() { - return end; + long getEndMs() { + return endMs; } } @Override - public boolean isSameSession(Calendar time1, Calendar time2) { + public boolean isSameSession(ZonedDateTime time1, ZonedDateTime time2) { if (isNonStopSession()) return true; - TimeInterval interval1 = theMostRecentIntervalBefore(time1); - if (!interval1.isContainingTime(time1)) { + long ms1 = time1.toInstant().toEpochMilli(); + long ms2 = time2.toInstant().toEpochMilli(); + TimeInterval interval1 = theMostRecentIntervalBefore(ms1); + if (!interval1.isContainingTime(ms1)) { return false; } - TimeInterval interval2 = theMostRecentIntervalBefore(time2); - return interval2.isContainingTime(time2) && interval1.equals(interval2); + TimeInterval interval2 = theMostRecentIntervalBefore(ms2); + return interval2.isContainingTime(ms2) && interval1.equals(interval2); } @Override @@ -311,14 +308,13 @@ public boolean isSessionTime() { if(isNonStopSession()) { return true; } - Calendar now = threadLocalCalendar.get(); - now.setTimeInMillis(SystemTime.currentTimeMillis()); + long nowMs = SystemTime.currentTimeMillis(); TimeInterval mostRecentInterval = threadLocalRecentTimeInterval.get(); - if (mostRecentInterval != null && mostRecentInterval.isContainingTime(now)) { + if (mostRecentInterval != null && mostRecentInterval.isContainingTime(nowMs)) { return true; } - mostRecentInterval = theMostRecentIntervalBefore(now); - boolean result = mostRecentInterval.isContainingTime(now); + mostRecentInterval = theMostRecentIntervalBefore(nowMs); + boolean result = mostRecentInterval.isContainingTime(nowMs); threadLocalRecentTimeInterval.set(mostRecentInterval); return result; } @@ -328,7 +324,7 @@ public String toString() { DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HH:mm:ss-z", Locale.getDefault()); - TimeInterval ti = theMostRecentIntervalBefore(SystemTime.getUtcCalendar()); + TimeInterval ti = theMostRecentIntervalBefore(SystemTime.currentTimeMillis()); formatTimeInterval(buf, ti, timeFormat, false); @@ -366,7 +362,7 @@ private void formatTimeInterval(StringBuilder buf, TimeInterval timeInterval, } ZoneId startZone = local ? startTime.getTimeZone().toZoneId() : TimeZone.getTimeZone("UTC").toZoneId(); - buf.append(timeFormat.format(Instant.ofEpochMilli(timeInterval.getStart().getTimeInMillis()) + buf.append(timeFormat.format(Instant.ofEpochMilli(timeInterval.getStartMs()) .atZone(startZone))); buf.append(" - "); @@ -376,7 +372,7 @@ private void formatTimeInterval(StringBuilder buf, TimeInterval timeInterval, buf.append(" "); } ZoneId endZone = local ? endTime.getTimeZone().toZoneId() : TimeZone.getTimeZone("UTC").toZoneId(); - buf.append(timeFormat.format(Instant.ofEpochMilli(timeInterval.getEnd().getTimeInMillis()) + buf.append(timeFormat.format(Instant.ofEpochMilli(timeInterval.getEndMs()) .atZone(endZone))); } @@ -412,8 +408,8 @@ private static int timeInSeconds(int hour, int minute, int second) { * @param startDateTime time to test * @return flag indicating if valid */ - private boolean validDayOfWeek(Calendar startDateTime) { - int dow = startDateTime.get(Calendar.DAY_OF_WEEK); + private boolean validDayOfWeek(ZonedDateTime startDateTime) { + int dow = (int) startDateTime.get(WeekFields.SUNDAY_START.dayOfWeek()); for (int i = 0; i < weekdayOffsets.length; i++) if (weekdayOffsets[i] == dow) return true; diff --git a/quickfixj-core/src/main/java/quickfix/Session.java b/quickfixj-core/src/main/java/quickfix/Session.java index 7a58f2f325..df63f68a68 100644 --- a/quickfixj-core/src/main/java/quickfix/Session.java +++ b/quickfixj-core/src/main/java/quickfix/Session.java @@ -59,8 +59,10 @@ import java.io.Closeable; import java.io.IOException; import java.net.InetAddress; +import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -658,7 +660,8 @@ public String getRemoteAddress() { private boolean isCurrentSession(final long time) throws IOException { return sessionSchedule == null || sessionSchedule.isSameSession( - SystemTime.getUtcCalendar(time), state.getCreationTimeCalendar()); + ZonedDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneOffset.UTC), + ZonedDateTime.ofInstant(state.getCreationTime().toInstant(), ZoneOffset.UTC)); } /** diff --git a/quickfixj-core/src/main/java/quickfix/SessionSchedule.java b/quickfixj-core/src/main/java/quickfix/SessionSchedule.java index a19d612da4..130f550617 100644 --- a/quickfixj-core/src/main/java/quickfix/SessionSchedule.java +++ b/quickfixj-core/src/main/java/quickfix/SessionSchedule.java @@ -19,7 +19,7 @@ package quickfix; -import java.util.Calendar; +import java.time.ZonedDateTime; /** * Used to decide when to login and out of FIX sessions @@ -32,7 +32,7 @@ public interface SessionSchedule { * @param time2 test time 2 * @return return true if in the same session */ - boolean isSameSession(Calendar time1, Calendar time2); + boolean isSameSession(ZonedDateTime time1, ZonedDateTime time2); boolean isNonStopSession(); diff --git a/quickfixj-core/src/test/java/quickfix/SessionScheduleTest.java b/quickfixj-core/src/test/java/quickfix/SessionScheduleTest.java index 348c3226dc..43386a3055 100644 --- a/quickfixj-core/src/test/java/quickfix/SessionScheduleTest.java +++ b/quickfixj-core/src/test/java/quickfix/SessionScheduleTest.java @@ -29,6 +29,8 @@ import java.text.DateFormatSymbols; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; @@ -875,7 +877,7 @@ private void doWeeklyIsSameSessionTest(SessionSchedule schedule, Calendar sessio while (beforeSession(scheduleStartTime)) { assertFalse(formatErrorMessage("before session", sessionCreateTime), schedule - .isSameSession(sessionCreateTime, SystemTime.getUtcCalendar())); + .isSameSession(toZdt(sessionCreateTime), toZdt(SystemTime.getUtcCalendar()))); mockSystemTimeSource.increment(timeIncrement * 1000L); } @@ -884,13 +886,13 @@ private void doWeeklyIsSameSessionTest(SessionSchedule schedule, Calendar sessio // This should be an impossible situation. "Now" should always be // after the session create time. assertFalse(formatErrorMessage("before create", sessionCreateTime), schedule - .isSameSession(sessionCreateTime, SystemTime.getUtcCalendar())); + .isSameSession(toZdt(sessionCreateTime), toZdt(SystemTime.getUtcCalendar()))); mockSystemTimeSource.increment(timeIncrement * 1000L); } while (withinSession(scheduleStartTime, scheduleEndTime)) { assertTrue(formatErrorMessage("within", sessionCreateTime), schedule.isSameSession( - sessionCreateTime, SystemTime.getUtcCalendar())); + toZdt(sessionCreateTime), toZdt(SystemTime.getUtcCalendar()))); mockSystemTimeSource.increment(timeIncrement * 1000L); } @@ -899,7 +901,7 @@ private void doWeeklyIsSameSessionTest(SessionSchedule schedule, Calendar sessio while (beforeSession(scheduleStartTime)) { assertFalse(formatErrorMessage("after", sessionCreateTime), schedule.isSameSession( - sessionCreateTime, SystemTime.getUtcCalendar())); + toZdt(sessionCreateTime), toZdt(SystemTime.getUtcCalendar()))); mockSystemTimeSource.increment(timeIncrement * 1000L); } } @@ -930,8 +932,14 @@ private boolean beforeSession(Calendar start) { private void doIsSameSessionTest(SessionSchedule schedule, Calendar time1, Calendar time2, boolean isSameSession) { - assertEquals("isSameSession is wrong", isSameSession, schedule.isSameSession(time1, time2)); - assertEquals("isSameSession is wrong", isSameSession, schedule.isSameSession(time2, time1)); + ZonedDateTime zdt1 = toZdt(time1); + ZonedDateTime zdt2 = toZdt(time2); + assertEquals("isSameSession is wrong", isSameSession, schedule.isSameSession(zdt1, zdt2)); + assertEquals("isSameSession is wrong", isSameSession, schedule.isSameSession(zdt2, zdt1)); + } + + private static ZonedDateTime toZdt(Calendar c) { + return c == null ? null : ZonedDateTime.ofInstant(c.toInstant(), ZoneOffset.UTC); } private Calendar getTimeStamp(int year, int month, int day, int hour, int minute, int second, From 12150800cd255ea9053900e3109cd464becb0bf3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 10:25:48 +0000 Subject: [PATCH 4/7] Use SystemTime-compatible epoch checks in SessionSchedule and NoopStore Agent-Logs-Url: https://github.com/quickfix-j/quickfixj/sessions/ac53da8d-957c-49ef-82c3-1e528a9fc119 Co-authored-by: chrjohn <6644028+chrjohn@users.noreply.github.com> --- .../java/quickfix/DefaultSessionSchedule.java | 12 +++++------- .../src/main/java/quickfix/NoopStore.java | 7 +++---- .../src/main/java/quickfix/Session.java | 5 +---- .../src/main/java/quickfix/SessionSchedule.java | 16 +++++++++++++++- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/quickfixj-core/src/main/java/quickfix/DefaultSessionSchedule.java b/quickfixj-core/src/main/java/quickfix/DefaultSessionSchedule.java index 51131f2eea..26320c0891 100644 --- a/quickfixj-core/src/main/java/quickfix/DefaultSessionSchedule.java +++ b/quickfixj-core/src/main/java/quickfix/DefaultSessionSchedule.java @@ -281,17 +281,15 @@ long getEndMs() { } @Override - public boolean isSameSession(ZonedDateTime time1, ZonedDateTime time2) { + public boolean isSameSession(long time1, long time2) { if (isNonStopSession()) return true; - long ms1 = time1.toInstant().toEpochMilli(); - long ms2 = time2.toInstant().toEpochMilli(); - TimeInterval interval1 = theMostRecentIntervalBefore(ms1); - if (!interval1.isContainingTime(ms1)) { + TimeInterval interval1 = theMostRecentIntervalBefore(time1); + if (!interval1.isContainingTime(time1)) { return false; } - TimeInterval interval2 = theMostRecentIntervalBefore(ms2); - return interval2.isContainingTime(ms2) && interval1.equals(interval2); + TimeInterval interval2 = theMostRecentIntervalBefore(time2); + return interval2.isContainingTime(time2) && interval1.equals(interval2); } @Override diff --git a/quickfixj-core/src/main/java/quickfix/NoopStore.java b/quickfixj-core/src/main/java/quickfix/NoopStore.java index 8bdc4cc354..6a6dbf6e49 100644 --- a/quickfixj-core/src/main/java/quickfix/NoopStore.java +++ b/quickfixj-core/src/main/java/quickfix/NoopStore.java @@ -31,8 +31,7 @@ */ public class NoopStore implements MessageStore { - private Date creationTime = new Date(); - private Calendar creationTimeCalendar = SystemTime.getUtcCalendar(creationTime); + private Date creationTime = SystemTime.getDate(); private int nextSenderMsgSeqNum = 1; private int nextTargetMsgSeqNum = 1; @@ -44,7 +43,7 @@ public Date getCreationTime() { } public Calendar getCreationTimeCalendar() { - return creationTimeCalendar; + return SystemTime.getUtcCalendar(creationTime); } public int getNextSenderMsgSeqNum() { @@ -64,7 +63,7 @@ public void incrNextTargetMsgSeqNum() { } public void reset() { - creationTime = new Date(); + creationTime = SystemTime.getDate(); nextSenderMsgSeqNum = 1; nextTargetMsgSeqNum = 1; } diff --git a/quickfixj-core/src/main/java/quickfix/Session.java b/quickfixj-core/src/main/java/quickfix/Session.java index df63f68a68..e66c980a34 100644 --- a/quickfixj-core/src/main/java/quickfix/Session.java +++ b/quickfixj-core/src/main/java/quickfix/Session.java @@ -59,10 +59,8 @@ import java.io.Closeable; import java.io.IOException; import java.net.InetAddress; -import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; -import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -660,8 +658,7 @@ public String getRemoteAddress() { private boolean isCurrentSession(final long time) throws IOException { return sessionSchedule == null || sessionSchedule.isSameSession( - ZonedDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneOffset.UTC), - ZonedDateTime.ofInstant(state.getCreationTime().toInstant(), ZoneOffset.UTC)); + time, state.getCreationTime().getTime()); } /** diff --git a/quickfixj-core/src/main/java/quickfix/SessionSchedule.java b/quickfixj-core/src/main/java/quickfix/SessionSchedule.java index 130f550617..bda93da3f5 100644 --- a/quickfixj-core/src/main/java/quickfix/SessionSchedule.java +++ b/quickfixj-core/src/main/java/quickfix/SessionSchedule.java @@ -26,13 +26,27 @@ */ public interface SessionSchedule { + /** + * Predicate for determining if the two epoch-millisecond times are in the same session. + * + * @param time1 first time in epoch milliseconds + * @param time2 second time in epoch milliseconds + * @return true if in the same session + */ + boolean isSameSession(long time1, long time2); + /** * Predicate for determining if the two times are in the same session * @param time1 test time 1 * @param time2 test time 2 * @return return true if in the same session */ - boolean isSameSession(ZonedDateTime time1, ZonedDateTime time2); + default boolean isSameSession(ZonedDateTime time1, ZonedDateTime time2) { + if (time1 == null || time2 == null) { + return isNonStopSession(); + } + return isSameSession(time1.toInstant().toEpochMilli(), time2.toInstant().toEpochMilli()); + } boolean isNonStopSession(); From c4701b0eb49589b414a7b09b4ecfe06eecd8e7fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 13:40:08 +0000 Subject: [PATCH 5/7] Replace NoopStore SystemTime.getDate with java.time-based Date conversion Agent-Logs-Url: https://github.com/quickfix-j/quickfixj/sessions/86d191b3-7848-4e01-9557-1c6bc19cb837 Co-authored-by: chrjohn <6644028+chrjohn@users.noreply.github.com> --- quickfixj-core/src/main/java/quickfix/NoopStore.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/quickfixj-core/src/main/java/quickfix/NoopStore.java b/quickfixj-core/src/main/java/quickfix/NoopStore.java index 6a6dbf6e49..ec69f0c116 100644 --- a/quickfixj-core/src/main/java/quickfix/NoopStore.java +++ b/quickfixj-core/src/main/java/quickfix/NoopStore.java @@ -20,6 +20,7 @@ package quickfix; +import java.time.Instant; import java.util.Calendar; import java.util.Collection; import java.util.Date; @@ -31,7 +32,7 @@ */ public class NoopStore implements MessageStore { - private Date creationTime = SystemTime.getDate(); + private Date creationTime = Date.from(Instant.ofEpochMilli(SystemTime.currentTimeMillis())); private int nextSenderMsgSeqNum = 1; private int nextTargetMsgSeqNum = 1; @@ -63,7 +64,7 @@ public void incrNextTargetMsgSeqNum() { } public void reset() { - creationTime = SystemTime.getDate(); + creationTime = Date.from(Instant.ofEpochMilli(SystemTime.currentTimeMillis())); nextSenderMsgSeqNum = 1; nextTargetMsgSeqNum = 1; } From 91223adc071b34da9e835c9997eeec475f78d1c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 18:55:37 +0000 Subject: [PATCH 6/7] test: add SessionSchedule previous-day start test Agent-Logs-Url: https://github.com/quickfix-j/quickfixj/sessions/d264a768-54d3-4d1c-aa57-93785bc1f619 Co-authored-by: chrjohn <6644028+chrjohn@users.noreply.github.com> --- .../java/quickfix/SessionScheduleTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/quickfixj-core/src/test/java/quickfix/SessionScheduleTest.java b/quickfixj-core/src/test/java/quickfix/SessionScheduleTest.java index 43386a3055..d5513c69e0 100644 --- a/quickfixj-core/src/test/java/quickfix/SessionScheduleTest.java +++ b/quickfixj-core/src/test/java/quickfix/SessionScheduleTest.java @@ -346,6 +346,29 @@ public void testIsSameSessionWithDay() throws Exception { doIsSameSessionTest(schedule, t1, t2, true); } + @Test + public void testSessionStartInPreviousDayOfWeek() throws Exception { + Locale.setDefault(new Locale("en", "AU")); + + final TimeZone tz = TimeZone.getTimeZone("Australia/Victoria"); + mockSystemTimeSource.setTime(getTimeStamp(2026, Calendar.FEBRUARY, 23, 10, 0, 0, tz)); + + final SessionSettings settings = new SessionSettings(); + settings.setString(Session.SETTING_START_TIME, "13:00:00 Australia/Victoria"); + + // AU week starts on Sunday on Java 17 and below, but on Monday on Java 21 and above (not sure about in between). + settings.setString(Session.SETTING_START_DAY, "Sunday"); + + settings.setString(Session.SETTING_END_TIME, "17:00:00 America/New_York"); + settings.setString(Session.SETTING_END_DAY, "Saturday"); + + SessionID sessionID = new SessionID("FIX.4.2", "SENDER", "TARGET"); + final SessionSchedule schedule = new DefaultSessionSchedule(settings, sessionID); + + mockSystemTimeSource.setTime(getTimeStamp(2026, Calendar.FEBRUARY, 23, 10, 0, 0, tz)); + assertEquals("in session expectation incorrect", true, schedule.isSessionTime()); + } + @Test public void testSettingsWithoutStartEndDay() throws Exception { SessionSettings settings = new SessionSettings(); From 3ea3d92a1fa5bdd3502aeb79d1bcef2dfdf6391f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 20:57:22 +0000 Subject: [PATCH 7/7] test: fix LogUtilTest store stub Agent-Logs-Url: https://github.com/quickfix-j/quickfixj/sessions/6e680b34-2576-43e6-8a75-bcadbffdd3df Co-authored-by: chrjohn <6644028+chrjohn@users.noreply.github.com> --- quickfixj-core/src/test/java/quickfix/LogUtilTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/quickfixj-core/src/test/java/quickfix/LogUtilTest.java b/quickfixj-core/src/test/java/quickfix/LogUtilTest.java index aee0925266..7bc0ea0995 100644 --- a/quickfixj-core/src/test/java/quickfix/LogUtilTest.java +++ b/quickfixj-core/src/test/java/quickfix/LogUtilTest.java @@ -22,7 +22,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; -import java.util.Calendar; import java.util.Date; import org.junit.After; import static org.junit.Assert.assertTrue; @@ -61,7 +60,7 @@ private void createSessionAndGenerateException(LogFactory mockLogFactory) throws Session session = new Session(null, sessionID1 -> { try { return new MemoryStore() { - public Calendar getCreationTimeCalendar() throws IOException { + public Date getCreationTime() throws IOException { throw new IOException("test"); } };