TimeSpan.swift
//
// TimeSpan.swift
// SeatNinjaBase
//
// Created by odyth on 12/15/16.
// Copyright © 2016 SeatNinja Inc. All rights reserved.
//
import Foundation
public struct TimeSpan: Comparable, Equatable, CustomStringConvertible, Hashable, Codable
{
public static let maxValue = TimeSpan(ticks: Int64.max)
public static let minValue = TimeSpan(ticks: Int64.min)
public static let zero = TimeSpan()
public private(set) var ticks: Int64 = 0
public init()
{
self.ticks = 0
}
public init(ticks: Int64)
{
self.ticks = ticks
}
public init(hours: Int, minutes: Int, seconds: Int)
{
// totalSeconds is bounded by 2^31 * 2^12 + 2^31 * 2^8 + 2^31,
// which is less than 2^44, meaning we won't overflow totalSeconds.
let totalSeconds = Int64(hours) * 3600 + Int64(minutes) * 60 + Int64(seconds)
assert(totalSeconds <= Time.maxSeconds && totalSeconds >= Time.minSeconds)
self.ticks = totalSeconds * Time.ticksPerSecond
}
public init(days: Int, hours: Int, minutes: Int, seconds: Int)
{
self.init(days:days, hours:hours, minutes:minutes, seconds:seconds, milliseconds:0)
}
public init(days: Int, hours: Int, minutes: Int, seconds: Int, milliseconds: Int)
{
let totalMilliSeconds:Int64 = (Int64(days) * Int64(Time.oneDay) +
Int64(hours) * Int64(Time.oneHour) +
Int64(minutes) * Int64(Time.oneMinute) + Int64(seconds)) * 1000 +
Int64(milliseconds)
assert(totalMilliSeconds <= Time.maxMilliSeconds && totalMilliSeconds >= Time.minMilliSeconds)
self.init(ticks:totalMilliSeconds * Time.ticksPerMillisecond)
}
public var days: Int
{
return Int(ticks / Time.ticksPerDay )
}
public var hours: Int
{
return Int((ticks / Time.ticksPerHour) % 24)
}
public var minutes: Int
{
return Int((ticks / Time.ticksPerMinute) % 60)
}
public var seconds: Int
{
return Int((ticks / Time.ticksPerSecond) % 60)
}
public var milliseconds: Int
{
return Int((ticks / Time.ticksPerMillisecond) % 1000)
}
public var totalDays: Double
{
return Double(ticks) * Time.daysPerTick
}
public var totalHours: Double
{
return Double(ticks) * Time.hoursPerTick
}
public var totalMinutes: Double
{
return Double(ticks) * Time.minutesPerTick
}
public var totalSeconds: Double
{
return Double(ticks) * Time.secondsPerTick
}
public var totalMilliseconds: Double
{
let temp = Double(ticks) * Time.millisecondsPerTick
if temp > Double(Time.maxMilliSeconds)
{
return Double(Time.maxMilliSeconds)
}
if temp < Double(Time.minMilliSeconds)
{
return Double(Time.minMilliSeconds)
}
return temp
}
public var timeInterval: TimeInterval
{
return totalSeconds
}
public var isMaxValue: Bool
{
return ticks == TimeSpan.maxValue.ticks
}
public var isMinValue: Bool
{
return ticks == TimeSpan.minValue.ticks
}
public func add(timeSpan: TimeSpan) -> TimeSpan
{
let result = ticks.addingReportingOverflow(timeSpan.ticks)
assert(result.overflow == false)
return TimeSpan(ticks:result.partialValue)
}
public func subtract(timeSpan: TimeSpan) -> TimeSpan
{
let result = ticks.subtractingReportingOverflow(timeSpan.ticks)
assert(result.overflow == false)
return TimeSpan(ticks:result.partialValue)
}
public func duration() -> TimeSpan
{
assert(ticks != TimeSpan.minValue.ticks, "overflow duration")
return TimeSpan(ticks: ticks >= 0 ? ticks : -ticks)
}
public func negate() -> TimeSpan
{
assert(ticks != TimeSpan.minValue.ticks, "overflow negate")
return TimeSpan(ticks:-ticks)
}
public static func from(days: Double) -> TimeSpan
{
return interval(days, scale:Time.millisPerDay)
}
public static func from(hours: Double) -> TimeSpan
{
return interval(hours, scale:Time.millisPerHour)
}
public static func from(minutes: Double) -> TimeSpan
{
return interval(minutes, scale:Time.millisPerMinute)
}
public static func from(seconds: Double) -> TimeSpan
{
return interval(seconds, scale:Time.millisPerSecond)
}
public static func from(milliseconds: Double) -> TimeSpan
{
return interval(milliseconds, scale:1)
}
public static func from(ticks: Int64) -> TimeSpan
{
return TimeSpan(ticks:ticks)
}
public static func from(string: String) -> TimeSpan
{
return TimeSpan(ticks: parse(string: string))
}
private static func interval(_ value: Double, scale: Int) -> TimeSpan
{
assert(Double.nan != value, "value cannot be nan")
let tmp = value * Double(scale)
let millis = tmp + (value >= 0 ? 0.5 : -0.5)
assert(!(Int64(millis) > Int64.max / Time.ticksPerMillisecond) || (Int64(millis) < Int64.min / Time.ticksPerMillisecond), "timespan too long")
let result = Int64(millis) * Time.ticksPerMillisecond
return TimeSpan(ticks:result)
}
private static func parse(string: String) -> Int64
{
var days: Int64 = 0
var hours: Int64 = 0
var minutes: Int64 = 0
var seconds: Int64 = 0
var milliseconds: Int64 = 0
let colonParts = string.components(separatedBy: ":")
let hoursString = colonParts.first!
if hoursString.range(of: ".") != nil
{
let hourParts = hoursString.components(separatedBy: ".")
days = Int64(hourParts[0])!
hours = Int64(hourParts[1])!
}
else
{
hours = Int64(hoursString)!
}
minutes = Int64(colonParts[1])!
var millisecondsString: String? = nil
if colonParts.count == 3
{
let secondsString = colonParts.last!
if secondsString .range(of: ".") != nil
{
let secondParts = secondsString.components(separatedBy: ".")
seconds = Int64(secondParts[0])!
millisecondsString = secondParts[1]
milliseconds = Int64(millisecondsString!)!
}
else
{
seconds = Int64(secondsString)!
}
}
var time = abs(days) * 3600 * 24
time += abs(hours) * 3600
time += abs(minutes) * 60
time += abs(seconds)
time *= 1000
if milliseconds != 0
{
var lowerLimit = Time.ticksPerTenthSecond
if millisecondsString![0] == "0"
{
var i: Int = 0
var divisor: Int64 = 10
while millisecondsString![i] == "0"
{
divisor *= 10
i += 1
}
lowerLimit /= divisor
}
while milliseconds < lowerLimit
{
milliseconds *= 10
}
}
let result = milliseconds.addingReportingOverflow(time * Time.ticksPerMillisecond)
var ticks = result.partialValue
if string[0] == "-"
{
if result.overflow == false
{
ticks = -ticks
}
}
else
{
assert(result.overflow == false)
}
return ticks
}
//MARK: - CustomStringConvertible
public var description: String
{
let days = abs(self.days)
let hours = abs(self.hours)
let minutes = abs(self.minutes)
let seconds = abs(self.seconds)
let fraction = abs(ticks % Time.ticksPerSecond)
var result = "\(String(format: "%02d", hours)):\(String(format: "%02d", minutes)):\(String(format: "%02d", seconds))"
if days != 0
{
result = "\(days).\(result)"
}
if fraction != 0
{
result = "\(result).\(String(format: "%07d", fraction))"
}
return ticks < 0 ? "-\(result)" : result
}
//MARK: - Operators
public static prefix func +(_ t: TimeSpan) -> TimeSpan
{
return t
}
public static func +=(left: inout TimeSpan, right: TimeSpan)
{
left = left + right
}
public static func +(left: TimeSpan, right: TimeSpan) -> TimeSpan
{
return left.add(timeSpan:right)
}
public static prefix func -(_ t: TimeSpan) -> TimeSpan
{
assert(t != TimeSpan.minValue)
return TimeSpan(ticks: -t.ticks)
}
public static func -(left: TimeSpan, right: TimeSpan) -> TimeSpan
{
return left.subtract(timeSpan:right)
}
public static func -=(left: inout TimeSpan, right: TimeSpan)
{
left = left - right
}
//Mark: - Hashable
public var hashValue: Int
{
return Int(ticks) ^ Int(ticks >> 32)
}
//MARK: - Equatable
public static func == (left: TimeSpan, right: TimeSpan) -> Bool
{
return left.ticks == right.ticks
}
//MARK: - Comparable
public static func <(left: TimeSpan, right: TimeSpan) -> Bool
{
return left.ticks < right.ticks
}
//MARK: - Codable
public init(from decoder: Decoder) throws
{
let container = try! decoder.singleValueContainer()
if let string = try? container.decode(String.self)
{
ticks = TimeSpan.parse(string: string)
}
else
{
ticks = 0
}
}
public func encode(to encoder: Encoder) throws
{
var container = encoder.singleValueContainer()
try! container.encode(description)
}
}
See Time.swift