Building a better date/time library for Swift

Overview

Time

Time is a Swift package that makes dealing with calendar values a natural and straight-forward process.

Working with calendars can be extremely complicated and error-prone. Time solves these problems by clarifying concepts and restricting improper usage through type-safe APIs.

Installing

Time can be installed like any other Swift package. Add this to the dependencies section of your Package.swift:

.package(url: "https://github.com/davedelong/time", from: "0.9.1")

The Basics

Here's the TL;DR of the documentation:

  • If you want to know what time it is, you need a Clock. You can get the device's clock by using Clock.system.

  • A Clock can tell you the current time via some functions. For example, .today() will give you the current calendar day. .thisMinute() will give you the current time, accurate down to the minute level.

  • Each of these returned values has methods to retrieve more- and less- precise values. For example, today.hours() will give you a sequence of all the "Hour" values in the day.

  • These values also are how you format them into human-readable strings (via the .format(...) method)

Some Small Examples

There are some examples below showing a sneak peek of what you can do with Time.

Fetching the Current Time

let clock = Clock.system

// retrieve the current instantaneous time from the clock
let now = clock.thisInstant()

// retrieve the current calendar day, as defined by the user's region
let today = clock.today()

More information in "Clock".

Converting Between Regions

let nycTimeZone = TimeZone(identifier: "America/New_York")!

let myClock = Clock.system
let nycClock = myClock.converting(to: nycTimeZone)

let myLocalTime = myClock.thisMinute() // Ex: 28 Feb 2020 at 3:14 PM Pacific Time

let nycLocalTime = nycClock.thisMinute() // Ex: 28 Feb 2020 at 6:14 PM Eastern Time

More information in "Clock".

Retrieving Components

let today: Absolute<Day> = myClock.today()
let year = today.year // Ex: 2020
let month = today.month // Ex: 2
let day = today.day // Ex: 28

More information in "TimePeriod".

Calculating Differences

let day1: Absolute<Day> = ...
let day2: Absolute<Day> = ...

// compute the difference in days, months, years, and eras
let difference: TimeDifference<Day, Era> = day1.difference(to: day2)

// or conveniently the number of calendar days between the two values
let daysDifference = day1.differenceInDays(to: day2)

More information in "Differences".

Iterating Over TimePeriods

let thisMonth = Clock.system.thisMonth()
let daysInThisMonth = thisMonth.days()

for day in daysInThisMonth {
    // โ€ฆ
}

More information in "Iterating Over TimePeriods".

Formatting TimePeriods

let today: Absolute<Day> = ...

let fullYearString = today.format(date: .full) // Ex: February 28, 2020
let shortYearString = today.format(year: .twoDigits, month: .full) // Ex: February '20

More information in "Formatting TimePeriods".

Observing time changes

let clock: Clock = ...
clock
    .chime(every: .seconds(5))
    .sink { (value: Absolute<Second>) in
        print("Another 5 seconds have passed")
    }
    .store(in: &cancellables)

More information in "Observing time changes".

Detailed Information

For more information, please see the documentation.

