-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add DocC implementation of current docs #59
Changes from 3 commits
0347207
4ea133f
14f61b6
7eb331a
1c70c5b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// swift-tools-version:5.5 | ||
// The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "Time", | ||
products: [ | ||
.library(name: "Time", targets: ["Time"]) | ||
], | ||
dependencies: [ | ||
|
||
], | ||
targets: [ | ||
.target(name: "Time", dependencies: []), | ||
|
||
.testTarget(name: "TimeTests", dependencies: ["Time"]), | ||
] | ||
) | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# Adjusting TimePeriods | ||
|
||
An overview of how to mutate instances of ``TimePeriod`` to form new values. | ||
|
||
## Overview | ||
|
||
"Adjusting" is the process of mutating a ``TimePeriod`` to form a new value. | ||
|
||
Simple adjustments would be things like: | ||
|
||
```swift | ||
let today: Absolute<Day> = ... | ||
let tomorrow = today.adding(days: 1) | ||
``` | ||
|
||
In this example, we have _adjusted_ the `today` value to get a new one that represents "tomorrow". | ||
|
||
There are two fundamental categories of adjustments: Safe adjustments, and Strict adjustments. | ||
|
||
## Safe Adjustments | ||
|
||
Safe adjustments are _relative_ adjustments. This means they typically involve starting with a known value, | ||
and then applying a relative difference. The example above is one such adjustment. | ||
|
||
These adjustments are "safe", because there is no reasonable way in which they will fail. | ||
For example, if I have a value representing a "day", then it will always be possible to find the preceding or succeeding day. | ||
|
||
``TimePeriod`` includes many methods for performing safe adjustments (such as the `adding(...)` and `subtracting(...)` methods). | ||
As a convenience, it also includes overrides of the `+` and `-` operator for a more expressive syntax: | ||
|
||
```swift | ||
let today: Absolute<Day> = ... | ||
let tomorrow = today + .days(1) | ||
``` | ||
Other "safe" adjustments include: | ||
|
||
- truncating a value to form a "less-precise" value: | ||
```swift | ||
let today: Absolute<Day> = ... | ||
let thisMonth = today.absoluteMonth() // an Absolute<Month> | ||
``` | ||
- finding the "next" or "previous" values: | ||
```swift | ||
let today: Absolute<Day> = ... | ||
let tomorrow = today.next() | ||
let yesterday = today.previous() | ||
``` | ||
|
||
## Strict Adjustments | ||
|
||
Strict adjustments are ones where we cannot guarantee at compile time that the operation will be calendrically correct. | ||
For example, if I have a value that refers to "February 2020", we cannot guarantee that attempting to find a specific day will succeed: | ||
|
||
```swift | ||
let february: Absolute<Month> = ... | ||
let feb30th = try february.setting(day: 30) | ||
``` | ||
|
||
Strict adjustments are one of the ways in which you can find "more precise" values: | ||
|
||
```swift | ||
let today: Absolute<Day> = ... | ||
let todayAt3PM = try today.setting(hour: 15, minute: 0) | ||
``` | ||
|
||
When a strict adjustment fails, it throws a ``TimeError``. | ||
|
||
## Topics | ||
|
||
### Adjustments | ||
|
||
- ``TimeError`` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# Common Mistakes | ||
|
||
An overview of common mistakes made while dealing with **Time** or its concepts in general. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Everywhere in the new markdown, I formatted the package's name to be bold, rather than These docs are 98% verbatim from their source. I made links to Foundation docs where missing, as well as used DocC symbol linking to provide a richer experience. The only formatting changes I made were to fit within DocC structure, and where it made absolute sense to break up paragraphs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 that works for me. |
||
|
||
## Overview | ||
|
||
Many of the "gotchas" around calendrical calculations can be found at [https://yourcalendricalfallacyis.com](https://yourcalendricalfallacyis.com). | ||
|
||
Many of these fallacies have been mitigated via the **Time** API, but many of them are still applicable. | ||
|
||
## Addition | ||
|
||
### Addition is not commutative | ||
|
||
We are used to a world where `1 + 2` produces the same result as `2 + 1`. This is not the case with calendars. | ||
|
||
Consider the following example: | ||
|
||
```swift | ||
let today: Absolute<Day> = ... | ||
|
||
let answer1 = today + .days(1) + .months(1) | ||
let answer2 = today + .months(1) + .days(1) | ||
``` | ||
|
||
There are many situations where `answer1` will be identical to `answer2`. | ||
|
||
However, the are also specific scenarios in which it will not. | ||
|
||
For example, if `today` is `February 29th, 2020`, then: | ||
- `answer1` will produce `April 1st, 2020` (29 Feb + 1 day = 1 Mar; 1 Mar + 1 month = 1 Apr) | ||
- `answer2` will product `March 30th, 2020` (29 Feb + 1 month = 29 Mar; 29 Mar + 1 day = 30 Mar) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo (originally mine, I think): There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
|
||
### Iterative addition | ||
|
||
Iterative mathematics can also produce issues in calendrical code. | ||
|
||
For example: | ||
|
||
```swift | ||
let today: Absolute<Day> = ... | ||
|
||
let answer1 = today + .months(1) + .months(1) | ||
let answer2 = today + .months(2) | ||
``` | ||
|
||
If `today` is `January 31st, 2020`, then: | ||
- `answer1` will produce `March 29th, 2020` (31 Jan + 1 month = 29 Feb; 29 Feb + 1 month = 29 Mar) | ||
- `answer2` will produce `March 31st, 2020` (31 Jan + 2 months = 31 Mar) | ||
|
||
In **Time**, iterative addition is handled by the ``AbsoluteTimePeriodSequence`` (and its underlying ``AbsoluteTimePeriodIterator``). | ||
|
||
The implementation of this type uses the second approach, | ||
which involves *scaling* the "stride" value for each successive iteration, | ||
and always performing the offset calculation relative to the *start* value, and not the most-recently-calculated value. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# Differences | ||
|
||
An overview of calendrical arithmetic using two ``TimePeriod`` instances. | ||
|
||
In **Time**, the calendrical interval between two `TimePeriod` values is expressed via the ``TimeDifference`` type. | ||
Like `TimePeriod`, it also has two generic parameters that define the range of represented units. | ||
|
||
The generic parameters *usually* match the parameters of the two `TimePeriod` values, | ||
but you can explicitly request different kinds of differences. | ||
|
||
```swift | ||
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) | ||
``` | ||
|
||
There are convenience methods for requesting differences in other units such as: | ||
|
||
```swift | ||
let day1: Absolute<Day> = ... | ||
let day2: Absolute<Day> = ... | ||
|
||
// the number of calendar days between the two values | ||
let difference = day1.differenceInDays(to: day2) | ||
``` | ||
|
||
And if your situation merits it, you can request a set of custom unit differences by providing an explicit type for the return value: | ||
|
||
```swift | ||
let day1: Absolute<Day> = ... | ||
let day2: Absolute<Day> = ... | ||
|
||
// compute the difference in days and months | ||
let difference: TimeDifference<Day, Month> = day1 - day2 | ||
``` | ||
|
||
`TimeDifference` can be used to adjust values by using the `applying(difference:)` method. | ||
|
||
> Important: Be aware of the <doc:Common-Mistakes> made with calculating differences between two `TimePeriod` values. | ||
|
||
## Topics | ||
|
||
### Representing Differences | ||
|
||
- ``TimeDifference`` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Formatting TimePeriods | ||
|
||
Formatting is the process of taking a ``TimePeriod`` and turning it into a human-readable form. | ||
|
||
**Time** offers a rich set of APIs for formatting values according to the user's localized preferences. | ||
|
||
```swift | ||
let today: Absolute<Day> = ... | ||
|
||
let string = today.format(date: .full) // Ex: February 28, 2020 | ||
``` | ||
|
||
Additionally, **Time** supports building formatted values by specifying individual field formats: | ||
|
||
```swift | ||
let today: Absolute<Day> = ... | ||
|
||
let string = today.format(year: .twoDigits, month: .naturalName) // Ex: February '20 | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# Iterating Over Values | ||
|
||
Iterating over calendrical values is a common problem, especially when it comes to building a UI around calendars. | ||
|
||
Many developers want to iterate over every day in a month, or every month in a year. **Time** makes this easy: | ||
|
||
```swift | ||
let thisMonth = Clocks.system.thisMonth() | ||
let daysInThisMonth = thisMonth.days() | ||
|
||
for day in daysInThisMonth { | ||
// day is an Absolute<Day> | ||
} | ||
``` | ||
|
||
By default, all absolute ``TimePeriod`` instances provide convenience methods for iterating over subcomponents: | ||
|
||
- An `Absolute<Year>` provides `months()` and `days()` to iterate over the months in the year and the days in the year | ||
- An `Absolute<Month>` provides `days()` and `hours()` to iterate over the days or hours in the month | ||
- etc | ||
|
||
However, you can *also* create your own value sequence, by using the ``AbsoluteTimePeriodSequence`` provided by **Time**: | ||
|
||
```swift | ||
let day1: Absolute<Day> = ... | ||
let day2: Absolute<Day> = ... | ||
|
||
let dayRange = day1 ..< day2 | ||
|
||
let everyOtherDay = AbsoluteTimePeriodSequence(range: dayRange, stride: .days(2)) | ||
for day in everyOtherDay { | ||
// ... | ||
} | ||
``` | ||
|
||
The `AbsoluteTimePeriodSequence` type provides several convenience constructors for performing different kinds of iterations. | ||
|
||
## Topics | ||
|
||
### Iterating Time Periods | ||
|
||
- ``AbsoluteTimePeriodSequence`` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# Observing Time Changes | ||
|
||
Overview on how to receive notifications when significant time events occur with **Combine**. | ||
|
||
On platforms that have the [`Combine` framework](https://developer.apple.com/documentation/combine), | ||
**Time** provides a way for ``Clock`` instances to notify when significant time events occur. | ||
|
||
There are three main waits to observe time changes. | ||
|
||
### Observing Intervals | ||
|
||
The ``Clock/chime(every:startingFrom:)`` method allows you to create a publisher ``ClockChime`` | ||
that will emit events after a particular calendrical interval has elapsed. | ||
|
||
A simple example of this would be to be notified when every 2 seconds have passed: | ||
|
||
```swift | ||
clock | ||
.chime(every: .seconds(2)) | ||
.sink { (value: Absolute<Second>) in | ||
print("Another 2 seconds have passed. It is now \(value)") | ||
} | ||
.store(in: &cancellables) | ||
``` | ||
|
||
By default, the `chime(every:)` method starts counting from the current time shown on the clock. | ||
|
||
However, if you'd like to start counting for a particular point (such as the beginning of the next minute or at the start of the next hour), | ||
you may also optionally specify the `startingFrom:` parameter: | ||
|
||
```swift | ||
let startOfNextHour = clock.nextHour().firstMinute | ||
clock | ||
.chime(every: .minutes(5), startingFrom: startOfNextHour) | ||
.sink { value in | ||
print("The time is now \(value)") | ||
} | ||
.store(in: &cancellables) | ||
``` | ||
|
||
### Observing Arbitrary Values | ||
|
||
Sometimes you want to be notified when the time on a clock matches some indetermine set of requirements, | ||
such as "The minute must be evenly divisible by 7". | ||
|
||
To do this, use the ``Clock/chime(when:)`` method: | ||
|
||
```swift | ||
clock | ||
.chime(when: { $0.minute.isMultiple(of: 7) }) | ||
.sink { (value: Absolute<Minute>) in | ||
print("The time is now \(value)") | ||
} | ||
.store(in: &cancellables) | ||
``` | ||
|
||
### Observing Once | ||
|
||
**Time** also makes it easy to be notified when the time on a clock is a certain value with the ``Clock/chime(at:)`` method. | ||
|
||
```swift | ||
let startOfNextHour = clock.nextHour() | ||
clock | ||
.chime(at: startOfNextHour) | ||
.sink { value in | ||
print("The time is now \(value)") | ||
} | ||
.store(in: &cancellables) | ||
``` | ||
|
||
This publisher will fire at most one time. | ||
|
||
If the specified time is in the past, then the publisher will complete immediately without emitting any values. | ||
|
||
## Notes | ||
|
||
The ability to be notified about time changes can be incredibly useful. As you use this feature, please keep in mind these points: | ||
|
||
### Clock rate | ||
|
||
The "chiming" of the clock factors in the *rate* at which time passes on the clock. | ||
|
||
If you have created a custom ``Clock`` where every second "in real life" means that a minute has passed on the clock, | ||
then the `Clock` would notify you every second that one minute has passed. | ||
|
||
You can see an example of this in the unit tests. | ||
|
||
### Precision | ||
|
||
Even though **Time** _technically_ supports precision down to the ``Nanosecond``, | ||
it is very difficult to get notifications timed to be more accurate than about every 2 milliseconds. | ||
|
||
Thus, it is possible that you may see slight inaccuracies if you're asking for nanosecond intervals between chime times, | ||
or if you're working with a ``Clock`` that are scaled to run faster than about 500 times real speed. | ||
|
||
### Performance | ||
|
||
When using the ``Clock/chime(every:startingFrom:)`` method, | ||
avoid choosing start times that are "far away" from your desired next chime time. | ||
|
||
In order to correctly determine chime times, | ||
the underlying code must iterate through *every possible value* between the start time and "now" to make sure that it stays calendrically accurate. | ||
|
||
For example, if you're wanting to receive a chime every minute, but your start date is a month ago, | ||
the publisher must first calculate *every minute in the past month* in order to figure out the next matching time. | ||
|
||
Wisely choosing a start time can drastically affect the accuracy of delivering the first chime. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this
Package.swift
necessary if the primary one is set to request Swift 5.5?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pre 5.5, the compiler emits a warning of unused/unrecognized files and suggests explicitly excluding the DocC file.
To be honest, I’m not 100% sure if that warming is emitted when downstream users are building the library as a dependency, but it definitely emits a warning when building the project itself
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just tested this by depending on a special branch that doesn't do this conditional package manifest stuff and I can conform that the compiler warning does get emitted when the library is built by a downstream user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, if your Package.swift doesn't somewhere explicitly say 5.5 is a supported build flavor, the DocC output is not generated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, shoot. I'm seeing my mistake - I didn't properly switch the minimum version in question... I'll fix it!