From e4629bb8e9d4d5d4865ec43e8031cdf3d9c4363a Mon Sep 17 00:00:00 2001 From: luckyyy Date: Sun, 12 Jan 2025 20:06:43 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[Feat/#25]=20Schedule=20Picker=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SchedulePicker/SchedulePicker.swift | 113 ++++++++++++++++++ .../SchedulePicker/SchedulePickerCell.swift | 96 +++++++++++++++ .../SchedulePicker/SchedulePickerLayout.swift | 64 ++++++++++ 3 files changed, 273 insertions(+) create mode 100644 Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePicker.swift create mode 100644 Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePickerCell.swift create mode 100644 Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePickerLayout.swift diff --git a/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePicker.swift b/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePicker.swift new file mode 100644 index 0000000..f1ab322 --- /dev/null +++ b/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePicker.swift @@ -0,0 +1,113 @@ +// +// SchedulePicker.swift +// Noostak_iOS +// +// Created by 오연서 on 1/10/25. +// + +import UIKit + +final class SchedulePicker: UICollectionView { + enum Mode { + case editMode + case readMode + } + + private let layout = SchedulePickerLayout() + private let timeHeaders: [String] + private let dateHeaders: [String] + private let mode: Mode + private var selectedCells: Set = [] // edit Mode + private var cellAvailability: [IndexPath: Int] = [:] // read Mode + + init(timeHeaders: [String], dateHeaders: [String], mode: Mode) { + self.timeHeaders = timeHeaders + self.dateHeaders = dateHeaders + self.mode = mode + super.init(frame: .zero, collectionViewLayout: layout) + self.layout.configure(totalRows: timeHeaders.count + 1, totalColumns: dateHeaders.count + 1) + self.register(SchedulePickerCell.self, forCellWithReuseIdentifier: SchedulePickerCell.identifier) + self.cellAvailability = calculateCellAvailability(totalRows: timeHeaders.count + 1, + totalColumns: dateHeaders.count + 1, + //Fix: mockMemberStartTime 변경 필요 + startTimes: mockMemberStartTime) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func addSelectedCell(at indexPath: IndexPath) { + guard mode == .editMode else { return } + if let cell = cellForItem(at: indexPath) as? SchedulePickerCell { + cell.isSelectedCell.toggle() + if cell.isSelectedCell { + selectedCells.insert(indexPath) + } else { + selectedCells.remove(indexPath) + } + } + } + + func configureCellBackground(_ cell: SchedulePickerCell, for indexPath: IndexPath, participants: Int) { + guard mode == .readMode else { return } + let count = cellAvailability[indexPath, default: 0] + let ratio = Float(count) / Float(participants) + + switch ratio { + case 0.01...0.2: + cell.backgroundColor = .appBlue50 + case 0.2...0.4: + cell.backgroundColor = .appBlue200 + case 0.4...0.6: + cell.backgroundColor = .appBlue400 + case 0.6...0.8: + cell.backgroundColor = .appBlue700 + case 0.8...1: + cell.backgroundColor = .appBlue800 + default: + cell.backgroundColor = .clear + } + } +} + +/// .ReadMode 색상 반환 로직 +extension SchedulePicker { + ///각 시간에 대한 가능 인원 계산 + private func calculateCellAvailability(totalRows: Int, totalColumns: Int, startTimes: [String]) -> [IndexPath: Int] { + var cellAvailability: [IndexPath: Int] = [:] + let dateTimeMapping = createDateTimeMapping(totalRows: totalRows, totalColumns: totalColumns) + for startTime in startTimes { + if let indexPath = dateTimeMapping[startTime] { + cellAvailability[indexPath, default: 0] += 1 + } + } + return cellAvailability + } + + /// 시각 - cell 매핑 + private func createDateTimeMapping(totalRows: Int, totalColumns: Int) -> [String: IndexPath] { + var mapping: [String: IndexPath] = [:] + //FIX: mockDateList 변경 필요 + let dates = mockDateList.map { String($0.prefix(10)) } + + for row in 1.. 0 { + self.textLabel.text = dateHeaders[column - 1] + } else if column == 0, row > 0 { + self.textLabel.text = "\(timeHeaders[row - 1])시" + } else { + self.textLabel.text = "" + } + + /// 테이블 모서리 둥글게 + if isTopLeft || isTopRight || isBottomLeft || isBottomRight { + self.layer.cornerRadius = 10 + self.layer.masksToBounds = true + self.layer.borderColor = UIColor.appGray200.cgColor + if isTopLeft { + self.layer.maskedCorners = [.layerMinXMinYCorner] + } else if isTopRight { + self.layer.maskedCorners = [.layerMaxXMinYCorner] + } else if isBottomLeft { + self.layer.maskedCorners = [.layerMinXMaxYCorner] + } else if isBottomRight { + self.layer.maskedCorners = [.layerMaxXMaxYCorner] + } + } else { + self.layer.cornerRadius = 0 + } + } +} diff --git a/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePickerLayout.swift b/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePickerLayout.swift new file mode 100644 index 0000000..6419f66 --- /dev/null +++ b/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePickerLayout.swift @@ -0,0 +1,64 @@ +// +// SchedulePickerLayout.swift +// Noostak_iOS +// +// Created by 오연서 on 1/10/25. +// + +import UIKit + +final class SchedulePickerLayout: UICollectionViewFlowLayout { + private let fixedFirstColumnWidth: CGFloat = 42 + private let fixedFirstRowHeight: CGFloat = 36 + + private var totalRows: Int = 0 + private var totalColumns: Int = 0 + private let minimumSpacing: CGFloat = 0 + + func configure(totalRows: Int = 0, totalColumns: Int = 0) { + self.totalRows = totalRows + self.totalColumns = totalColumns + invalidateLayout() + } + + override func prepare() { + super.prepare() + guard let collectionView = collectionView else { return } + let remainingWidth = collectionView.bounds.width - fixedFirstColumnWidth - CGFloat(totalColumns - 1) * minimumSpacing + let dynamicColumnWidth = remainingWidth / CGFloat(totalColumns - 1) + let dynamicRowHeight = 32.0 + + itemSize = CGSize(width: dynamicColumnWidth, height: dynamicRowHeight) + minimumLineSpacing = 0 + minimumInteritemSpacing = 0 + sectionInset = .zero + } + + override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + let attributes = super.layoutAttributesForElements(in: rect) + attributes?.forEach { layoutAttribute in + let indexPath = layoutAttribute.indexPath + let column = indexPath.item % totalColumns + let row = indexPath.item / totalColumns + + // 첫 번째 열의 너비 고정, 열 간 간격 조정 + if column == 0 { + layoutAttribute.frame.size.width = fixedFirstColumnWidth + layoutAttribute.frame.origin.x = 0 + } else { // 두 번째 열 이후 + let previousColumnRight = fixedFirstColumnWidth + CGFloat(column - 1) * (itemSize.width + minimumInteritemSpacing) + layoutAttribute.frame.origin.x = previousColumnRight + } + + // 첫 번째 행의 높이 고정, 행 간 간격 조정 + if indexPath.item < totalColumns { + layoutAttribute.frame.size.height = fixedFirstRowHeight + layoutAttribute.frame.origin.y = 0 + } else { // 두 번째 행 이후 + let previousRowBottom = fixedFirstRowHeight + CGFloat(row - 1) * (itemSize.height + minimumLineSpacing) + layoutAttribute.frame.origin.y = previousRowBottom + } + } + return attributes + } +} From 57826388b9437c668514b6b5f9148c8f7993ce4d Mon Sep 17 00:00:00 2001 From: luckyyy Date: Sun, 12 Jan 2025 20:15:41 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[Feat/#25]=20Date=20fomat=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Global/Utils/NSTDateUtility.swift | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/Noostak_iOS/Noostak_iOS/Global/Utils/NSTDateUtility.swift b/Noostak_iOS/Noostak_iOS/Global/Utils/NSTDateUtility.swift index 7fc453d..6487dc0 100644 --- a/Noostak_iOS/Noostak_iOS/Global/Utils/NSTDateUtility.swift +++ b/Noostak_iOS/Noostak_iOS/Global/Utils/NSTDateUtility.swift @@ -47,13 +47,18 @@ public final class NSTDateUtility { public extension NSTDateUtility { enum NSTDateFormatter { + case yyyyMMddTHHmmss case yyyyMMddHHmmss case yyyyMMdd case yyyyMM case EE + case HH + case MMddEE var format: String { switch self { + case .yyyyMMddTHHmmss: + return "yyyy-MM-dd'T'HH:mm:ss" case .yyyyMMddHHmmss: return "yyyy-MM-dd HH:mm:ss" case .yyyyMMdd: @@ -62,6 +67,10 @@ public extension NSTDateUtility { return "yyyy-MM" case .EE: return "EE" + case .HH: + return "HH" + case .MMddEE: + return "EE\nMM/dd" } } } @@ -77,3 +86,42 @@ public extension NSTDateUtility { } } } + +func dateList(_ dateStrings: [String]) -> [String] { + let formatter = NSTDateUtility(format: .yyyyMMddTHHmmss) // ISO 8601 형식 + let displayFormatter = NSTDateUtility(format: .MMddEE) // 출력 형식 + + return dateStrings.compactMap { dateString in + switch formatter.date(from: dateString) { + case .success(let date): + return displayFormatter.string(from: date) + case .failure(let error): + print("Failed to parse date \(dateString): \(error.localizedDescription)") + return nil + } + } +} + +func timeList(_ startTime: String, _ endTime: String) -> [String] { + let formatter = NSTDateUtility(format: .yyyyMMddTHHmmss) // ISO 8601 형식 + var result: [String] = [] + + switch (formatter.date(from: startTime), formatter.date(from: endTime)) { + case (.success(let start), .success(let end)): + let calendar = Calendar.current + var current = start + + while current <= end { + result.append(NSTDateUtility(format: .HH).string(from: current)) // 출력 형식 + if let nextHour = calendar.date(byAdding: .hour, value: 1, to: current) { + current = nextHour + } else { + break + } + } + default: + print("Failed to parse start or end time.") + return [] + } + return result +} From e75118659410802a9cf0cf62893fb9f2ddd6c46a Mon Sep 17 00:00:00 2001 From: luckyyy Date: Mon, 13 Jan 2025 23:12:49 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[Feat/#25]=201=EC=B0=A8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SchedulePicker/SchedulePicker.swift | 83 ++++++++-------- .../SchedulePicker/SchedulePickerCell.swift | 29 +++--- .../SchedulePicker/SchedulePickerLayout.swift | 99 ++++++++++--------- .../Global/Utils/NSTDateUtility.swift | 62 ++++++------ 4 files changed, 142 insertions(+), 131 deletions(-) diff --git a/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePicker.swift b/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePicker.swift index f1ab322..db99f9f 100644 --- a/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePicker.swift +++ b/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePicker.swift @@ -25,27 +25,32 @@ final class SchedulePicker: UICollectionView { self.dateHeaders = dateHeaders self.mode = mode super.init(frame: .zero, collectionViewLayout: layout) - self.layout.configure(totalRows: timeHeaders.count + 1, totalColumns: dateHeaders.count + 1) + setupLayout() + setupFoundation() + } + + private func setupLayout() { + layout.configure(totalRows: timeHeaders.count + 1, totalColumns: dateHeaders.count + 1) + } + + private func setupFoundation() { self.register(SchedulePickerCell.self, forCellWithReuseIdentifier: SchedulePickerCell.identifier) - self.cellAvailability = calculateCellAvailability(totalRows: timeHeaders.count + 1, - totalColumns: dateHeaders.count + 1, - //Fix: mockMemberStartTime 변경 필요 - startTimes: mockMemberStartTime) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + // MARK: Public Methods func addSelectedCell(at indexPath: IndexPath) { - guard mode == .editMode else { return } - if let cell = cellForItem(at: indexPath) as? SchedulePickerCell { - cell.isSelectedCell.toggle() - if cell.isSelectedCell { - selectedCells.insert(indexPath) - } else { - selectedCells.remove(indexPath) - } + guard mode == .editMode, + let cell = cellForItem(at: indexPath) as? SchedulePickerCell + else { return } + cell.isSelectedCell.toggle() + if cell.isSelectedCell { + selectedCells.insert(indexPath) + } else { + selectedCells.remove(indexPath) } } @@ -53,30 +58,25 @@ final class SchedulePicker: UICollectionView { guard mode == .readMode else { return } let count = cellAvailability[indexPath, default: 0] let ratio = Float(count) / Float(participants) - - switch ratio { - case 0.01...0.2: - cell.backgroundColor = .appBlue50 - case 0.2...0.4: - cell.backgroundColor = .appBlue200 - case 0.4...0.6: - cell.backgroundColor = .appBlue400 - case 0.6...0.8: - cell.backgroundColor = .appBlue700 - case 0.8...1: - cell.backgroundColor = .appBlue800 - default: - cell.backgroundColor = .clear - } + cell.backgroundColor = calculateBackgroundColor(for: ratio) + } + + func updateCellAvailability(with dateList: [String], startTimes: [String]) { + guard mode == .readMode else { return } + self.cellAvailability = calculateCellAvailability(totalRows: self.timeHeaders.count + 1, + totalColumns: self.dateHeaders.count + 1, + dateList: dateList, + startTimes: startTimes) + reloadData() } } -/// .ReadMode 색상 반환 로직 +// MARK: Internal Logics extension SchedulePicker { ///각 시간에 대한 가능 인원 계산 - private func calculateCellAvailability(totalRows: Int, totalColumns: Int, startTimes: [String]) -> [IndexPath: Int] { + private func calculateCellAvailability(totalRows: Int, totalColumns: Int, dateList: [String], startTimes: [String]) -> [IndexPath: Int] { var cellAvailability: [IndexPath: Int] = [:] - let dateTimeMapping = createDateTimeMapping(totalRows: totalRows, totalColumns: totalColumns) + let dateTimeMapping = createDateTimeMapping(totalRows: totalRows, totalColumns: totalColumns, dateList: dateList) for startTime in startTimes { if let indexPath = dateTimeMapping[startTime] { cellAvailability[indexPath, default: 0] += 1 @@ -86,10 +86,9 @@ extension SchedulePicker { } /// 시각 - cell 매핑 - private func createDateTimeMapping(totalRows: Int, totalColumns: Int) -> [String: IndexPath] { + private func createDateTimeMapping(totalRows: Int, totalColumns: Int, dateList: [String]) -> [String: IndexPath] { var mapping: [String: IndexPath] = [:] - //FIX: mockDateList 변경 필요 - let dates = mockDateList.map { String($0.prefix(10)) } + let dates = dateList.map { String($0.prefix(10)) } for row in 1.. UIColor { + switch ratio { + case 0.01...0.2: return .appBlue50 + case 0.2...0.4: return .appBlue200 + case 0.4...0.6: return .appBlue400 + case 0.6...0.8: return .appBlue700 + case 0.8...1: return .appBlue800 + default: return .clear + } } } diff --git a/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePickerCell.swift b/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePickerCell.swift index 5085491..9814bf4 100644 --- a/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePickerCell.swift +++ b/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePickerCell.swift @@ -56,16 +56,10 @@ final class SchedulePickerCell: UICollectionViewCell { } func configureHeader(for indexPath: IndexPath, dateHeaders: [String], timeHeaders: [String]) { - let totalRows = timeHeaders.count + 1 let totalColumns = dateHeaders.count + 1 let row = indexPath.item / totalColumns let column = indexPath.item % totalColumns - let isTopLeft = indexPath.item == 0 - let isTopRight = indexPath.item == totalColumns - 1 - let isBottomLeft = indexPath.item == (totalRows - 1) * totalColumns - let isBottomRight = indexPath.item == totalRows * totalColumns - 1 - /// dateHeader, timeHeader text binding if row == 0, column > 0 { self.textLabel.text = dateHeaders[column - 1] @@ -74,20 +68,31 @@ final class SchedulePickerCell: UICollectionViewCell { } else { self.textLabel.text = "" } + } + + func configureTableRoundness(for indexPath: IndexPath, dateHeaders: [String], timeHeaders: [String]) { + let totalRows = timeHeaders.count + 1 + let totalColumns = dateHeaders.count + 1 + + let isTopLeft = indexPath.item == 0 + let isTopRight = indexPath.item == totalColumns - 1 + let isBottomLeft = indexPath.item == (totalRows - 1) * totalColumns + let isBottomRight = indexPath.item == totalRows * totalColumns - 1 - /// 테이블 모서리 둥글게 if isTopLeft || isTopRight || isBottomLeft || isBottomRight { self.layer.cornerRadius = 10 self.layer.masksToBounds = true - self.layer.borderColor = UIColor.appGray200.cgColor - if isTopLeft { + switch true { + case isTopLeft: self.layer.maskedCorners = [.layerMinXMinYCorner] - } else if isTopRight { + case isTopRight: self.layer.maskedCorners = [.layerMaxXMinYCorner] - } else if isBottomLeft { + case isBottomLeft: self.layer.maskedCorners = [.layerMinXMaxYCorner] - } else if isBottomRight { + case isBottomRight: self.layer.maskedCorners = [.layerMaxXMaxYCorner] + default: + break } } else { self.layer.cornerRadius = 0 diff --git a/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePickerLayout.swift b/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePickerLayout.swift index 6419f66..0715c3b 100644 --- a/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePickerLayout.swift +++ b/Noostak_iOS/Noostak_iOS/Global/Components/SchedulePicker/SchedulePickerLayout.swift @@ -7,58 +7,59 @@ import UIKit -final class SchedulePickerLayout: UICollectionViewFlowLayout { - private let fixedFirstColumnWidth: CGFloat = 42 - private let fixedFirstRowHeight: CGFloat = 36 - - private var totalRows: Int = 0 - private var totalColumns: Int = 0 - private let minimumSpacing: CGFloat = 0 - - func configure(totalRows: Int = 0, totalColumns: Int = 0) { - self.totalRows = totalRows - self.totalColumns = totalColumns - invalidateLayout() - } - - override func prepare() { - super.prepare() - guard let collectionView = collectionView else { return } - let remainingWidth = collectionView.bounds.width - fixedFirstColumnWidth - CGFloat(totalColumns - 1) * minimumSpacing - let dynamicColumnWidth = remainingWidth / CGFloat(totalColumns - 1) - let dynamicRowHeight = 32.0 +extension SchedulePicker { + final class SchedulePickerLayout: UICollectionViewFlowLayout { + private let fixedFirstColumnWidth: CGFloat = 42 + private let fixedFirstRowHeight: CGFloat = 36 - itemSize = CGSize(width: dynamicColumnWidth, height: dynamicRowHeight) - minimumLineSpacing = 0 - minimumInteritemSpacing = 0 - sectionInset = .zero - } - - override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { - let attributes = super.layoutAttributesForElements(in: rect) - attributes?.forEach { layoutAttribute in - let indexPath = layoutAttribute.indexPath - let column = indexPath.item % totalColumns - let row = indexPath.item / totalColumns - - // 첫 번째 열의 너비 고정, 열 간 간격 조정 - if column == 0 { - layoutAttribute.frame.size.width = fixedFirstColumnWidth - layoutAttribute.frame.origin.x = 0 - } else { // 두 번째 열 이후 - let previousColumnRight = fixedFirstColumnWidth + CGFloat(column - 1) * (itemSize.width + minimumInteritemSpacing) - layoutAttribute.frame.origin.x = previousColumnRight - } + private var totalRows: Int = 0 + private var totalColumns: Int = 0 + private let minimumSpacing: CGFloat = 0 + + func configure(totalRows: Int = 0, totalColumns: Int = 0) { + self.totalRows = totalRows + self.totalColumns = totalColumns + } + + override func prepare() { + super.prepare() + guard let collectionView = collectionView else { return } + let remainingWidth = collectionView.bounds.width - fixedFirstColumnWidth - CGFloat(totalColumns - 1) * minimumSpacing + let dynamicColumnWidth = remainingWidth / CGFloat(totalColumns - 1) + let dynamicRowHeight = 32.0 - // 첫 번째 행의 높이 고정, 행 간 간격 조정 - if indexPath.item < totalColumns { - layoutAttribute.frame.size.height = fixedFirstRowHeight - layoutAttribute.frame.origin.y = 0 - } else { // 두 번째 행 이후 - let previousRowBottom = fixedFirstRowHeight + CGFloat(row - 1) * (itemSize.height + minimumLineSpacing) - layoutAttribute.frame.origin.y = previousRowBottom + itemSize = CGSize(width: dynamicColumnWidth, height: dynamicRowHeight) + minimumLineSpacing = 0 + minimumInteritemSpacing = 0 + sectionInset = .zero + } + + override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + let attributes = super.layoutAttributesForElements(in: rect) + attributes?.forEach { layoutAttribute in + let indexPath = layoutAttribute.indexPath + let column = indexPath.item % totalColumns + let row = indexPath.item / totalColumns + + // 첫 번째 열의 너비 고정, 열 간 간격 조정 + if column == 0 { + layoutAttribute.frame.size.width = fixedFirstColumnWidth + layoutAttribute.frame.origin.x = 0 + } else { // 두 번째 열 이후 + let previousColumnRight = fixedFirstColumnWidth + CGFloat(column - 1) * (itemSize.width + minimumInteritemSpacing) + layoutAttribute.frame.origin.x = previousColumnRight + } + + // 첫 번째 행의 높이 고정, 행 간 간격 조정 + if indexPath.item < totalColumns { + layoutAttribute.frame.size.height = fixedFirstRowHeight + layoutAttribute.frame.origin.y = 0 + } else { // 두 번째 행 이후 + let previousRowBottom = fixedFirstRowHeight + CGFloat(row - 1) * (itemSize.height + minimumLineSpacing) + layoutAttribute.frame.origin.y = previousRowBottom + } } + return attributes } - return attributes } } diff --git a/Noostak_iOS/Noostak_iOS/Global/Utils/NSTDateUtility.swift b/Noostak_iOS/Noostak_iOS/Global/Utils/NSTDateUtility.swift index 6487dc0..e3e029d 100644 --- a/Noostak_iOS/Noostak_iOS/Global/Utils/NSTDateUtility.swift +++ b/Noostak_iOS/Noostak_iOS/Global/Utils/NSTDateUtility.swift @@ -87,41 +87,43 @@ public extension NSTDateUtility { } } -func dateList(_ dateStrings: [String]) -> [String] { - let formatter = NSTDateUtility(format: .yyyyMMddTHHmmss) // ISO 8601 형식 - let displayFormatter = NSTDateUtility(format: .MMddEE) // 출력 형식 - - return dateStrings.compactMap { dateString in - switch formatter.date(from: dateString) { - case .success(let date): - return displayFormatter.string(from: date) - case .failure(let error): - print("Failed to parse date \(dateString): \(error.localizedDescription)") - return nil +extension NSTDateUtility { + static func dateList(_ dateStrings: [String]) -> [String] { + let formatter = NSTDateUtility(format: .yyyyMMddTHHmmss) // ISO 8601 형식 + let displayFormatter = NSTDateUtility(format: .MMddEE) // 출력 형식 + + return dateStrings.compactMap { dateString in + switch formatter.date(from: dateString) { + case .success(let date): + return displayFormatter.string(from: date) + case .failure(let error): + print("Failed to parse date \(dateString): \(error.localizedDescription)") + return nil + } } } -} -func timeList(_ startTime: String, _ endTime: String) -> [String] { - let formatter = NSTDateUtility(format: .yyyyMMddTHHmmss) // ISO 8601 형식 - var result: [String] = [] - - switch (formatter.date(from: startTime), formatter.date(from: endTime)) { - case (.success(let start), .success(let end)): - let calendar = Calendar.current - var current = start + static func timeList(_ startTime: String, _ endTime: String) -> [String] { + let formatter = NSTDateUtility(format: .yyyyMMddTHHmmss) // ISO 8601 형식 + var result: [String] = [] - while current <= end { - result.append(NSTDateUtility(format: .HH).string(from: current)) // 출력 형식 - if let nextHour = calendar.date(byAdding: .hour, value: 1, to: current) { - current = nextHour - } else { - break + switch (formatter.date(from: startTime), formatter.date(from: endTime)) { + case (.success(let start), .success(let end)): + let calendar = Calendar.current + var current = start + + while current <= end { + result.append(NSTDateUtility(format: .HH).string(from: current)) // 출력 형식 + if let nextHour = calendar.date(byAdding: .hour, value: 1, to: current) { + current = nextHour + } else { + break + } } + default: + print("Failed to parse start or end time.") + return [] } - default: - print("Failed to parse start or end time.") - return [] + return result } - return result }