🙌

Swiftで経過時間を簡単な形式で表示する方法

2022/03/14に公開

やりたいこと

Twitterの投稿日などの今からどのくらい前にツイートがあったかの日付の差分を表示するものです。ここでは単位を1つだけに絞って表示することを目標とします

以下のクラスを使ってやっていきます

  • Date
  • DateComponents
  • DateComponentsFormatter
  • Calendar

実装

extension Calendar {
  public func durationString(candidate components: [Calendar.Component], style unitsStyle: DateComponentsFormatter.UnitsStyle = .abbreviated, from start: Date, to end: Date) -> String? {
    for component in components {
      let dateComponents = self.dateComponents([component], from: start, to: end)
      if let time = dateComponents.getTime(with: component) {
        if time > 0 {
          let formatter = DateComponentsFormatter()
          formatter.allowedUnits = component.unit
          formatter.unitsStyle = unitsStyle
          formatter.calendar = self
          return formatter.string(from: dateComponents)
        }
      }
    }
    
    return nil
  }
}
 
extension DateComponents {
  public func getTime(with component: Calendar.Component) -> Int? {
    switch component {
      case .second: return second
      case .era: return era
      case .year: return year
      case .month: return month
      case .day: return day
      case .hour: return hour
      case .minute: return minute
      case .weekday: return weekday
      case .weekdayOrdinal: return weekdayOrdinal
      case .quarter: return quarter
      case .weekOfMonth: return weekOfMonth
      case .weekOfYear: return weekOfYear
      case .yearForWeekOfYear: return yearForWeekOfYear
      case .nanosecond: return nanosecond
      default:
        fatalError()
    }
  }
}

extension Calendar.Component {
  public var unit: NSCalendar.Unit {
    switch self {
      case .second: return .second
      case .era: return .era
      case .year: return .year
      case .month: return .month
      case .day: return .day
      case .hour: return .hour
      case .minute: return .minute
      case .weekday: return .weekday
      case .weekdayOrdinal: return .weekdayOrdinal
      case .quarter: return .quarter
      case .weekOfMonth: return .weekOfMonth
      case .weekOfYear: return .weekOfYear
      case .yearForWeekOfYear: return .yearForWeekOfYear
      case .nanosecond: return .nanosecond
      case .calendar: return .calendar
      case .timeZone: return .timeZone
      @unknown default:
        fatalError()
    }
  }
}

テストコード

var calendar = Calendar.current
calendar.locale = .init(identifier: "ja_JP")

let now = Date()

let oneDayAgo: Date = {
  var components = DateComponents()
  components.day = -1
  return calendar.date(byAdding: components, to: now)!
}()

let oneHourAgo: Date = {
  var components = DateComponents()
  components.hour = -1
  return calendar.date(byAdding: components, to: now)!
}()

let oneMinuteAgo: Date = {
  var components = DateComponents()
  components.minute = -1
  return calendar.date(byAdding: components, to: now)!
}()

let oneSecondAgo: Date = {
  var components = DateComponents()
  components.second = -1
  return calendar.date(byAdding: components, to: now)!
}()

let candidates: [Calendar.Component] = [.day, .hour, .minute, .second]

let dayDuration = calendar.durationString(candidate: candidates, from: oneDayAgo, to: now)
// "1日"

let hourDuration = calendar.durationString(candidate: candidates, from: oneHourAgo, to: now)
// "1時間"

let minuteDuration = calendar.durationString(candidate: candidates, from: oneMinuteAgo, to: now)
// "1分"

let secondDuration = calendar.durationString(candidate: candidates, from: oneSecondAgo, to: now)
// "1秒"

まとめ

Calendarのextensionとして実装することでLocaleの設定などもできます。

同じように見えるCalendar.ComponentNSCalendar.Unitを変換しているだけの
extensionもあり微妙に見えるところもあるので、何か改善点などがあればコメントで教えてください。

Discussion