Issues
  • Feature: Clock

    Feature: Clock "chimes"

    It'd be nice to add a way for a Clock to notify you about significant time changes, like how clocks chime in the real world.

    There are a couple API scenarios to consider:

    • starting from a provided Value, notify me every time this Difference has elapsed
    • starting from now, notify me every time this SISecond/Difference has elapsed
    • starting from now, notify me every time a value matching these DateComponents occurs

    There will be some interesting challenges when it comes to clocks that scale time (ie, run slower or faster than real time).

    enhancement help wanted 
    opened by davedelong 13
  • All zero-argument methods that return a value should be computed properties instead

    All zero-argument methods that return a value should be computed properties instead

    What would you think about changing the method signatures such that as a general rule, all zero-argument methods that return a value were replaced with computed properties? For example, func now() -> Instant would be replaced by var now: Instant. I think this would make code that called those methods significantly more concise and easier to read.

    api 
    opened by sstigler 7
  • Storing and retrieving future dates and times

    Storing and retrieving future dates and times

    Given the purpose of this library it occurs to me that there's something else you could try to help users with: storing and retrieving future date-time values.

    To motivate, I'll refer to this post:

    which recommends that developers "preserve local time, using UTC as derived data to be recomputed" in specific use-cases. (There's HackerNews discussion here; one of the comments points to RFC5545. In particular, take a look at section 3.3.5.)

    What I'm thinking is that this library could a) enumerate the different kinds of storage, different kinds of trade-offs; and b) provide appropriate encodings/decodings. Bonus points if the encodings/decodings aren't jut BLOBsโ€”e.g., if they work well in CoreData, JSON, etc.!

    opened by michaellenaghan 6
  • Usage of

    Usage of "Unsafe"

    The discussion on the Swift Forums brought up some objections to the usage of unsafe in the APIs:

    init(region: Region, unsafeDateComponents: DateComponents) throws
    
    struct UnsafeAdjustment<...> {
        // ...
    }
    

    The primary objection is that "unsafe" is used exclusively in the standard library to refer to memory safety issues, and that having a method declared with throws is enough indication that something can go wrong.

    @belkadan brought up this point:

    "Unsafe" is not supposed to mean "has preconditions which are the client's responsibility". In a narrow form, it would be "can break type safety, memory safety, or exhaustivity" (because breaking any one can lead to the others breaking), but if you were to make an argument for a broader form, I would say it would be "may corrupt data if you break the preconditions", which Time's usage is almost the opposite of.

    These are all valid points.


    I'm open to changing the usage of "Unsafe" in Time, but here are some thoughts to consider:

    • Unsafe is used for more than just a name in an initializer parameter; it's also used as the name of a type (currently internal, but will be public): UnsafeAdjustment

    • Given the two broad categories of adjustments, using the term Unsafe has been nice, because it has a clear and familiar antonym of Safe. I'd prefer any replacement word to also have a clear antonym, to maintain the strong pairing between "safe" and "unsafe" adjustments.

    • Any alternative would ideally be succinct, as dealing with MightPossiblyFailAdjustment in code would be tiresome and aesthetically unpleasing.

    • Time is not part of the standard library, thus it is not bound by the standard library's style and conventions.

    • "Unsafe" is a term familiar to Swift developers, even beyond the explicit context of "memory safety"

    help wanted question 
    opened by davedelong 4
  • Document SISeconds

    Document SISeconds

    good first issue documentation 
    opened by davedelong 3
  • Getting a Foundation `Date` from `Value`

    Getting a Foundation `Date` from `Value`

    Hey there,

    Thank you for this fantastic library!! Quick question that I couldn't find in the docs (hopefully I didn't just miss it).

    I need to calculate a date 5 minutes from now in UTC. Here's what I'm doing:

    let utc = Clock.system.converting(to: TimeZone(identifier: "UTC")!).thisMinute()
    let plusFiveMinutes = (utc + .minutes(5)).firstInstant.date
    

    I'm clear on everything up until .firstInstant.date - that was the only way I could find to pull a Foundation Date out of my addition operation. Does that seem right?

    Thank you again!

    opened by jdmcd 3
  • Infinite loop in `Absolute.range`

    Infinite loop in `Absolute.range`

    Hi,

    I'm looking for a way to check, whether a previous call to my networking api was sent more than 5 minutes ago. I thought trying your project for this would be a nice start. On first launch, there was now call at all so I usually use Foundation.Date.distantPast. I ended with the following code ...

    let now = Clock.system.thisMinute()
    let lastUpdate = Absolute<Minute>(region: .current, instant: .init(date: .distantPast))
    
    let differenceSinceLastUpdate = now - lastUpdate
    
    guard differenceSinceLastUpdate.minutes > 5 else { return }
    

    ... and found that it is ending in an infinite loop in Absolute.range.

    Here my questions:

    1. Is this the right way to solve my issue?
    2. Is this infinite loop a known bug?

    Thanks in advance.

    opened by trispo 2
  • Stack overflow (?) when calling `firstInstant`

    Stack overflow (?) when calling `firstInstant`

    I have the following code:

    let utc = Clock.system.converting(to: TimeZone(identifier: "UTC")!).thisMinute()
    let plusFiveMinutes = (utc + .minutes(5)).firstInstant.date
    

    Running it causes the following error to be thrown in Xcode:

    Screen Shot 2020-03-22 at 6 18 09 PM

    (Nothing useful in the console, blocked out because it had sensitive API routes)

    Any ideas here? It looks like some kind of possible recursion or stack overflow bug because the entire stack is filled with the two same calls (Value.range.getter and Value.approximateMidPoint.getter) but not positive.

    Let me know if there's anything I can do to help debug.

    opened by jdmcd 2
  • Custom Formatting

    Custom Formatting

    I would like to have more control over the formatting of the absolute value. For example I would like to get the day of the week. I would usually just use "E" as a date format in order to get the first letter of the day of the week. However it appears that Template initialisers are internal and therefore unable to use a custom format. What would you suggest in this scenario?

    opened by alexjameslittle 2
  • How does one set the clock so that Monday is the first dayu of the week?

    How does one set the clock so that Monday is the first dayu of the week?

    Is this possible? If so, where in the documentation is this referenced?

    opened by Rillieux 2
  • Hide Internal Public Types

    Hide Internal Public Types

    Motivation

    LTO* and GTO* prefixed protocols act as markers for conditional conformance clauses, and as such need to be public symbols, but for all intents and purposes they are internal to Time.

    This means these types appear in autocompletion as well as have symbol documentation generated in DocC.

    Changes Made

    • Change: LTO* and GTO* prefixed protocols to be prefixed by _

    Results

    The swift compiler will "hide" _LTO* and _GTO* symbols for purposes such as DocC and autocompletion while still leaving them public.

    opened by Mordil 3
  • Add Swift 5.5 syntax sugar for static extensions on Clock

    Add Swift 5.5 syntax sugar for static extensions on Clock

    Motivation

    When determining which Clock value to use, it can be less expressive or easily forgotten to use the Clocks namespace to access the several built-in values.

    In Swift 5.5 we gain the ability to provide a leading dot syntax, even when dealing in a protocol context where the concrete type isn't (yet) known.

    Changes Made

    When developers are building the library with a 5.5 compiler or later, new static extensions on Clock will be available that provide the same values as found on Clocks.

    This required making some properties and types public, and changing their names to have a _ prefix to hide them in autocompletion. Their initializers are still internal to disallow outside creation of instances.

    Results

    Developers will now be able to write the following code:

    func someClockContext(_ clock: Clock) { /* ... */ }
    
    someClockContext(.system)
    someClockContext(.custom(startingFrom: Clocks.system.thisInstant(), rate: 2.0))
    
    opened by Mordil 3
  • Add total unit values to TimeDifference

    Add total unit values to TimeDifference

    Right now TimeDifference provides concrete Int properties of each unit component of a given difference, which leaves users to implement a way of getting a total of a specific unit.

    For example, to find the total number of minutes between two TimePeriods (such as an event with a start/end), you need to do:

    difference.hours * 60 + difference.minutes
    

    I'd be appreciative to have an API like the following:

    extension TimeDifference {
      func total(_ unit: Calendar.Component) -> Double
    }
    
    let diff = start - end // hour: 1 minute: 3
    
    print(diff.total(.minute))
    // 63
    print(diff.total(.hour))
    // 1.05
    

    I understand this can get very problematic at higher & lower precision, maybe a first release could just be seconds/minutes/hours?

    opened by Mordil 3
  • Make Template.template public

    Make Template.template public

    Hello, first of all, thank you for all your work on this library.

    Apart from that, the title says it all, would be nice to be able to use Template values for raw formatting/parsing. I thought about making a micro PR, but I feared there was a reason for it not being public, besides it's a change so small that you could make trough Github edit.

    P.S.: Am I missing something or is it not possible to parse a date with the format y-MM-dd without using the rawFormat? I thought that using the iso8601 Calendar would work, but it does not seem to be the case.

    opened by Robuske 0
  • CocoaPods support

    CocoaPods support

    Iโ€™m still using CocoaPods mainly because of automatic Settings bundle generation.

    Happy to make a PR adding a Podspec if youโ€™re interested?

    opened by Brett-Best 0
  • Slow initialisation

    Slow initialisation

    When initialising an Absolute TimePeriod we are seeing very slow performance. It is taking 90ms+ to initialise a time period. Also getting bad performance when trying to add or subtract from time periods. Please see the attached image of the time profile I ran which leads me to the TimePeriod initialiser and the subtracting of time period.

    Screenshot 2020-06-23 at 13 13 11
    opened by alexjameslittle 0
  • How to ask whether an event is more than n minutes ago?

    How to ask whether an event is more than n minutes ago?

    Hi,

    I'm looking for a way to check, whether a previous call to my networking api was sent more than 5 minutes ago. I thought trying your project for this would be a nice start. On first launch, there was now call at all so I usually use Foundation.Date.distantPast. I ended with the following code ...

    let now = Clock.system.thisMinute()
    let lastUpdate = Absolute<Minute>(region: .current, instant: .init(date: .distantPast))
    
    let differenceSinceLastUpdate = now - lastUpdate
    
    guard differenceSinceLastUpdate.minutes > 5 else { return }
    

    ... but this seems to be wrong as differenceSinceLastUpdate.minutes is not the value I expected it to be.

    What is the right way to get the number of minutes between two dates?

    opened by trispo 2
  • Precision tighter than milliseconds is problematic

    Precision tighter than milliseconds is problematic

    It's difficult to deal with precision higher than milliseconds.

    I'm attempting to write tests dealing with nanosecond precision and they're failing all over the place. Couple this with the face that DateFormatter won't format anything smaller than milliseconds and maybe Time should just cap things off there.

    enhancement 
    opened by davedelong 1
  • convert(to timeZone:) ">

    convert(to timeZone:) "feels wrong" for Absolute

    convert(to timeZone:) behaves consistently for all T in Absolute<T> and in all other cases besides <Day> it behaves intuitively. For example, taking March 16 2020 at 12:00 PM EST (Absolute<Minute>) and converting it to PST gives you a different set of date components, which "feels" right.

    Converting March 16, 2020 (Absolute<Day>) from UTC to NZDT (UTC+13) gives March 17, 2020 because internally it takes the approximate midpoint of March 16, 2020 UTC (noon on that day in UTC) and uses the date components of that instant in NZDT, which is 1:00AM March 17 NZDT. This "feels" wrong (to me).

    The behaviour for anything smaller than Day is intuitively correct (as in the first example above), and for anything bigger than Day is intuitively correct because the midpoint of a month, year, or era will end up in the same month, year, or era (AFAIK, maybe this is one of those falsehoods programmers believe) because time zones are never several days apart from each other.

    I suppose we can't change just the behaviour of convert(to timeZone:) for Absolute<Day> but I wonder if we could provide a sameDay(in timeZone:) or something?

    This realization made me rethink the way I was doing things and in my own app this isn't actually something I need, so maybe things are fine as-is, or maybe convert(to timeZone:) just needs some docs explaining this?

    bug api 
    opened by sadlerjw 7
  • Perceived time difference

    Perceived time difference

    There's no actual time difference between 1 PM Pacific Time and 4 PM Eastern Time, but there is a perceived difference based on the different time zones.

    There should be an "easy" way to capture this perceived difference.

    enhancement api 
    opened by davedelong 1
