Implement "holiday", "holiday 20230617" command

This commit is contained in:
2023-06-15 07:05:20 +09:00
parent 9503f02f92
commit 5760daaf1f
11 changed files with 108 additions and 51 deletions

View File

@@ -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)
}
}

View File

@@ -42,6 +42,7 @@ public enum GeneralError: Error {
case noCsvFile
case invalidCandleCsvFile(String)
case incorrectCsvHeaderField(String)
case noData
}

View File

@@ -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):

View File

@@ -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
}
}

View File

@@ -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) }

View File

@@ -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"

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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)` | 관심 종목에 추가함. (번호) 를 지정하지 않으면 (탭) 마지막에 추가함.