From 4e205a43f434eafa7fef0850eb15f3819ea26399 Mon Sep 17 00:00:00 2001 From: Erik Christensen <40830816+erikc5000@users.noreply.github.com> Date: Wed, 26 Aug 2020 12:59:42 -0400 Subject: [PATCH] Update/expand website and API documentation (#133) --- .github/workflows/deploy-website.yaml | 6 +- README.md | 3 +- build.gradle.kts | 4 - buildSrc/build.gradle.kts | 2 +- .../kotlin/io/islandtime/clock/Clock.kt | 4 +- .../io/islandtime/darwin/Conversions.kt | 92 +++++++++---------- docs/basics/clocks.md | 90 ++++++++++++++++++ docs/basics/dates-and-times.md | 22 ++--- docs/basics/interop.md | 26 +++--- docs/basics/overview.md | 12 ++- docs/getting-started.md | 12 +-- mkdocs.yml | 1 + tools/mkdocs-dokka-plugin/build.gradle.kts | 4 +- .../src/main/kotlin/MkdocsPlugin.kt | 16 ++-- 14 files changed, 193 insertions(+), 101 deletions(-) create mode 100644 docs/basics/clocks.md diff --git a/.github/workflows/deploy-website.yaml b/.github/workflows/deploy-website.yaml index 51104a2c0..59841ec2c 100644 --- a/.github/workflows/deploy-website.yaml +++ b/.github/workflows/deploy-website.yaml @@ -20,9 +20,7 @@ jobs: run: pip install mkdocs-material mkdocs-macros-plugin - name: Build API docs - run: | - ./gradlew assemble - ./gradlew generateMkdocsApiDocs + run: ./gradlew mkdocsDokka - name: Deploy - run: mkdocs gh-deploy --force \ No newline at end of file + run: mkdocs gh-deploy --force diff --git a/README.md b/README.md index 5fcfbe0a8..7d03dd1fd 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,9 @@ Features: - Convert to and from platform-specific date-time types - Works on JVM, Android, iOS, macOS, tvOS, and watchOS -Current limitations: +Notable Limitations: - No custom format strings (must write platform-specific code to do this) +- No support for JavaScript or non-Apple native platforms - Only supports the ISO calendar system Island Time is still early in development and "moving fast" so to speak. The API is likely to experience changes between minor version increments. diff --git a/build.gradle.kts b/build.gradle.kts index 741809652..cc8215fcf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,10 +13,6 @@ buildscript { } } -plugins { - id("org.jetbrains.dokka") -} - subprojects { tasks.withType().configureEach { sourceCompatibility = JavaVersion.VERSION_1_8.toString() diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index d79d816b0..2251b506d 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { implementation(kotlin("gradle-plugin")) implementation(kotlin("stdlib-jdk8")) implementation(kotlin("reflect")) - implementation("org.jetbrains.dokka:dokka-gradle-plugin:1.4.0-dev-38") + implementation("org.jetbrains.dokka:dokka-gradle-plugin:1.4.0-dev-53") implementation("com.android.tools.build:gradle:4.0.1") } diff --git a/core/src/commonMain/kotlin/io/islandtime/clock/Clock.kt b/core/src/commonMain/kotlin/io/islandtime/clock/Clock.kt index 4bbd2306e..ba7b75aec 100644 --- a/core/src/commonMain/kotlin/io/islandtime/clock/Clock.kt +++ b/core/src/commonMain/kotlin/io/islandtime/clock/Clock.kt @@ -9,7 +9,7 @@ import io.islandtime.internal.deprecatedToError import io.islandtime.measures.* /** - * A time source. + * An abstraction providing the current time. * * For an implementation that uses the system's clock, see [SystemClock]. [FixedClock] is also available for testing * purposes. @@ -19,7 +19,7 @@ import io.islandtime.measures.* */ interface Clock { /** - * The clock's time zone. + * The time zone of this clock. */ val zone: TimeZone diff --git a/core/src/darwinMain/kotlin/io/islandtime/darwin/Conversions.kt b/core/src/darwinMain/kotlin/io/islandtime/darwin/Conversions.kt index f99df7c4c..4c856a321 100644 --- a/core/src/darwinMain/kotlin/io/islandtime/darwin/Conversions.kt +++ b/core/src/darwinMain/kotlin/io/islandtime/darwin/Conversions.kt @@ -14,7 +14,7 @@ import kotlinx.cinterop.convert import platform.Foundation.* /** - * Convert an Island Time [Year] to an equivalent `NSDateComponents` object. + * Converts this year to an equivalent `NSDateComponents` object. * @param includeCalendar `true` if the resulting `NSDateComponents` should include the ISO-8601 calendar * @return an equivalent `NSDateComponents` object */ @@ -26,7 +26,7 @@ fun Year.toNSDateComponents(includeCalendar: Boolean = false): NSDateComponents } /** - * Convert an Island Time [YearMonth] to an equivalent `NSDateComponents` object. + * Converts this year-month to an equivalent `NSDateComponents` object. * @param includeCalendar `true` if the resulting `NSDateComponents` should include the ISO-8601 calendar * @return an equivalent `NSDateComponents` object */ @@ -39,7 +39,7 @@ fun YearMonth.toNSDateComponents(includeCalendar: Boolean = false): NSDateCompon } /** - * Convert an Island Time [Date] to an equivalent `NSDateComponents` object. + * Converts this date to an equivalent `NSDateComponents` object. * @param includeCalendar `true` if the resulting `NSDateComponents` should include the ISO-8601 calendar * @return an equivalent `NSDateComponents` object */ @@ -51,7 +51,7 @@ fun Date.toNSDateComponents(includeCalendar: Boolean = false): NSDateComponents } /** - * Convert an Island Time [Time] to an equivalent `NSDateComponents` object. + * Converts this time to an equivalent `NSDateComponents` object. * @param includeCalendar `true` if the resulting `NSDateComponents` should include the ISO-8601 calendar * @return an equivalent `NSDateComponents` object */ @@ -63,7 +63,7 @@ fun Time.toNSDateComponents(includeCalendar: Boolean = false): NSDateComponents } /** - * Convert an Island Time [DateTime] to an equivalent `NSDateComponents` object. + * Converts this date-time to an equivalent `NSDateComponents` object. * @param includeCalendar `true` if the resulting `NSDateComponents` should include the ISO-8601 calendar * @return an equivalent `NSDateComponents` object */ @@ -75,7 +75,7 @@ fun DateTime.toNSDateComponents(includeCalendar: Boolean = false): NSDateCompone } /** - * Convert an Island Time [OffsetTime] to an equivalent `NSDateComponents` object. + * Converts this time to an equivalent `NSDateComponents` object. * @param includeCalendar `true` if the resulting `NSDateComponents` should include the ISO-8601 calendar * @return an equivalent `NSDateComponents` object */ @@ -88,7 +88,7 @@ fun OffsetTime.toNSDateComponents(includeCalendar: Boolean = false): NSDateCompo } /** - * Convert an Island Time [OffsetDateTime] to an equivalent `NSDateComponents` object. + * Converts this date-time to an equivalent `NSDateComponents` object. * @param includeCalendar `true` if the resulting `NSDateComponents` should include the ISO-8601 calendar * @return an equivalent `NSDateComponents` object */ @@ -101,7 +101,7 @@ fun OffsetDateTime.toNSDateComponents(includeCalendar: Boolean = false): NSDateC } /** - * Convert an Island Time [ZonedDateTime] to an equivalent `NSDateComponents` object. + * Converts this date-time to an equivalent `NSDateComponents` object. * @param includeCalendar `true` if the resulting `NSDateComponents` should include the ISO-8601 calendar * @return an equivalent `NSDateComponents` object */ @@ -114,35 +114,35 @@ fun ZonedDateTime.toNSDateComponents(includeCalendar: Boolean = false): NSDateCo } /** - * Convert an `NSDateComponents` object to an Island Time [Date]. + * Converts this set of date components to an Island Time [Date]. */ fun NSDateComponents.toIslandDate(): Date { return Date(year.convert(), month.convert(), day.convert()) } /** - * Convert an `NSDateComponents` object to an Island Time [Time]. + * Converts this set of date components to an Island Time [Time]. */ fun NSDateComponents.toIslandTime(): Time { return Time(hour.convert(), minute.convert(), second.convert(), nanosecond.convert()) } /** - * Convert an `NSDateComponents` object to an Island Time [DateTime]. + * Converts this set of date components to an Island Time [DateTime]. */ fun NSDateComponents.toIslandDateTime(): DateTime { return DateTime(toIslandDate(), toIslandTime()) } /** - * Convert an `NSDateComponents` object to an Island Time [OffsetDateTime]. + * Converts this set of date components to an Island Time [OffsetDateTime]. * @throws DateTimeException if the `timeZone` property is absent. */ fun NSDateComponents.toIslandOffsetDateTime() = toIslandOffsetDateTimeOrNull() ?: throw DateTimeException("The 'timeZone' property must be non-null") /** - * Convert an `NSDateComponents` object to an Island Time [OffsetDateTime] or `null` if the `timeZone` property is + * Converts this set of date components to an Island Time [OffsetDateTime], or `null` if the `timeZone` property is * absent. */ fun NSDateComponents.toIslandOffsetDateTimeOrNull(): OffsetDateTime? { @@ -153,14 +153,14 @@ fun NSDateComponents.toIslandOffsetDateTimeOrNull(): OffsetDateTime? { } /** - * Convert an `NSDateComponents` object to an Island Time [ZonedDateTime]. + * Converts this set of date components to an Island Time [ZonedDateTime]. * @throws DateTimeException if the `timeZone` property is absent. */ fun NSDateComponents.toIslandZonedDateTime() = toIslandZonedDateTimeOrNull() ?: throw DateTimeException("The 'timeZone' property must be non-null") /** - * Convert an `NSDateComponents` object to an Island Time [ZonedDateTime] or `null` if the `timeZone` property is + * Converts this set of date components to an Island Time [ZonedDateTime], or `null` if the `timeZone` property is * absent. */ fun NSDateComponents.toIslandZonedDateTimeOrNull(): ZonedDateTime? { @@ -168,7 +168,7 @@ fun NSDateComponents.toIslandZonedDateTimeOrNull(): ZonedDateTime? { } /** - * Convert an Island Time [TimePoint] to an `NSDate`. + * Converts this time point to an `NSDate`. */ fun TimePoint.toNSDate(): NSDate { return NSDate.dateWithTimeIntervalSince1970( @@ -177,7 +177,7 @@ fun TimePoint.toNSDate(): NSDate { } /** - * Convert an `NSDate` to an Island Time [Instant]. + * Converts this `NSDate` to an Island Time [Instant]. */ fun NSDate.toIslandInstant(): Instant { val unixEpochSecond = timeIntervalSince1970.toLong() @@ -186,7 +186,7 @@ fun NSDate.toIslandInstant(): Instant { } /** - * Convert an `NSDate` to an Island Time [DateTime] at the specified UTC offset. + * Converts this `NSDate` to an Island Time [DateTime] at the specified UTC offset. */ fun NSDate.toIslandDateTimeAt(offset: UtcOffset): DateTime { val unixEpochSecond = timeIntervalSince1970.toLong() @@ -195,12 +195,12 @@ fun NSDate.toIslandDateTimeAt(offset: UtcOffset): DateTime { } /** - * Convert an `NSDate` to an Island Time [DateTime] at the specified time zone. + * Converts this `NSDate` to an Island Time [DateTime] at the specified time zone. */ fun NSDate.toIslandDateTimeAt(nsTimeZone: NSTimeZone) = toIslandDateTimeAt(nsTimeZone.toIslandUtcOffsetAt(this)) /** - * Convert an `NSDate` to an Island Time [OffsetDateTime] at the specified UTC offset. + * Converts this `NSDate` to an Island Time [OffsetDateTime] at the specified [offset]. */ fun NSDate.toIslandOffsetDateTimeAt(offset: UtcOffset): OffsetDateTime { val unixEpochSecond = timeIntervalSince1970.toLong() @@ -209,14 +209,14 @@ fun NSDate.toIslandOffsetDateTimeAt(offset: UtcOffset): OffsetDateTime { } /** - * Convert an `NSDate` to an Island Time [OffsetDateTime] at the specified time zone. + * Converts this `NSDate` to an Island Time [OffsetDateTime] at the specified time zone. */ fun NSDate.toIslandOffsetDateTimeAt(nsTimeZone: NSTimeZone): OffsetDateTime { return toIslandOffsetDateTimeAt(nsTimeZone.toIslandUtcOffsetAt(this)) } /** - * Convert an `NSDate` to an Island Time [ZonedDateTime] at the specified time zone. + * Converts this `NSDate` to an Island Time [ZonedDateTime] at the specified time zone. */ fun NSDate.toIslandZonedDateTimeAt(zone: TimeZone): ZonedDateTime { val unixEpochSecond = timeIntervalSince1970.toLong() @@ -225,12 +225,12 @@ fun NSDate.toIslandZonedDateTimeAt(zone: TimeZone): ZonedDateTime { } /** - * Convert an `NSDate` to an Island Time [ZonedDateTime] at the specified time zone. + * Converts this `NSDate` to an Island Time [ZonedDateTime] at the specified time zone. */ fun NSDate.toIslandZonedDateTimeAt(nsTimeZone: NSTimeZone) = toIslandZonedDateTimeAt(nsTimeZone.toIslandTimeZone()) /** - * Convert an Island Time [UtcOffset] into an equivalent `NSTimeZone` with a fixed UTC offset. + * Converts this offset into an equivalent `NSTimeZone` with a fixed UTC offset. * * Note that `NSTimeZone`will round the `totalSeconds` value to the nearest minute. */ @@ -244,26 +244,26 @@ fun UtcOffset.toNSTimeZone(): NSTimeZone = NSTimeZone.timeZoneForSecondsFromGMT( fun NSTimeZone.toIslandUtcOffset(): UtcOffset = throw UnsupportedOperationException("Should not be called") /** - * Convert an `NSTimeZone` to an Island Time [UtcOffset] at the provided date. + * Converts this `NSTimeZone` to an Island Time [UtcOffset] at the provided date. */ fun NSTimeZone.toIslandUtcOffsetAt(date: NSDate): UtcOffset { return secondsFromGMTForDate(date).convert().seconds.asUtcOffset() } /** - * Convert an NSTimeZone` to an Island Time [TimeZone] with the same identifier. + * Converts this NSTimeZone` to an Island Time [TimeZone] with the same identifier. */ fun NSTimeZone.toIslandTimeZone(): TimeZone = TimeZone(name) /** - * Convert an Island Time [TimeZone] to an `NSTimeZone`. + * Converts this time zone to an `NSTimeZone`. * @throws TimeZoneRulesException if the identifier isn't recognized as valid for an `NSTimeZone` */ fun TimeZone.toNSTimeZone(): NSTimeZone = toNSTimeZoneOrNull() ?: throw TimeZoneRulesException("The identifier '$id' could not be converted to an NSTimeZone") /** - * Convert an Island Time [TimeZone] to an `NSTimeZone` or `null` if the identifier isn't recognized as valid for an + * Converts this time zone to an `NSTimeZone`, or `null` if the identifier isn't recognized as valid for an * `NSTimeZone`. */ fun TimeZone.toNSTimeZoneOrNull(): NSTimeZone? { @@ -275,96 +275,96 @@ fun TimeZone.toNSTimeZoneOrNull(): NSTimeZone? { } /** - * Convert to an equivalent `NSTimeInterval`. + * Converts this duration to an equivalent `NSTimeInterval`. */ fun Duration.toNSTimeInterval(): NSTimeInterval = toComponents { seconds, nanoseconds -> seconds.value.toDouble() + nanoseconds.value.toDouble() / NANOSECONDS_PER_SECOND } /** - * Convert to an equivalent `NSTimeInterval`. + * Converts this duration to an equivalent `NSTimeInterval`. */ fun IntDays.toNSTimeInterval(): NSTimeInterval = this.toLongDays().inSecondsUnchecked.value.toDouble() /** - * Convert to an equivalent `NSTimeInterval`. + * Converts this duration to an equivalent `NSTimeInterval`. */ fun LongDays.toNSTimeInterval(): NSTimeInterval = this.inSeconds.value.toDouble() /** - * Convert to an equivalent `NSTimeInterval`. + * Converts this duration to an equivalent `NSTimeInterval`. */ fun IntHours.toNSTimeInterval(): NSTimeInterval = this.toLongHours().inSecondsUnchecked.value.toDouble() /** - * Convert to an equivalent `NSTimeInterval`. + * Converts this duration to an equivalent `NSTimeInterval`. */ fun LongHours.toNSTimeInterval(): NSTimeInterval = this.inSeconds.value.toDouble() /** - * Convert to an equivalent `NSTimeInterval`. + * Converts this duration to an equivalent `NSTimeInterval`. */ fun IntMinutes.toNSTimeInterval(): NSTimeInterval = this.toLongMinutes().inSecondsUnchecked.value.toDouble() /** - * Convert to an equivalent `NSTimeInterval`. + * Converts this duration to an equivalent `NSTimeInterval`. */ fun LongMinutes.toNSTimeInterval(): NSTimeInterval = this.inSeconds.value.toDouble() /** - * Convert to an equivalent `NSTimeInterval`. + * Converts this duration to an equivalent `NSTimeInterval`. */ fun IntSeconds.toNSTimeInterval(): NSTimeInterval = value.toDouble() /** - * Convert to an equivalent `NSTimeInterval`. + * Converts this duration to an equivalent `NSTimeInterval`. */ fun LongSeconds.toNSTimeInterval(): NSTimeInterval = value.toDouble() /** - * Convert to an equivalent `NSTimeInterval`. + * Converts this duration to an equivalent `NSTimeInterval`. */ fun IntMilliseconds.toNSTimeInterval(): NSTimeInterval = toComponents { seconds, milliseconds -> seconds.value.toDouble() + milliseconds.value.toDouble() / MILLISECONDS_PER_SECOND } /** - * Convert to an equivalent `NSTimeInterval`. + * Converts this duration to an equivalent `NSTimeInterval`. */ fun LongMilliseconds.toNSTimeInterval(): NSTimeInterval = toComponents { seconds, milliseconds -> seconds.value.toDouble() + milliseconds.value.toDouble() / MILLISECONDS_PER_SECOND } /** - * Convert to an equivalent `NSTimeInterval`. + * Converts this duration to an equivalent `NSTimeInterval`. */ fun IntMicroseconds.toNSTimeInterval(): NSTimeInterval = toComponents { seconds, microseconds -> seconds.value.toDouble() + microseconds.value.toDouble() / MICROSECONDS_PER_SECOND } /** - * Convert to an equivalent `NSTimeInterval`. + * Converts this duration to an equivalent `NSTimeInterval`. */ fun LongMicroseconds.toNSTimeInterval(): NSTimeInterval = toComponents { seconds, microseconds -> seconds.value.toDouble() + microseconds.value.toDouble() / MICROSECONDS_PER_SECOND } /** - * Convert to an equivalent `NSTimeInterval`. + * Converts this duration to an equivalent `NSTimeInterval`. */ fun IntNanoseconds.toNSTimeInterval(): NSTimeInterval = toComponents { seconds, nanoseconds -> seconds.value.toDouble() + nanoseconds.value.toDouble() / NANOSECONDS_PER_SECOND } /** - * Convert to an equivalent `NSTimeInterval`. + * Converts this duration to an equivalent `NSTimeInterval`. */ fun LongNanoseconds.toNSTimeInterval(): NSTimeInterval = toComponents { seconds, nanoseconds -> seconds.value.toDouble() + nanoseconds.value.toDouble() / NANOSECONDS_PER_SECOND } /** - * Convert to an equivalent `NSDateInterval`. + * Converts this interval to an equivalent `NSDateInterval`. * @throws UnsupportedOperationException if the interval is unbounded */ fun > TimePointInterval.toNSDateInterval(): NSDateInterval { @@ -373,7 +373,7 @@ fun > TimePointInterval.toNSDateInterval(): NSDateInterval { } /** - * Convert to an equivalent `NSDateInterval` or `null` if the interval is unbounded. + * Converts this interval to an equivalent `NSDateInterval`, or `null` if the interval is unbounded. */ fun > TimePointInterval.toNSDateIntervalOrNull(): NSDateInterval? { return if (isBounded()) { @@ -384,7 +384,7 @@ fun > TimePointInterval.toNSDateIntervalOrNull(): NSDateInte } /** - * Convert to an equivalent Island Time [InstantInterval]. + * Converts this interval to an equivalent Island Time [InstantInterval]. */ fun NSDateInterval.toIslandInstantInterval(): InstantInterval { return startDate.toIslandInstant() until endDate.toIslandInstant() diff --git a/docs/basics/clocks.md b/docs/basics/clocks.md new file mode 100644 index 000000000..e777eb089 --- /dev/null +++ b/docs/basics/clocks.md @@ -0,0 +1,90 @@ +# Clocks + +Island Time's [`Clock`](../api/core/io.islandtime.clock/-clock/index.md) interface abstracts access to the current time, easing testing and opening up a number of other possibilities, such as offsetting the current time or modifying the precision. + +So while it's possible to retrieve the current system time like this: + +```kotlin +val now = ZonedDateTime.now() +``` + +... It's generally better practice to supply a `Clock` explicitly — ideally, via dependency injection. + +```kotlin +val clock: Clock = SystemClock() +val now = ZonedDateTime.now(clock) +``` + +Two implementations are provided out of the box — [`SystemClock`](../api/core/io.islandtime.clock/-system-clock/index.md), which accesses the current system time, and [`FixedClock`](../api/core/io.islandtime.clock/-fixed-clock/index.md), which returns a fixed time that can be controlled, making it well-suited for testing. + +## Time Zones + +Each [`Clock`](../api/core/io.islandtime.clock/-clock/index.md) has a time zone associated with it. The implementations included in Island Time treat it as an immutable property, requiring you to create a new clock if you wish to change it. Depending on the needs of your application, you may choose to respond to time zone change notifications generated by the system to do that. + +## System Clock Precision + +Island Time's [`SystemClock`](../api/core/io.islandtime.clock/-system-clock/index.md) provides the highest precision time available on each platform. In practice, that usually looks like this: + +| Platform | Precision | +| --- | --- | +| Java 8 | millisecond | +| Java 9+ | microsecond | +| Android | millisecond | +| Apple | microsecond | + +## Writing Tests with `FixedClock` + +The following example shows how [`FixedClock`](../api/core/io.islandtime.clock/-fixed-clock/index.md) can be used to add predictability when testing time-sensitive code. + +Let's assume we have a class called `ClassUnderTest`. + +```kotlin +class ClassUnderTest(private val clock: Clock) { + val createdAt: Instant = Instant.now(clock) + + var lastModified: Instant = createdAt + private set + + // ... + + fun performAction() { + // Do something + lastModified = Instant.now(clock) + } +} +``` + +And now to test it... + +```kotlin +@Test +fun testClassUnderTest() { + val clock = FixedClock("2020-08-25T04:30Z".toInstant(), TimeZone.UTC) + + val classUnderTest = ClassUnderTest(clock) + + // Now, let's increment the clock by an hour + clock += 1.hours + + classUnderTest.performAction() + + assertEquals("2020-08-25T04:30Z".toInstant(), classUnderTest.createdAt) + assertEquals("2020-08-25T05:30Z".toInstant(), classUnderTest.lastModified) +} +``` + +## Using a `java.time.Clock` + +On the JVM, it's possible to use a java.time `Clock` instead of an Island Time [`Clock`](../api/core/io.islandtime.clock/-clock/index.md) when calling any of the `now()` functions. + +```kotlin +val javaClock = java.time.Clock.system() +val today = Date.now(javaClock) +``` + +You can also take an existing Java `Clock` and make it compatible with Island Time's [`Clock`](../api/core/io.islandtime.clock/-clock/index.md) interface: + +```kotlin +val javaClock = java.time.Clock.system() +val islandClock = javaClock.asIslandClock() +``` diff --git a/docs/basics/dates-and-times.md b/docs/basics/dates-and-times.md index a5225f4f9..0ab2210a2 100644 --- a/docs/basics/dates-and-times.md +++ b/docs/basics/dates-and-times.md @@ -10,7 +10,7 @@ Island Time has a wide array of different date-time classes, each tailored to it | [`YearMonth`](../api/core/io.islandtime/-year-month/index.md) | month | `2020-02` | | [`Year`](../api/core/io.islandtime/-year/index.md) | year | `2020` | -The [`Date`](../api/core/io.islandtime/-date/index.md) class represents a date in an ambiguous region. It could be in New York City. It could be in Tokyo. The instants in time that define the start and end of a `Date` can only be determined in the context of a particular time zone — hence the _ambiguous_ part. +The [`Date`](../api/core/io.islandtime/-date/index.md) class represents a date in an ambiguous region. It could be in New York City, it could be in Tokyo. The instants in time that define the start and end of a `Date` can only be determined in the context of a particular time zone — hence the _ambiguous_ part. ```kotlin // Get the current date in the local time zone of the system @@ -50,7 +50,7 @@ val (hour, minute, second, nanosecond) = time ## Combined Date and Time of Day -A [`DateTime`](../api/core/io.islandtime/-date-time/index.md) combines a `Date` and `Time`, allowing you to represent both in a single data structure, still in an ambiguous region. +A [`DateTime`](../api/core/io.islandtime/-date-time/index.md) combines a [`Date`](../api/core/io.islandtime/-date/index.md) and [`Time`](../api/core/io.islandtime/-time/index.md), allowing you to represent both in a single data structure, still in an ambiguous region. ```kotlin // Create a date-time from individual date and time components @@ -68,11 +68,11 @@ val anotherDateTime: DateTime = date at Time.NOON val startOfDay: DateTime = date.startOfDay ``` -There's no guarantee that a particular `DateTime` will exist in every time zone and it could even exist twice, all thanks to the fun that is daylight savings time. We'll get into that more shortly, but it's important to keep this in mind since constructing or manipulating a `DateTime` directly can lead to subtle bugs. +There's no guarantee that a [`DateTime`](../api/core/io.islandtime/-date-time/index.md) will exist exactly once in a given time zone. Due to daylight savings time transitions, it may exist twice or it may not exist at all. We'll get into this more shortly, but it's important to keep in mind that working with and manipulating a [`DateTime`](../api/core/io.islandtime/-date-time/index.md) directly can lead to subtle bugs. ## Instants in Time -The classes we've looked at so far model dates and times in an ambiguous region, but often we want to unambiguously capture an instant in time. There are actually three different classes in Island Time that can do this, each serving a different purpose. +So far, the classes we've looked at model dates and times in an ambiguous region, but often we want to unambiguously capture an instant in time. There are three different classes in Island Time that can do this, each serving a different purpose. | Class | Description | | --- | --- | @@ -80,7 +80,7 @@ The classes we've looked at so far model dates and times in an ambiguous region, | [`ZonedDateTime`](../api/core/io.islandtime/-zoned-date-time/index.md) | A date and time of day in a particular time zone | | [`OffsetDateTime`](../api/core/io.islandtime/-offset-date-time/index.md) | A date and time of day with fixed UTC offset | -An [`Instant`](../api/core/io.islandtime/-instant/index.md) is simply a number of seconds and nanoseconds that have elpased since the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time) (`1970-01-01T00:00Z`), ignoring leap seconds. There's no concept of "date" without conversion to one of the other types. Practically speaking, this is the class you should use when you don't care about the local time and just want a [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) timestamp. +An [`Instant`](../api/core/io.islandtime/-instant/index.md) is simply a number of seconds and nanoseconds that have elapsed since the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time) (`1970-01-01T00:00Z`), ignoring leap seconds. There's no concept of "date" without conversion to one of the other types. Practically speaking, this is the class you should use when you don't care about the local time and just want a [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) timestamp. ```kotlin data class DogDto( @@ -91,21 +91,21 @@ data class DogDto( ) ``` -To capture an instant along with the local time, you have two options — [`OffsetDateTime`](../api/core/io.islandtime/-offset-date-time/index.md) and [`ZonedDateTime`](../api/core/io.islandtime/-zoned-date-time/index.md). Both store a date-time with an offset from UTC, however, `ZonedDateTime` is also aware of time zone rules, which is an important distinction. +To capture an instant along with the local time, you have two options — [`OffsetDateTime`](../api/core/io.islandtime/-offset-date-time/index.md) and [`ZonedDateTime`](../api/core/io.islandtime/-zoned-date-time/index.md). Both store a `DateTime` along with a `UtcOffset`, however, `ZonedDateTime` is also aware of time zone rules, which is an important distinction. -### `UtcOffset` vs. `TimeZone` +### `TimeZone` vs. `UtcOffset` In Island Time, a [`UtcOffset`](../api/core/io.islandtime/-utc-offset/index.md) is just a number of seconds that a local time must be adjusted forward or backward by to be equivalent to UTC. A [`TimeZone`](../api/core/io.islandtime/-time-zone/index.md) defines the rules used to determine the UTC offset. Time zones fall into two categories — region-based (`TimeZone.Region`) and fixed offset (`TimeZone.FixedOffset`). Region-based zones have identifiers, such as "America/New_York" or "Europe/London", that correspond to entries in the [IANA Time Zone Database](https://www.iana.org/time-zones). -Fixed offset zones have a fixed UTC offset. While region-based zones are generally preferrable, a suitable one may not exist in all situations and sometimes you just want a fixed offset. +Fixed offset zones have a fixed UTC offset. While region-based zones are generally preferrable, a suitable one may not exist in all situations. ### `ZonedDateTime` vs. `OffsetDateTime` -Most platforms nowadays draw their understanding of time zones from the [IANA Time Zone Database](https://www.iana.org/time-zones), but time zones and their rules change all the time and different systems might have different versions of the database or only a subset of it available. This makes persistance and serialization of `ZonedDateTime` troublesome since there's the possibility that when it gets read later, the zone can't be found or its rules have changed, thus altering the local date and time. +While most platforms nowadays draw their understanding of time zones from the [IANA Time Zone Database](https://www.iana.org/time-zones), time zones and their rules change all the time and different systems might have different versions of the database or only a subset of it available. This makes persistance and serialization of `ZonedDateTime` troublesome since there's the possibility that when the stored data gets read later, the zone can't be found or its rules have changed, thus altering the local date and time. -Using `OffsetDateTime` guarantees that you'll never get an exception due to an unavailable time zone and that the value you save will be the value that's read later, making it well-suited for this particular use case. More often than not though, you should use `ZonedDateTime` since it will handle daylight savings transitions correctly when doing any sort of calendar math, but you may want to consider converting to an `OffsetDateTime` when you persist or serialize your data. +Using `OffsetDateTime` guarantees that you'll never get an exception due to an unavailable time zone and that the value you save will be the value that's read later, making it well-suited for this particular use case. More often than not, you should use `ZonedDateTime` since it will handle daylight savings transitions correctly when doing any sort of calendar math, but you may want to consider converting to an `OffsetDateTime` when you persist or serialize your data. ```kotlin val date = Date(2020, Month.MARCH, 8) @@ -140,7 +140,7 @@ println(zonedDateTime.adjustedTo(TimeZone("America/Los_Angeles"))) ## Patterns, Properties, and Operators -Throughout Island Time's date-time primitives, you'll find a set of patterns that remain (relatively) constant as well as a number of properties and operators that simplify common tasks. +Throughout Island Time's date-time primitives, you'll find a set of patterns that remain (relatively) constant, as well as a number of properties and operators that simplify common tasks. ### `at` diff --git a/docs/basics/interop.md b/docs/basics/interop.md index d538d6a8c..8e6b36bc4 100644 --- a/docs/basics/interop.md +++ b/docs/basics/interop.md @@ -8,17 +8,17 @@ Island Time's classes map very closely to those in [java.time](https://docs.orac | java.time | Island Time | Description | | --- | --- | --- | -| `LocalDate` | `Date` | A date in arbitrary region | -| `LocalTime` | `Time` | A time of day in arbitrary region | -| `LocalDateTime` | `DateTime` | A combined date and time of day in arbitrary region | -| `Instant` | `Instant` | An instant in time, represented by the number of seconds/nanoseconds relative to the Unix epoch (1970-01-01T00:00Z) | -| `OffsetTime` | `OffsetTime` | A time of day with UTC offset | -| `OffsetDateTime` | `OffsetDateTime` | A date and time of day with fixed UTC offset | -| `ZonedDateTime` | `ZonedDateTime` | A date and time of day in a particular time zone region | -| `ZoneOffset` | `UtcOffset` | An offset from UTC | -| `ZoneId` | `TimeZone` | An IANA time zone database region ID or fixed offset from UTC | -| `Duration` | `Duration` | A (potentially large) duration of time | -| `Period` | `Period` | A date-based period of time | +| `LocalDate` | [`Date`](../api/core/io.islandtime/-date/index.md) | A date in arbitrary region | +| `LocalTime` | [`Time`](../api/core/io.islandtime/-time/index.md) | A time of day in arbitrary region | +| `LocalDateTime` | [`DateTime`](../api/core/io.islandtime/-date-time/index.md) | A combined date and time of day in arbitrary region | +| `Instant` | [`Instant`](../api/core/io.islandtime/-instant/index.md) | An instant in time, represented by the number of seconds/nanoseconds relative to the Unix epoch (`1970-01-01T00:00Z`) | +| `OffsetTime` | [`OffsetTime`](../api/core/io.islandtime/-offset-time/index.md) | A time of day with UTC offset | +| `OffsetDateTime` | [`OffsetDateTime`](../api/core/io.islandtime/-offset-date-time/index.md) | A date and time of day with fixed UTC offset | +| `ZonedDateTime` | [`ZonedDateTime`](../api/core/io.islandtime/-zoned-date-time/index.md) | A date and time of day in a particular time zone region | +| `ZoneOffset` | [`UtcOffset`](../api/core/io.islandtime/-utc-offset/index.md) | An offset from UTC | +| `ZoneId` | [`TimeZone`](../api/core/io.islandtime/-time-zone/index.md) | An IANA time zone database region ID or fixed offset from UTC | +| `Duration` | [`Duration`](../api/core/io.islandtime.measures/-duration/index.md) | A (potentially large) duration of time | +| `Period` | [`Period`](../api/core/io.islandtime.measures/-period/index.md) | A date-based period of time | To convert between an Island Time `Date` and Java `LocalDate`, you can do something like this: @@ -42,7 +42,7 @@ You can find the full set of conversions in the [io.islandtime.jvm](../api/core/ ## Apple Foundation Classes -We can map between some of the Island Time types and the date-time types provided in Apple's [Foundation API](https://developer.apple.com/documentation/foundation/dates_and_times?language=objc), such as `NSDate`, `NSDateComponents`, and `NSTimeZone`. Keep in mind that `NSDate` and `NSTimeInterval` are based around floating-point numbers, so conversion can result in lost precision sometimes. +We can map between some of the Island Time types and the date-time types provided in Apple's [Foundation API](https://developer.apple.com/documentation/foundation/dates_and_times?language=objc), such as `NSDate`, `NSDateComponents`, and `NSTimeZone`. Keep in mind that `NSDate` and `NSTimeInterval` are based around floating-point numbers, so conversion may result in lost precision. ```kotlin // NSDate is a timestamp, just like Instant @@ -64,7 +64,7 @@ The full set of conversions can be found in the [io.islandtime.darwin](../api/co ## kotlin.time -The Kotlin Standard Library has an experimental [`Duration`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-duration/) type of its own, which you can convert to and from Island Time durations. Kotlin's `Duration` is based on a floating-point number, so keep in mind that conversion can be lossy. +The Kotlin Standard Library has an experimental [`Duration`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-duration/) type of its own, which you can convert to and from Island Time durations. Kotlin's `Duration` is based on a floating-point number, so keep in mind that conversion may result in lost precision. ```kotlin import kotlin.time.seconds as kotlinSeconds diff --git a/docs/basics/overview.md b/docs/basics/overview.md index 7f90f38c6..2d187fb6b 100644 --- a/docs/basics/overview.md +++ b/docs/basics/overview.md @@ -1,19 +1,21 @@ # Overview -Being heavily inspired by the java.time library, Island Time tends to follow many of the same design principles. +Being heavily inspired by the java.time library, Island Time should look fairly familiar to those acquainted with it and tends to follow many of the same design principles. -#### Immutability +## General Design + +### Immutability All date-time primitives are immutable and thread-safe. Operations that manipulate a date, time, duration, or interval will always return a new object. -#### Precision +### Precision Island Time uses integer rather than floating-point values, offering a fixed nanosecond precision across the entire supported time scale. This avoids any surprises that might emerge from the use of floating-point arithmetic and the reduction in precision that occurs when representing larger durations. -#### Overflow Handling +### Overflow Handling When working with dates and times, overflow is almost never a behavior that you want. See [Y2k](https://en.wikipedia.org/wiki/Year_2000_problem) or [Time formatting and storage bugs](https://en.wikipedia.org/wiki/Time_formatting_and_storage_bugs). Island Time uses checked arithmetic throughout to detect overflow and throw exceptions rather than failing silently. -#### Type-Safety +### Type-Safety In general, Island Time tries to prevent nonsensical operations at compile time rather than runtime. To that end, you'll find that there are a lot more classes than there are in a number of other date-time libraries. diff --git a/docs/getting-started.md b/docs/getting-started.md index 082502e5c..ca176413a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -16,7 +16,7 @@ As a [Kotlin Multiplatform](https://kotlinlang.org/docs/reference/multiplatform. !!! warning "Important" Your project's Kotlin compiler version must match the version used by Island Time. -Due to the experimental status of [inline classes](https://kotlinlang.org/docs/reference/inline-classes.html), which are used in Island Time's public API, the version of Kotlin that you use in your project must match the version used by Island Time — even for non-native targets. Those of you who are using [Kotlin/Native](https://kotlinlang.org/docs/reference/native-overview.html) are probably already accustomed to dealing with this since there is no binary compatibility between releases yet. +Due to the experimental status of [inline classes](https://kotlinlang.org/docs/reference/inline-classes.html), which are used in Island Time's public API, the version of Kotlin that you use in your project must match the version used by Island Time — even for non-native targets. | Island Time Version | Kotlin Version | | --- | --- | @@ -26,11 +26,11 @@ Due to the experimental status of [inline classes](https://kotlinlang.org/docs/r ### JVM -Island Time requires Java 8 or above. +Java 8 or above is required. ### Android -Island Time requires Android Gradle Plugin 4.0 or later and a minimum compile SDK of API 21. +Android Gradle Plugin 4.0 or above and a minimum compile SDK of API 21 are required. ## Gradle Setup @@ -79,7 +79,7 @@ You'll need to turn on [core library desugaring](https://developer.android.com/s } dependencies { - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.9' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' } ``` @@ -93,7 +93,7 @@ You'll need to turn on [core library desugaring](https://developer.android.com/s compileOptions { // Flag to enable support for the new language APIs - coreLibraryDesugaringEnabled = true + isCoreLibraryDesugaringEnabled = true // Sets Java compatibility to Java 8 sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 @@ -101,7 +101,7 @@ You'll need to turn on [core library desugaring](https://developer.android.com/s } dependencies { - coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.0.9") + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.0.10") } ``` diff --git a/mkdocs.yml b/mkdocs.yml index 63f7a6e10..aaaf2b2f1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -18,6 +18,7 @@ nav: - 'basics/interop.md' - 'basics/formatting.md' - 'basics/parsing.md' + - 'basics/clocks.md' - 'Advanced Topics': - 'advanced/custom-providers.md' - 'Extensions': diff --git a/tools/mkdocs-dokka-plugin/build.gradle.kts b/tools/mkdocs-dokka-plugin/build.gradle.kts index a5d2550fe..605326d44 100644 --- a/tools/mkdocs-dokka-plugin/build.gradle.kts +++ b/tools/mkdocs-dokka-plugin/build.gradle.kts @@ -10,8 +10,8 @@ repositories { } dependencies { - compileOnly("org.jetbrains.dokka:dokka-core:1.4.0-dev-38") - implementation("org.jetbrains.dokka:dokka-base:1.4.0-dev-38") + compileOnly("org.jetbrains.dokka:dokka-core:1.4.0-dev-53") + implementation("org.jetbrains.dokka:dokka-base:1.4.0-dev-53") } tasks.withType().configureEach { diff --git a/tools/mkdocs-dokka-plugin/src/main/kotlin/MkdocsPlugin.kt b/tools/mkdocs-dokka-plugin/src/main/kotlin/MkdocsPlugin.kt index 6bd69c954..10206ecef 100644 --- a/tools/mkdocs-dokka-plugin/src/main/kotlin/MkdocsPlugin.kt +++ b/tools/mkdocs-dokka-plugin/src/main/kotlin/MkdocsPlugin.kt @@ -54,13 +54,17 @@ class MarkdownContent { private val stringBuilder = StringBuilder() fun append(content: String): Unit = with(stringBuilder) { - if (inInlineCodeBlock && codeBlockIsTerminated) { - //append('`') - append("") - codeBlockIsTerminated = false - } + if (inInlineCodeBlock) { + if (codeBlockIsTerminated) { + //append('`') + append("") + codeBlockIsTerminated = false + } - append(content) + append(content.replace("<", "<").replace(">", ">")) + } else { + append(content) + } } fun appendNonCode(content: String): Unit = with(stringBuilder) {