Releases(0.9.1)
Owner
Dave DeLong
Dave DeLong
A "time ago", "time since", "relative date", or "fuzzy date" category for NSDate and iOS, Objective-C, Cocoa Touch, iPhone, iPad

Migration 2014.04.12 NSDate+TimeAgo has merged with DateTools. DateTools is the parent project and Matthew York is the project head. This project is n

Kevin Lawler 1.8k Nov 8, 2021
Better time picker for iOS.

TimePicker Better TimePicker for iOS Requirements Swift 5.0 iOS 10.0+ Xcode 10.2+ Installation The easiest way is through CocoaPods. Simply add the de

Oleh 14 Jun 15, 2021
:watch: Date and time manager for iOS/OSX written in Swift

Tempo was designed to work both in OSX and in iOS (7.0+). Work with the time or dates can be cumbersome, iOS development. Tempo allows you to deal eas

Remi ROBERT 153 Jun 3, 2021
Swifty Date & Time API inspired from Java 8 DateTime API.

AnyDate Swifty Date & Time API inspired from Java 8 DateTime API. Background I think that date & time API should be easy and accurate. Previous dates,

Jungwon An 182 Nov 2, 2021
Sync your app's time with server's UTC time

ServerSync for iOS Apps Sometimes your app can't rely on the device's date and time settings being correct. This library will help you calibrate your

