diff --git a/DXFeedFramework.xcodeproj/project.pbxproj b/DXFeedFramework.xcodeproj/project.pbxproj index 5bdadfd2c..109fef029 100644 --- a/DXFeedFramework.xcodeproj/project.pbxproj +++ b/DXFeedFramework.xcodeproj/project.pbxproj @@ -193,6 +193,7 @@ 64B436402AB8857F0003919E /* SessionFilter+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B4363F2AB8857F0003919E /* SessionFilter+Ext.swift */; }; 64B436422AB88DE70003919E /* NativeSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B436412AB88DE70003919E /* NativeSession.swift */; }; 64B436442AB88EA40003919E /* NativeDay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B436432AB88EA40003919E /* NativeDay.swift */; }; + 64B436462AB985AE0003919E /* NativeBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B436452AB985AE0003919E /* NativeBox.swift */; }; 64B627352A375C0F00196D07 /* QuoteModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B627292A375C0E00196D07 /* QuoteModel.swift */; }; 64B627382A375C0F00196D07 /* QuoteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B6272C2A375C0E00196D07 /* QuoteViewController.swift */; }; 64B627392A375C0F00196D07 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B6272D2A375C0E00196D07 /* AppDelegate.swift */; }; @@ -546,6 +547,7 @@ 64B4363F2AB8857F0003919E /* SessionFilter+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionFilter+Ext.swift"; sourceTree = ""; }; 64B436412AB88DE70003919E /* NativeSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeSession.swift; sourceTree = ""; }; 64B436432AB88EA40003919E /* NativeDay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeDay.swift; sourceTree = ""; }; + 64B436452AB985AE0003919E /* NativeBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeBox.swift; sourceTree = ""; }; 64B627152A375BBA00196D07 /* DXQuoteTableApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DXQuoteTableApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 64B627292A375C0E00196D07 /* QuoteModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuoteModel.swift; sourceTree = ""; }; 64B6272C2A375C0E00196D07 /* QuoteViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuoteViewController.swift; sourceTree = ""; }; @@ -931,6 +933,7 @@ 64B4363F2AB8857F0003919E /* SessionFilter+Ext.swift */, 64B436412AB88DE70003919E /* NativeSession.swift */, 64B436432AB88EA40003919E /* NativeDay.swift */, + 64B436452AB985AE0003919E /* NativeBox.swift */, ); path = Schedule; sourceTree = ""; @@ -1728,6 +1731,7 @@ 64ECD6852A9DDF6200B36935 /* DXInstrumentProfileCollector.swift in Sources */, 64656F5B2A1B9784006A0B19 /* DXFeed.swift in Sources */, 6447A5EF2A8FD1CD00739CCF /* DayUtil.swift in Sources */, + 64B436462AB985AE0003919E /* NativeBox.swift in Sources */, 6447A5E92A8F9E0B00739CCF /* TimeNanosUtil.swift in Sources */, 640C3FD82A617D4900555161 /* CandlePriceLevel.swift in Sources */, 6498E6C42AB20B860093A065 /* ScheduleSessionType+Ext.swift in Sources */, diff --git a/DXFeedFramework/Native/Schedule/NativeBox.swift b/DXFeedFramework/Native/Schedule/NativeBox.swift new file mode 100644 index 000000000..1473fe26a --- /dev/null +++ b/DXFeedFramework/Native/Schedule/NativeBox.swift @@ -0,0 +1,19 @@ +// +// NativeBox.swift +// DXFeedFramework +// +// Created by Aleksey Kosylo on 19.09.23. +// + +import Foundation +@_implementationOnly import graal_api + +/// Just box for graal structure. +/// +/// Please, override deinit to correct deallocation graal structure +class NativeBox { + let native: UnsafeMutablePointer + init(native: UnsafeMutablePointer) { + self.native = native + } +} diff --git a/DXFeedFramework/Native/Schedule/NativeDay.swift b/DXFeedFramework/Native/Schedule/NativeDay.swift index 6946643a6..c4a056db2 100644 --- a/DXFeedFramework/Native/Schedule/NativeDay.swift +++ b/DXFeedFramework/Native/Schedule/NativeDay.swift @@ -8,16 +8,9 @@ import Foundation @_implementationOnly import graal_api -class NativeDay { - let native: UnsafeMutablePointer - - init(native: UnsafeMutablePointer) { - self.native = native - } - +class NativeDay: NativeBox { deinit { let thread = currentThread() _ = try? ErrorCheck.nativeCall(thread, dxfg_JavaObjectHandler_release(thread, &(native.pointee.handler))) } - } diff --git a/DXFeedFramework/Native/Schedule/NativeSchedule.swift b/DXFeedFramework/Native/Schedule/NativeSchedule.swift index 38d66b385..6280f8be0 100644 --- a/DXFeedFramework/Native/Schedule/NativeSchedule.swift +++ b/DXFeedFramework/Native/Schedule/NativeSchedule.swift @@ -76,24 +76,19 @@ class NativeSchedule { public func getDayByTime(time: Long) throws -> ScheduleDay { let thread = currentThread() let day = try ErrorCheck.nativeCall(thread, dxfg_Schedule_getDayByTime(thread, schedule, time)) - defer { - _ = try? ErrorCheck.nativeCall(thread, dxfg_JavaObjectHandler_release(thread, &(day.pointee.handler))) - } return try createDay(thread, day) } public func getDayById(dayId: Int32) throws -> ScheduleDay { let thread = currentThread() let day = try ErrorCheck.nativeCall(thread, dxfg_Schedule_getDayById(thread, schedule, dayId)) - defer { - _ = try? ErrorCheck.nativeCall(thread, dxfg_JavaObjectHandler_release(thread, &(day.pointee.handler))) - } return try createDay(thread, day) } private func createDay(_ thread: OpaquePointer?, _ day: UnsafeMutablePointer) throws -> ScheduleDay { let scheduleDay = ScheduleDay() + scheduleDay.native = NativeDay(native: day) scheduleDay.nativeSchedule = self scheduleDay.dayId = try ErrorCheck.nativeCall(thread, dxfg_Day_getDayId(thread, day)) scheduleDay.yearMonthDay = try ErrorCheck.nativeCall(thread, dxfg_Day_getYearMonthDay(thread, day)) @@ -133,60 +128,88 @@ class NativeSchedule { return session } - internal func getNextDay(after day: ScheduleDay, filter: DayFilter ) throws -> ScheduleDay? { - let qdValue = filter.toQDValue() - let thread = currentThread() - let filter = try ErrorCheck.nativeCall(thread, dxfg_DayFilter_getInstance(thread, qdValue)) - let currentDay = try ErrorCheck.nativeCall(thread, dxfg_Schedule_getDayById(thread, schedule, day.dayId)) - let nextDay = try ErrorCheck.nativeCall(thread, dxfg_Day_getNextDay(thread, currentDay, filter)) - let day = try? createDay(thread, nextDay) - try ErrorCheck.nativeCall(thread, dxfg_JavaObjectHandler_release(thread, &(nextDay.pointee.handler))) - return day + internal func getNextDay(after day: ScheduleDay, filter: DayFilter) throws -> ScheduleDay? { + try getDay(for: day, filter: filter) { thread, nativeDay, filter in + try ErrorCheck.nativeCall(thread, dxfg_Day_getNextDay(thread, nativeDay, filter)) + } } internal func getPrevtDay(before day: ScheduleDay, filter: DayFilter ) throws -> ScheduleDay? { + try getDay(for: day, filter: filter) { thread, nativeDay, filter in + try ErrorCheck.nativeCall(thread, dxfg_Day_getPrevDay(thread, nativeDay, filter)) + } + } + + typealias GetDayExecutor = + (OpaquePointer?, + UnsafeMutablePointer?, + UnsafeMutablePointer) throws -> UnsafeMutablePointer + + private func getDay(for day: ScheduleDay, + filter: DayFilter, + executor: GetDayExecutor) throws -> ScheduleDay? { let qdValue = filter.toQDValue() let thread = currentThread() let filter = try ErrorCheck.nativeCall(thread, dxfg_DayFilter_getInstance(thread, qdValue)) defer { _ = try? ErrorCheck.nativeCall(thread, dxfg_JavaObjectHandler_release(thread, &(filter.pointee.handler))) } - let currentDay = try ErrorCheck.nativeCall(thread, dxfg_Schedule_getDayById(thread, schedule, day.dayId)) - defer { - _ = try? ErrorCheck.nativeCall(thread, dxfg_JavaObjectHandler_release(thread, &(currentDay.pointee.handler))) - } - let prevDay = try ErrorCheck.nativeCall(thread, dxfg_Day_getPrevDay(thread, currentDay, filter)) - defer { - _ = try? ErrorCheck.nativeCall(thread, dxfg_JavaObjectHandler_release(thread, &(prevDay.pointee.handler))) - } - let day = try? createDay(thread, prevDay) + let nextDay = try executor(thread, day.native?.native, filter) + let day = try? createDay(thread, nextDay) return day } - internal func getNextSession(after session: ScheduleSession, filter: SessionFilter ) throws -> ScheduleSession? { + internal func getNextSession(after session: ScheduleSession, + filter: SessionFilter) throws -> ScheduleSession? { + try getSession(for: session, filter: filter, executor: { thread, session, filter in + try ErrorCheck.nativeCall(thread, dxfg_Session_getNextSession(thread, session, filter)) + }) + } + + internal func getPrevtSession(before session: ScheduleSession, filter: SessionFilter ) throws -> ScheduleSession? { + try getSession(for: session, filter: filter, executor: { thread, session, filter in + try ErrorCheck.nativeCall(thread, dxfg_Session_getPrevSession(thread, session, filter)) + }) + } + + typealias GetSessionyExecutor = + (OpaquePointer?, + UnsafeMutablePointer?, + UnsafeMutablePointer) throws -> UnsafeMutablePointer + + private func getSession(for session: ScheduleSession, + filter: SessionFilter, + executor: GetSessionyExecutor) throws -> ScheduleSession? { let qdValue = filter.toQDValue() let thread = currentThread() let filter = try ErrorCheck.nativeCall(thread, dxfg_SessionFilter_getInstance(thread, qdValue)) defer { _ = try? ErrorCheck.nativeCall(thread, dxfg_JavaObjectHandler_release(thread, &(filter.pointee.handler))) } - let currentSession = session.native.native - let nextSession = try ErrorCheck.nativeCall(thread, - dxfg_Session_getNextSession(thread, currentSession, filter)) + let nextSession = try executor(thread, session.native.native, filter) let session = try createSession(thread, session: nextSession) return session } - internal func getPrevtSession(before session: ScheduleSession, filter: SessionFilter ) throws -> ScheduleSession? { + public func getSessionByTime(time: Long) throws -> ScheduleSession { + let thread = currentThread() + let nextSession = try ErrorCheck.nativeCall(thread, dxfg_Schedule_getSessionByTime(thread, schedule, time)) + let session = try createSession(thread, session: nextSession) + return session + } + + public func getNearestSessionByTime(time: Long, filter: SessionFilter) throws -> ScheduleSession { let qdValue = filter.toQDValue() let thread = currentThread() let filter = try ErrorCheck.nativeCall(thread, dxfg_SessionFilter_getInstance(thread, qdValue)) defer { _ = try? ErrorCheck.nativeCall(thread, dxfg_JavaObjectHandler_release(thread, &(filter.pointee.handler))) } - let currentSession = session.native.native let nextSession = try ErrorCheck.nativeCall(thread, - dxfg_Session_getPrevSession(thread, currentSession, filter)) + dxfg_Schedule_getNearestSessionByTime(thread, + schedule, + time, + filter)) let session = try createSession(thread, session: nextSession) return session } diff --git a/DXFeedFramework/Native/Schedule/NativeSession.swift b/DXFeedFramework/Native/Schedule/NativeSession.swift index 2a427f03e..ce25d4324 100644 --- a/DXFeedFramework/Native/Schedule/NativeSession.swift +++ b/DXFeedFramework/Native/Schedule/NativeSession.swift @@ -8,13 +8,7 @@ import Foundation @_implementationOnly import graal_api -class NativeSession { - let native: UnsafeMutablePointer - - init(native: UnsafeMutablePointer) { - self.native = native - } - +class NativeSession: NativeBox { deinit { let thread = currentThread() _ = try? ErrorCheck.nativeCall(thread, dxfg_JavaObjectHandler_release(thread, &(native.pointee.handler))) diff --git a/DXFeedFramework/Schedule/DXSchedule.swift b/DXFeedFramework/Schedule/DXSchedule.swift index 38370703c..f3648ebda 100644 --- a/DXFeedFramework/Schedule/DXSchedule.swift +++ b/DXFeedFramework/Schedule/DXSchedule.swift @@ -74,24 +74,58 @@ public class DXSchedule { /// Returns day that contains specified time. /// - /// This method will throw {@link IllegalArgumentException} if specified time + /// This method will throw exception if specified time /// falls outside of valid date range from 0001-01-02 to 9999-12-30. /// - Parameters: /// - time: the time to search for /// - Throws: GraalException. Rethrows exception from Java.recore public func getDayByTime(time: Long) throws -> ScheduleDay { - var day = try native.getDayByTime(time: time) + let day = try native.getDayByTime(time: time) return day } + /// Returns day for specified day identifier. /// - /// This method will throw {@link IllegalArgumentException} if specified day identifier + /// This method will throw exception if specified day identifier /// falls outside of valid date range from 0001-01-02 to 9999-12-30. /// - Parameters: /// - day: identifier to search for /// - Throws: GraalException. Rethrows exception from Java.recore public func getDayById(day: Int32) throws -> ScheduleDay { - var day = try native.getDayById(dayId: day) + let day = try native.getDayById(dayId: day) return day } + + /// Returns session that contains specified time. + /// + /// This method will throw exception + /// if specified time falls outside of valid date range from 0001-01-02 to 9999-12-30. + /// + /// - Parameters: + /// - time: time the time to search for + /// - Returns: session that contains specified time + /// - Throws: GraalException. Rethrows exception from Java. + public func getSessionByTime(time: Long) throws -> ScheduleSession { + return try native.getSessionByTime(time: time) + } + + /// Returns session that is nearest to the specified time and that is accepted by specified filter. + /// + /// This method will throw exception if specified time + /// falls outside of valid date range from 0001-01-02 to 9999-12-30. + /// If no sessions acceptable by specified filter are found within one year this method will throw exception + /// + /// To find nearest trading session of any type use this code: + /// session = schedule.getNearestSessionByTime(time, SessionFilter.TRADING); + /// To find nearest regular trading session use this code: + /// session = schedule.getNearestSessionByTime(time, SessionFilter.REGULAR); + /// + /// - Parameters: + /// - time: time the time to search for + /// - filter: the filter to test sessions + /// - Returns: session that contains specified time + /// - Throws: GraalException. Rethrows exception from Java. + public func getNearestSessionByTime(time: Long, filter: SessionFilter) throws -> ScheduleSession { + return try native.getNearestSessionByTime(time: time, filter: filter) + } } diff --git a/DXFeedFramework/Schedule/ScheduleDay.swift b/DXFeedFramework/Schedule/ScheduleDay.swift index 15aaec3e6..22f46c8ec 100644 --- a/DXFeedFramework/Schedule/ScheduleDay.swift +++ b/DXFeedFramework/Schedule/ScheduleDay.swift @@ -28,6 +28,8 @@ import Foundation /// Such sessions can be of any appropriate type, trading or non-trading. /// Day may have zero duration as well - e.g. when all time within it is transferred to other days. public class ScheduleDay { + /// Returns native ref + internal var native: NativeDay? /// Returns schedule to which this day belongs. internal var nativeSchedule: NativeSchedule? /// Number of this day since January 1, 1970 (that day has identifier of 0 and previous days have negative identifiers). @@ -74,3 +76,9 @@ extension ScheduleDay { return try nativeSchedule?.getNextDay(after: self, filter: filter) } } + +extension ScheduleDay: Equatable { + public static func == (lhs: ScheduleDay, rhs: ScheduleDay) -> Bool { + return lhs === rhs || lhs.dayId == rhs.dayId + } +} diff --git a/DXFeedFramework/Schedule/ScheduleSession.swift b/DXFeedFramework/Schedule/ScheduleSession.swift index 2b949f153..7e5a8e24e 100644 --- a/DXFeedFramework/Schedule/ScheduleSession.swift +++ b/DXFeedFramework/Schedule/ScheduleSession.swift @@ -38,9 +38,12 @@ public enum ScheduleSessionType { /// Each session completely fits inside a certain day. Day may contain sessions with zero duration - e.g. indices /// that post value once a day. Such sessions can be of any appropriate type, trading or non-trading. public class ScheduleSession { + /// Returns native ref internal let native: NativeSession + /// Returns schedule to which this session belongs. internal let nativeSchedule: NativeSchedule /// Returns start time of this session (inclusive). + /// /// For normal sessions the start time is less than the end time, for empty sessions they are equal. public let startTime: Long /// Returns end time of this session (exclusive). @@ -63,7 +66,6 @@ public class ScheduleSession { } } - extension ScheduleSession { public func getPrevious(filter: SessionFilter) throws -> ScheduleSession? { return try nativeSchedule.getPrevtSession(before: self, filter: filter) @@ -79,6 +81,4 @@ extension ScheduleSession: Equatable { return lhs === rhs || (lhs.endTime == rhs.endTime && lhs.startTime == rhs.startTime && lhs.type == rhs.type) } - - } diff --git a/DXFeedFramework/Utils/TimeUtil.swift b/DXFeedFramework/Utils/TimeUtil.swift index 18d92b2e3..734f27190 100644 --- a/DXFeedFramework/Utils/TimeUtil.swift +++ b/DXFeedFramework/Utils/TimeUtil.swift @@ -9,8 +9,9 @@ import Foundation class TimeUtil { static let second = Long(1000) - static let minute = 60 * Long(1000) - static let day = Long(24 * 60 * 60 * 1000) + static let minute = 60 * second + static let hour = 60 * minute + static let day = 24 * hour static let dateFormatter = { let formatter = DateFormatter() diff --git a/DXFeedFrameworkTests/ScheduleTest.swift b/DXFeedFrameworkTests/ScheduleTest.swift index 61c1151e7..10f03164d 100644 --- a/DXFeedFrameworkTests/ScheduleTest.swift +++ b/DXFeedFrameworkTests/ScheduleTest.swift @@ -59,7 +59,7 @@ final class ScheduleTest: XCTestCase { let timeZone = try schedule.getTimeZone() XCTAssert(timeZone == "Greenwich Mean Time") let day = try schedule.getDayByTime(time: 0) - XCTAssert(day.dayId == 0) + XCTAssert(day.dayId == 0) } catch { XCTAssert(false, "Error \(error)") } @@ -169,10 +169,11 @@ final class ScheduleTest: XCTestCase { var startDay: ScheduleDay? = try schedule?.getDayByTime(time: start) schedule = nil var nextDay: ScheduleDay? = try startDay?.getNext(filter: .any) - var prevDay: ScheduleDay? = try startDay?.getNext(filter: .any) + var prevDay: ScheduleDay? = try startDay?.getPrevious(filter: .any) + XCTAssert(prevDay != nextDay) startDay = nil nextDay = nil - prevDay = nil + prevDay = nil // waiting here for an explosion in deinit let sec = 2 _ = XCTWaiter.wait(for: [expectation(description: "\(sec) seconds waiting")], timeout: TimeInterval(sec)) @@ -187,14 +188,25 @@ final class ScheduleTest: XCTestCase { XCTAssert(startDay?.sessions.count ?? 0 >= 3) do { let firstSession = startDay?.sessions.first - let nextSession = try firstSession?.getNext(filter: .any) XCTAssert(nextSession == startDay?.sessions[1]) let lastSession = startDay?.sessions.last let prevSession = try lastSession?.getPrevious(filter: .any) + let index = (startDay?.sessions.count ?? 0) - 2 + XCTAssert(prevSession == startDay?.sessions[index]) } + startDay = nil // waiting here for an explosion in deinit let sec = 2 _ = XCTWaiter.wait(for: [expectation(description: "\(sec) seconds waiting")], timeout: TimeInterval(sec)) } + + func testNearestSessionByTime() throws { + let schedule = try DXSchedule(scheduleDefinition: "(tz=GMT;0=01000200)") + let value = try schedule.getNearestSessionByTime(time: Int64(Date.now.timeIntervalSince1970) * 1000, + filter: .trading ) + let startTime = value.startTime % TimeUtil.day + let equals = TimeUtil.hour == startTime + XCTAssert(equals) + } }