Skip to content
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

Merged
merged 5 commits into from
Nov 6, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.0
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand All @@ -12,7 +12,7 @@ let package = Package(

],
targets: [
.target(name: "Time", dependencies: []),
.target(name: "Time", dependencies: [], exclude: ["Documentation.docc"]),

.testTarget(name: "TimeTests", dependencies: ["Time"]),
]
Expand Down
19 changes: 19 additions & 0 deletions Package@swift-5.5.swift
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"]),
]
)
Comment on lines +1 to +19
Copy link
Owner

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?

Copy link
Contributor Author

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

Copy link
Contributor Author

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.

found 1 file(s) which are unhandled; explicitly declare them as resources or exclude from the target
/Users/.../Library/Developer/Xcode/DerivedData/test-crmhlaksfvpqexbqodizljvczqrs/SourcePackages/checkouts/time/Sources/Time/Documentation.docc

Copy link
Contributor Author

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

Copy link
Contributor Author

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!

File renamed without changes.
File renamed without changes.
72 changes: 72 additions & 0 deletions Sources/Time/Documentation.docc/Adjustments.md
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``
55 changes: 55 additions & 0 deletions Sources/Time/Documentation.docc/Common-Mistakes.md
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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 monospaced to make it stand out more. That's just a personal style of mine, so please let me know if you have a different preference for referencing modules or libraries

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.

Copy link
Owner

Choose a reason for hiding this comment

The 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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo (originally mine, I think): productproduce

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
47 changes: 47 additions & 0 deletions Sources/Time/Documentation.docc/Differences.md
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``
19 changes: 19 additions & 0 deletions Sources/Time/Documentation.docc/Formatting.md
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
```
42 changes: 42 additions & 0 deletions Sources/Time/Documentation.docc/Iteration.md
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``
107 changes: 107 additions & 0 deletions Sources/Time/Documentation.docc/Observation.md
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.