Implement "holiday", "holiday 20230617" command
This commit is contained in:
@@ -11,24 +11,28 @@ import Foundation
|
||||
extension Date {
|
||||
public var yyyyMMdd: String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.timeZone = TimeZone(abbreviation: "KST")
|
||||
dateFormatter.dateFormat = "yyyyMMdd"
|
||||
return dateFormatter.string(from: self)
|
||||
}
|
||||
|
||||
public var yyyyMMdd_HHmmss_forTime: String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.timeZone = TimeZone(abbreviation: "KST")
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
return dateFormatter.string(from: self)
|
||||
}
|
||||
|
||||
public var yyyyMMdd_HHmmssSSSS_forFile: String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.timeZone = TimeZone(abbreviation: "KST")
|
||||
dateFormatter.dateFormat = "yyyyMMdd_HHmmss_SSSS"
|
||||
return dateFormatter.string(from: self)
|
||||
}
|
||||
|
||||
public var HHmmss: String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.timeZone = TimeZone(abbreviation: "KST")
|
||||
dateFormatter.dateFormat = "HHmmss"
|
||||
return dateFormatter.string(from: self)
|
||||
}
|
||||
@@ -46,12 +50,15 @@ extension String {
|
||||
}
|
||||
let mmStartIndex = index(startIndex, offsetBy: 4)
|
||||
let mmEndIndex = index(mmStartIndex, offsetBy: 2)
|
||||
guard let hh = Int(String(prefix(4))),
|
||||
guard let yyyy = Int(String(prefix(4))),
|
||||
let mm = Int(self[mmStartIndex..<mmEndIndex]),
|
||||
let ss = Int(String(suffix(2))) else {
|
||||
let dd = Int(String(suffix(2))) else {
|
||||
return nil
|
||||
}
|
||||
return (hh, mm, ss)
|
||||
guard yyyy >= 0, mm >= 1, mm <= 12, dd >= 1, dd <= 31 else {
|
||||
return nil
|
||||
}
|
||||
return (yyyy, mm, dd)
|
||||
}
|
||||
|
||||
public var HHmmss: (Int, Int, Int)? {
|
||||
@@ -65,6 +72,9 @@ extension String {
|
||||
let ss = Int(String(suffix(2))) else {
|
||||
return nil
|
||||
}
|
||||
guard hh >= 0, hh <= 12, mm >= 0, mm <= 59, ss >= 0, ss <= 59 else {
|
||||
return nil
|
||||
}
|
||||
return (hh, mm, ss)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ public enum GeneralError: Error {
|
||||
case noCsvFile
|
||||
case invalidCandleCsvFile(String)
|
||||
case incorrectCsvHeaderField(String)
|
||||
case noData
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -347,14 +347,14 @@ extension KissAccount {
|
||||
|
||||
/// 휴장일 확인하기
|
||||
///
|
||||
public func getHolyday(baseDate: Date) async throws -> HolidyResult {
|
||||
public func getHolyday(baseDate: String) async throws -> HolidyResult {
|
||||
return try await withUnsafeThrowingContinuation { continuation in
|
||||
guard let accessToken = accessToken else {
|
||||
continuation.resume(throwing: GeneralError.invalidAccessToken)
|
||||
return
|
||||
}
|
||||
|
||||
let request = Domestic.HolidayRequest(credential: credential, accessToken: accessToken, baseDate: baseDate.yyyyMMdd)
|
||||
let request = Domestic.HolidayRequest(credential: credential, accessToken: accessToken, baseDate: baseDate)
|
||||
request.query { result in
|
||||
switch result {
|
||||
case .success(let result):
|
||||
|
||||
@@ -153,15 +153,14 @@ public struct HolidyResult: Codable {
|
||||
case output = "output"
|
||||
}
|
||||
|
||||
public func isHoliday(_ date: Date) -> Bool {
|
||||
let dateString = Date().yyyyMMdd
|
||||
guard let target = output?.first(where: { $0.baseDate == dateString }) else {
|
||||
return false
|
||||
public func isHoliday(_ day: String) throws -> Bool {
|
||||
guard let target = output?.first(where: { $0.baseDate == day }) else {
|
||||
throw GeneralError.noData
|
||||
}
|
||||
return target.businessDayOpened != .yes
|
||||
}
|
||||
|
||||
public struct OutputDetail: Codable {
|
||||
public struct OutputDetail: Codable, PropertyIterable, ArrayDecodable {
|
||||
/// 기준일자
|
||||
public let baseDate: String
|
||||
|
||||
@@ -188,6 +187,37 @@ public struct HolidyResult: Codable {
|
||||
case peningDay = "opnd_yn"
|
||||
case settlementDay = "sttl_day_yn"
|
||||
}
|
||||
|
||||
public init(array: [String]) throws {
|
||||
guard array.count == 6 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
}
|
||||
self.baseDate = array[0]
|
||||
self.weekday = WeekdayDivision(rawValue: array[1])!
|
||||
self.businessDayOpened = YesNo(rawValue: array[2])!
|
||||
self.tradingDayOpened = YesNo(rawValue: array[3])!
|
||||
self.peningDay = YesNo(rawValue: array[4])!
|
||||
self.settlementDay = YesNo(rawValue: array[5])!
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! OutputDetail(array: Array(repeating: "", count: 22))
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
public static func localizedSymbols() -> [String: String] {
|
||||
[:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Array where Element == HolidyResult.OutputDetail {
|
||||
public func isHoliday(_ dayString: String) throws -> Bool {
|
||||
guard let target = first(where: { $0.baseDate == dayString }) else {
|
||||
throw GeneralError.noData
|
||||
}
|
||||
return target.businessDayOpened != .yes
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -285,39 +285,6 @@ extension String {
|
||||
self = str.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
/*
|
||||
init(firstLineOfFile path: String) throws {
|
||||
guard let handle = FileHandle(forReadingAtPath: path) else {
|
||||
throw GeneralError.cannotReadFile
|
||||
}
|
||||
defer {
|
||||
try? handle.close()
|
||||
}
|
||||
|
||||
var readData = Data()
|
||||
var headerString = ""
|
||||
while (true) {
|
||||
guard let data = try handle.read(upToCount: 512) else {
|
||||
break
|
||||
}
|
||||
readData.append(data)
|
||||
guard let part = String(data: readData, encoding: .utf8) else {
|
||||
continue
|
||||
}
|
||||
|
||||
if let range = part.range(of: "\n") {
|
||||
headerString += part[..<range.lowerBound]
|
||||
break
|
||||
}
|
||||
else {
|
||||
headerString += part
|
||||
}
|
||||
}
|
||||
|
||||
self = headerString
|
||||
}
|
||||
*/
|
||||
|
||||
static func readCsvHeader(fromFile: URL) throws -> [String] {
|
||||
let header = try String(firstLineOfFile: fromFile.path)
|
||||
return header.split(separator: ",").map { String($0) }
|
||||
|
||||
@@ -36,6 +36,10 @@ extension KissConsole {
|
||||
URL.currentDirectory().appending(path: "data/localized-names.csv")
|
||||
}
|
||||
|
||||
static var holidayUrl: URL {
|
||||
URL.currentDirectory().appending(path: "data/holiday.csv")
|
||||
}
|
||||
|
||||
static func productPriceUrl(productNo: String) -> URL {
|
||||
let subPath = "data/\(productNo)/price"
|
||||
let subFile = "\(subPath)/prices.csv"
|
||||
|
||||
@@ -169,11 +169,29 @@ extension KissConsole {
|
||||
|
||||
|
||||
func checkHoliday(_ date: Date) async throws -> Bool {
|
||||
guard await KissContext.shared.targetDate.yyyyMMdd != date.yyyyMMdd else {
|
||||
let day = date.yyyyMMdd
|
||||
guard await KissContext.shared.targetDay != day else {
|
||||
return await KissContext.shared.isHoliday
|
||||
}
|
||||
let isHoliday = try await account!.getHolyday(baseDate: date).isHoliday(date)
|
||||
await KissContext.shared.updateHoliday(isHoliday, targetDate: date)
|
||||
|
||||
do {
|
||||
let holidays = try [HolidyResult.OutputDetail].readCsv(fromFile: KissConsole.holidayUrl)
|
||||
let isHoliday = try holidays.isHoliday(day)
|
||||
await KissContext.shared.updateHoliday(isHoliday, targetDay: day)
|
||||
return isHoliday
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
|
||||
let result = try await account!.getHolyday(baseDate: day)
|
||||
do {
|
||||
try result.output?.writeCsv(toFile: KissConsole.holidayUrl, localized: localized)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
|
||||
let isHoliday = try result.isHoliday(day)
|
||||
await KissContext.shared.updateHoliday(isHoliday, targetDay: day)
|
||||
return isHoliday
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,9 @@ class KissConsole {
|
||||
case updateShop = "update shop"
|
||||
case look = "look"
|
||||
|
||||
// 휴장일
|
||||
case holiday = "holiday"
|
||||
|
||||
// 진열 종목 (시스템에서 제공하는 추천 리스트)
|
||||
case showcase = "showcase"
|
||||
|
||||
@@ -100,7 +103,7 @@ class KissConsole {
|
||||
return true
|
||||
case .shorts, .shortsAll:
|
||||
return false
|
||||
case .loadShop, .updateShop, .look:
|
||||
case .loadShop, .updateShop, .look, .holiday:
|
||||
return false
|
||||
case .showcase:
|
||||
return false
|
||||
@@ -229,6 +232,7 @@ class KissConsole {
|
||||
case .loadShop: await onLoadShop()
|
||||
case .updateShop: await onUpdateShop()
|
||||
case .look: await onLook(args)
|
||||
case .holiday: await onHoliday(args)
|
||||
|
||||
case .showcase: await onShowcase()
|
||||
case .loves: await onLoves()
|
||||
@@ -940,6 +944,28 @@ extension KissConsole {
|
||||
}
|
||||
|
||||
|
||||
private func onHoliday(_ args: [String]) async {
|
||||
var date = Date()
|
||||
if args.count == 1, args[0].utf8.count == 8, let day = args[0].yyyyMMdd {
|
||||
date.change(year: day.0, month: day.1, day: day.2)
|
||||
}
|
||||
|
||||
let targetDay = (Date().yyyyMMdd == date.yyyyMMdd ? "today": date.yyyyMMdd)
|
||||
|
||||
do {
|
||||
let holiday = try await checkHoliday(date)
|
||||
if holiday {
|
||||
print("DONE \(targetDay) is holiday")
|
||||
}
|
||||
else {
|
||||
print("DONE \(targetDay) is business day")
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func onShowcase() async {
|
||||
// TODO: write
|
||||
}
|
||||
|
||||
@@ -11,15 +11,15 @@ import Foundation
|
||||
actor KissContext {
|
||||
static let shared = KissContext()
|
||||
|
||||
private(set) var targetDate: Date = Date(timeIntervalSince1970: 0)
|
||||
private(set) var targetDay: String = "00010101" // yyyyMMdd
|
||||
private(set) var isHoliday: Bool = false
|
||||
private(set) var isResuming: Bool = false
|
||||
|
||||
private init() { }
|
||||
|
||||
func updateHoliday(_ isHolyday: Bool, targetDate: Date) {
|
||||
func updateHoliday(_ isHolyday: Bool, targetDay: String) {
|
||||
self.isHoliday = isHolyday
|
||||
self.targetDate = targetDate
|
||||
self.targetDay = targetDay
|
||||
}
|
||||
|
||||
func update(resuming: Bool) {
|
||||
|
||||
@@ -39,6 +39,7 @@ WIP `modify (PNO) (ONO) (가격) (수량)` | 주문 내역을 변경. (수량)
|
||||
`load shop` | data/shop-products.csv 로부터 전체 상품을 로딩.
|
||||
`update shop` | **금융위원회_KRX상장종목정보** 로부터 전체 상품을 얻어서 **data/shop-products.csv** 로 저장.
|
||||
`look (상품명)` | (상품명) 에 해당되는 PNO 를 표시함.
|
||||
`holiday [yyyyMMdd]` | 휴장일 여부를 판단함. yyyymmDD 를 생략하면 오늘 날짜로 확인. **data/holiday.csv** 로 저장.
|
||||
WIP `showcase` | 추천 상품을 제안함.
|
||||
`loves` | 관심 종목 전체를 열람. profile.json 에 저장된 관심 종목을 표시함.
|
||||
`love (탭).(번호) (PNO)` | 관심 종목에 추가함. (번호) 를 지정하지 않으면 (탭) 마지막에 추가함.
|
||||
|
||||
2
bin/data
2
bin/data
Submodule bin/data updated: c4f8c97d26...0c263342c6
Reference in New Issue
Block a user