null 11 Nov 1, 2020
Elegant NTP date library in Swift

Kronos is an NTP client library written in Swift. It supports sub-seconds precision and provides a stable monotonic clock that won't be affected by ch

Mobile Native Foundation 524 Nov 16, 2021
Elegant NTP date library in Swift

Kronos is an NTP client library written in Swift. It supports sub-seconds precision and provides a stable monotonic clock that won't be affected by ch

Mobile Native Foundation 524 Nov 16, 2021
๐Ÿ“… Swift4 Date extension library

Introduction NVDate is an extension of NSDate class (Swift4), created to make date and time manipulation easier. NVDate is testable and robust, we wro

Noval Agung Prayogo 178 Nov 2, 2021
Intuitive date handling in Swift

Timepiece Intuitive date handling in Swift Features ?? Intuitive: Timepiece provides a set of helpers to make date handling easier. ?? Correct: Using

Naoto Kaneko 2.7k Nov 9, 2021
A Swift Date extension helper

DateHelper A high performant Swift Date Extension for creating, converting, comparing, or modifying dates. Capabilities Creating a Date from a String

Melvin Rivera 1.3k Nov 13, 2021
๐Ÿ“† Breeze through Date, DateComponents, and TimeInterval with Swift!

Datez ?? Breeze through Date, DateComponents, and TimeInterval Highlights Two Custom Structs Only (value types FTW!): DateView: An Date associated wit

