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 Clocks.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 = Clocks.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 = Clocks.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 = Clocks.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.

Comments
  • 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
  • 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
  • Add DocC implementation of current docs

    Add DocC implementation of current docs

    Motivation

    DocC is a fantastic tool that was announced at WWDC 2021, and full support was released with Swift 5.5

    Supporting documentation within DocC gives additional capabilities to developers who work with Time to have better symbol linking and usage guides available while offline, and generated to render within Xcode itself.

    Changes Made

    • Add: Documentation catalog to the Time module
    • Add: Documentation articles that copy the text from the existing Documentation/ folder almost verbatim
    • Add: Package manifest variant for Swift 5.5 to support DocC symbol production
    • Change: Base Package manifest to exclude Documentation catalog

    Results

    DocC support!

    opened by Mordil 2
  • Infinite recursion between Absolute.range and Absolute.approximateMidPoint

    Infinite recursion between Absolute.range and Absolute.approximateMidPoint

    The Absolute.approximateMidPoint and Absolute.range getters seem to call each other. I'm running an app which corroborates this.

    In Time/5. Absolute Values/Absolute.swift:

    public var range: Range<Instant> {
        let date = approximateMidPoint.date  <--
        ...
    }
    

    And in Time/5. Absolute Values/Absolute+Internal.swift:

    ...
    internal var approximateMidPoint: Instant {
        let r = self.range  <--
        ...
    }
    

    One simply has to call Absolute(...).range to get a hang and then a crash.

    The implementations of these seem to make sense when considered independent of each other, but the fact that they depend on each other recursively is an issue.

    I'm not enough of an expert yet on working with Foundation.Date or Time to know quite what to do to split these, but I can at least cobble together some test cases to demonstrate it.

    opened by AverageHelper 2
  • 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
  • Added Chimes

    Added Chimes

    Added chimes feature, as per discussion in #18.

    Could perhaps use some more unit tests, but I built only what I had tests for (took a stab at TDD).

    If a client platform can import Combine, then relevant methods are made available on the Clock interface for observing when specific times or time intervals elapse.

    opened by AverageHelper 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
  • 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
  • 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
  • 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
Releases(0.9.1)
Owner
Dave DeLong
Dave DeLong
Date Formatter Pool - is a small utility that creates and stores your Date Formatter for simpler reuse

Date Formatter Pool Date Formatter Pool - is a small utility that creates and stores your Date Formatter for simpler reuse Installation is available i

Aleksei Artemev 13 Sep 6, 2022
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 Oct 21, 2022
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 easly with date and time. Basics manipulations are already implemented in Tempo.

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 Dec 1, 2022
NVDate is an extension of NSDate class (Swift4), created to make date and time manipulation easier.

NVDate is an extension of NSDate class (Swift4), created to make date and time manipulation easier. NVDate is testable and robust, we wrote intensive test to make sure everything is safe.

Noval Agung Prayogo 177 Oct 5, 2022
A basic countdown app that allows the user to create, edit, and delete events. Each event contains a live countdown timer to a specified date and time.

Event Countdown App (iOS) Created by Lucas Ausberger About This Project Created: January 4, 2021 Last Updated: January 8, 2021 Current Verison: 1.1.1

Lucas Ausberger 1 Jan 8, 2022
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 Dec 1, 2022
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 575 Dec 23, 2022
DateHelper - A high performant Swift Date Extension for creating, converting, comparing, or modifying dates.

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

Melvin Rivera 1.4k Jan 2, 2023
📆 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 263 Dec 7, 2022
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.6k Dec 22, 2022
Swift Date Formatter

Swift Date Formatter Date Formatter - Sample code on how to use Date Formatter in swift language Author: Denow Cleetus For OSSE Assignment 4 Group 24

Denow Cleetus 0 Nov 7, 2021
NasaApod - iOS, Swift, MVVM, Consuming NASA Astronomy Picture of the Day API for any selected date

NasaApod iOS, Swift, MVVM, Unit Tests Consuming NASA Astronomy Picture of the Da

Vishal Singh 1 Jan 10, 2022
🎗 Super lightweight ISO8601 Date Formatter in Swift

ISO8601 ❤️ Support my apps ❤️ Push Hero - pure Swift native macOS application to test push notifications PastePal - Pasteboard, note and shortcut mana

Khoa 19 May 12, 2020
Datify 🕛 Easypeasy date functions.

Datify ?? Easypeasy date functions.

Hemang 44 Dec 6, 2022
A customizable date picker for watchOS and SwiftUI.

Watch Date Picker A customizable date picker for watchOS and SwiftUI. Installation .package(url: "https://github.com/freyaariel/watch-date-picker.git"

Freya Ariel 30 Dec 4, 2022
SwiftMoment - 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 Dec 31, 2022
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 530 Jan 4, 2023
Timekeeper is an easy-to-use time measurement library written in Swift, with support for iOS, tvOS, watchOS and macOS.

Timekeeper is an easy-to-use time measurement library written in Swift, with support for iOS, tvOS, watchOS and macOS. Installation Timekee

Andreas Pfurtscheller 1 Oct 18, 2021