Kitz 258 Aug 4, 2021
Easypeasy date functions. ๐Ÿ•›

Datify ?? Easypeasy date functions. Usage: print("now: \(Date.init())") print("8 days later: \(8.days.fromNow)") print("2 w

Hemang 44 Apr 16, 2020
A time and calendar manipulation library for iOS 9+, macOS 10.11+, tvOS 9+, watchOS 2+ written in Swift 4.

SwiftMoment This framework is inspired by Moment.js. Its objectives are the following: Simplify the manipulation and readability of date and interval

Adrian Kosmaczewski 1.6k Nov 8, 2021
NTP library for Swift and Objective-C. Get the true time impervious to device clock changes.

TrueTime for Swift Make sure to check out our counterpart too: TrueTime, an NTP library for Android. NTP client for Swift. Calculate the time "now" im

Instacart 488 Nov 16, 2021
๐Ÿ” Toolkit to parse, validate, manipulate, compare and display dates, time & timezones in Swift.

Toolkit to parse, validate, manipulate, compare and display dates, time & timezones in Swift. What's This? SwiftDate is the definitive toolchain to ma

Daniele Margutti 6.6k Nov 12, 2021
๐Ÿ•ฐ Type-safe time calculations in Swift

Time This micro-library is made for you if: You have ever written something like this: let interval: TimeInterval = 10 * 60 To represent 10 minutes. U

Oleg Dreyman 1k Nov 21, 2021
Custom Time Picker ViewController with Selection of start and end times in Swift :large_orange_diamond:

LFTimePicker Custom Time Picker ViewController with Selection of start and end times in Swift ?? . Based on Adey Salyard's design @ Dribbble One to tw

Awesome Labs 63 Jun 15, 2021
This Control is a beautiful time-of-day picker heavily inspired by the iOS 10 "Bedtime" timer.

#10Clock Dark and Mysterious ?? Light Colors ?? Usage The control itsself is TenClock. Add that to your view hierarchy, and constrain it to be square

Joe 550 Nov 25, 2021
A TimeZonePicker UIViewController similar to the iOS Settings app. Search and select from a range of cities and countries to find your most suitable time zone.

TimeZonePicker A TimeZonePicker UIViewController similar to the iOS Settings app. Search and select from a range of cities and countries to find your

Gligor Kotushevski 118 Nov 1, 2021