diff --git a/build.gradle.kts b/build.gradle.kts index 5db167372..6eefe39f9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("kotlinx.team.infra") version "0.4.0-dev-81" + id("kotlinx.team.infra") version "0.4.0-dev-82" kotlin("multiplatform") apply false id("org.jetbrains.kotlinx.kover") version "0.8.0-Beta2" } diff --git a/core/api/kotlinx-datetime.api b/core/api/kotlinx-datetime.api index 1f554210f..0b7e0e04a 100644 --- a/core/api/kotlinx-datetime.api +++ b/core/api/kotlinx-datetime.api @@ -12,9 +12,14 @@ public final class kotlinx/datetime/Clock$System : kotlinx/datetime/Clock { } public final class kotlinx/datetime/ClockKt { - public static final fun asClock (Lkotlin/time/TimeSource;Lkotlinx/datetime/Instant;)Lkotlinx/datetime/Clock; + public static final fun asClock (Lkotlin/time/TimeSource;Lkotlin/time/Instant;)Lkotlin/time/Clock; + public static final fun asTimeSource (Lkotlin/time/Clock;)Lkotlin/time/TimeSource$WithComparableMarks; public static final fun asTimeSource (Lkotlinx/datetime/Clock;)Lkotlin/time/TimeSource$WithComparableMarks; + public static final fun toDeprecatedClock (Lkotlin/time/Clock;)Lkotlinx/datetime/Clock; + public static final fun toStdlibClock (Lkotlinx/datetime/Clock;)Lkotlin/time/Clock; + public static final fun todayAt (Lkotlin/time/Clock;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDate; public static final fun todayAt (Lkotlinx/datetime/Clock;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDate; + public static final fun todayIn (Lkotlin/time/Clock;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDate; public static final fun todayIn (Lkotlinx/datetime/Clock;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDate; } @@ -239,22 +244,42 @@ public final class kotlinx/datetime/Instant$Companion { } public final class kotlinx/datetime/InstantJvmKt { + public static final fun minus (Lkotlin/time/Instant;ILkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlin/time/Instant; public static final fun minus (Lkotlinx/datetime/Instant;ILkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; + public static final fun periodUntil (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/DateTimePeriod; public static final fun periodUntil (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/DateTimePeriod; + public static final fun plus (Lkotlin/time/Instant;ILkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlin/time/Instant; + public static final fun plus (Lkotlin/time/Instant;JLkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlin/time/Instant; + public static final fun plus (Lkotlin/time/Instant;JLkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlin/time/Instant; + public static final fun plus (Lkotlin/time/Instant;Lkotlinx/datetime/DateTimePeriod;Lkotlinx/datetime/TimeZone;)Lkotlin/time/Instant; + public static final fun plus (Lkotlin/time/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlin/time/Instant; public static final fun plus (Lkotlinx/datetime/Instant;ILkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; public static final fun plus (Lkotlinx/datetime/Instant;JLkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/datetime/Instant; public static final fun plus (Lkotlinx/datetime/Instant;JLkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; public static final fun plus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimePeriod;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; public static final fun plus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; + public static final fun until (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)J public static final fun until (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)J } public final class kotlinx/datetime/InstantKt { + public static final fun daysUntil (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/TimeZone;)I public static final fun daysUntil (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)I + public static final fun format (Lkotlin/time/Instant;Lkotlinx/datetime/format/DateTimeFormat;Lkotlinx/datetime/UtcOffset;)Ljava/lang/String; public static final fun format (Lkotlinx/datetime/Instant;Lkotlinx/datetime/format/DateTimeFormat;Lkotlinx/datetime/UtcOffset;)Ljava/lang/String; + public static synthetic fun format$default (Lkotlin/time/Instant;Lkotlinx/datetime/format/DateTimeFormat;Lkotlinx/datetime/UtcOffset;ILjava/lang/Object;)Ljava/lang/String; public static synthetic fun format$default (Lkotlinx/datetime/Instant;Lkotlinx/datetime/format/DateTimeFormat;Lkotlinx/datetime/UtcOffset;ILjava/lang/Object;)Ljava/lang/String; public static final fun isDistantFuture (Lkotlinx/datetime/Instant;)Z public static final fun isDistantPast (Lkotlinx/datetime/Instant;)Z + public static final fun minus (Lkotlin/time/Instant;ILkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlin/time/Instant; + public static final fun minus (Lkotlin/time/Instant;JLkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlin/time/Instant; + public static final fun minus (Lkotlin/time/Instant;JLkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlin/time/Instant; + public static final fun minus (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)J + public static final fun minus (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)J + public static final fun minus (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/DateTimePeriod; + public static final fun minus (Lkotlin/time/Instant;Lkotlinx/datetime/DateTimePeriod;Lkotlinx/datetime/TimeZone;)Lkotlin/time/Instant; + public static final fun minus (Lkotlin/time/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlin/time/Instant; + public static final fun minus (Lkotlin/time/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlin/time/Instant; public static final fun minus (Lkotlinx/datetime/Instant;ILkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/datetime/Instant; public static final fun minus (Lkotlinx/datetime/Instant;JLkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/datetime/Instant; public static final fun minus (Lkotlinx/datetime/Instant;JLkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; @@ -264,11 +289,19 @@ public final class kotlinx/datetime/InstantKt { public static final fun minus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)J public static final fun minus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)J public static final fun minus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/DateTimePeriod; + public static final fun monthsUntil (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/TimeZone;)I public static final fun monthsUntil (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)I + public static final fun parse (Lkotlin/time/Instant$Companion;Ljava/lang/CharSequence;Lkotlinx/datetime/format/DateTimeFormat;)Lkotlin/time/Instant; + public static final fun plus (Lkotlin/time/Instant;ILkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlin/time/Instant; + public static final fun plus (Lkotlin/time/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlin/time/Instant; public static final fun plus (Lkotlinx/datetime/Instant;ILkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/datetime/Instant; public static final fun plus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/datetime/Instant; + public static final fun toDeprecatedInstant (Lkotlin/time/Instant;)Lkotlinx/datetime/Instant; public static final fun toInstant (Ljava/lang/String;)Lkotlinx/datetime/Instant; + public static final fun toStdlibInstant (Lkotlinx/datetime/Instant;)Lkotlin/time/Instant; + public static final fun until (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)J public static final fun until (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)J + public static final fun yearsUntil (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/TimeZone;)I public static final fun yearsUntil (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)I } @@ -472,6 +505,9 @@ public final class kotlinx/datetime/MonthKt { public static final fun getNumber (Lkotlinx/datetime/Month;)I } +public final class kotlinx/datetime/OverloadMarker { +} + public final class kotlinx/datetime/Ser : java/io/Externalizable { public static final field Companion Lkotlinx/datetime/Ser$Companion; public static final field DATE_TAG I @@ -493,6 +529,9 @@ public class kotlinx/datetime/TimeZone { public final fun getId ()Ljava/lang/String; public fun hashCode ()I public final fun toInstant (Lkotlinx/datetime/LocalDateTime;)Lkotlinx/datetime/Instant; + public final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/OverloadMarker;)Lkotlin/time/Instant; + public static synthetic fun toInstant$default (Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlin/time/Instant; + public final fun toLocalDateTime (Lkotlin/time/Instant;)Lkotlinx/datetime/LocalDateTime; public final fun toLocalDateTime (Lkotlinx/datetime/Instant;)Lkotlinx/datetime/LocalDateTime; public fun toString ()Ljava/lang/String; } @@ -507,11 +546,21 @@ public final class kotlinx/datetime/TimeZone$Companion { public final class kotlinx/datetime/TimeZoneKt { public static final fun atStartOfDayIn (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; + public static final fun atStartOfDayIn (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/OverloadMarker;)Lkotlin/time/Instant; + public static synthetic fun atStartOfDayIn$default (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlin/time/Instant; + public static final fun offsetAt (Lkotlinx/datetime/TimeZone;Lkotlin/time/Instant;)Lkotlinx/datetime/UtcOffset; public static final fun offsetAt (Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/Instant;)Lkotlinx/datetime/UtcOffset; + public static final fun offsetIn (Lkotlin/time/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/UtcOffset; public static final fun offsetIn (Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/UtcOffset; public static final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; + public static final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/OverloadMarker;)Lkotlin/time/Instant; public static final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/UtcOffset;)Lkotlinx/datetime/Instant; + public static final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/UtcOffset;Lkotlinx/datetime/OverloadMarker;)Lkotlin/time/Instant; + public static synthetic fun toInstant$default (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlin/time/Instant; + public static synthetic fun toInstant$default (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/UtcOffset;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlin/time/Instant; + public static final fun toLocalDateTime (Lkotlin/time/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDateTime; public static final fun toLocalDateTime (Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDateTime; + public static final fun toLocalDateTime (Lkotlinx/datetime/Instant;Lkotlinx/datetime/UtcOffset;)Lkotlinx/datetime/LocalDateTime; } public final class kotlinx/datetime/UtcOffset : java/io/Serializable { @@ -582,6 +631,7 @@ public final class kotlinx/datetime/format/DateTimeComponents { public final fun setAmPm (Lkotlinx/datetime/format/AmPmMarker;)V public final fun setDate (Lkotlinx/datetime/LocalDate;)V public final fun setDateTime (Lkotlinx/datetime/LocalDateTime;)V + public final fun setDateTimeOffset (Lkotlin/time/Instant;Lkotlinx/datetime/UtcOffset;)V public final fun setDateTimeOffset (Lkotlinx/datetime/Instant;Lkotlinx/datetime/UtcOffset;)V public final fun setDateTimeOffset (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/UtcOffset;)V public final fun setDay (Ljava/lang/Integer;)V @@ -604,6 +654,8 @@ public final class kotlinx/datetime/format/DateTimeComponents { public final fun setTimeZoneId (Ljava/lang/String;)V public final fun setYear (Ljava/lang/Integer;)V public final fun toInstantUsingOffset ()Lkotlinx/datetime/Instant; + public final fun toInstantUsingOffset (Lkotlinx/datetime/OverloadMarker;)Lkotlin/time/Instant; + public static synthetic fun toInstantUsingOffset$default (Lkotlinx/datetime/format/DateTimeComponents;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlin/time/Instant; public final fun toLocalDate ()Lkotlinx/datetime/LocalDate; public final fun toLocalDateTime ()Lkotlinx/datetime/LocalDateTime; public final fun toLocalTime ()Lkotlinx/datetime/LocalTime; diff --git a/core/api/kotlinx-datetime.klib.api b/core/api/kotlinx-datetime.klib.api index 129b736dc..53cb2380b 100644 --- a/core/api/kotlinx-datetime.klib.api +++ b/core/api/kotlinx-datetime.klib.api @@ -193,11 +193,13 @@ final class kotlinx.datetime.format/DateTimeComponents { // kotlinx.datetime.for final fun setDate(kotlinx.datetime/LocalDate) // kotlinx.datetime.format/DateTimeComponents.setDate|setDate(kotlinx.datetime.LocalDate){}[0] final fun setDateTime(kotlinx.datetime/LocalDateTime) // kotlinx.datetime.format/DateTimeComponents.setDateTime|setDateTime(kotlinx.datetime.LocalDateTime){}[0] + final fun setDateTimeOffset(kotlin.time/Instant, kotlinx.datetime/UtcOffset) // kotlinx.datetime.format/DateTimeComponents.setDateTimeOffset|setDateTimeOffset(kotlin.time.Instant;kotlinx.datetime.UtcOffset){}[0] final fun setDateTimeOffset(kotlinx.datetime/Instant, kotlinx.datetime/UtcOffset) // kotlinx.datetime.format/DateTimeComponents.setDateTimeOffset|setDateTimeOffset(kotlinx.datetime.Instant;kotlinx.datetime.UtcOffset){}[0] final fun setDateTimeOffset(kotlinx.datetime/LocalDateTime, kotlinx.datetime/UtcOffset) // kotlinx.datetime.format/DateTimeComponents.setDateTimeOffset|setDateTimeOffset(kotlinx.datetime.LocalDateTime;kotlinx.datetime.UtcOffset){}[0] final fun setOffset(kotlinx.datetime/UtcOffset) // kotlinx.datetime.format/DateTimeComponents.setOffset|setOffset(kotlinx.datetime.UtcOffset){}[0] final fun setTime(kotlinx.datetime/LocalTime) // kotlinx.datetime.format/DateTimeComponents.setTime|setTime(kotlinx.datetime.LocalTime){}[0] final fun toInstantUsingOffset(): kotlinx.datetime/Instant // kotlinx.datetime.format/DateTimeComponents.toInstantUsingOffset|toInstantUsingOffset(){}[0] + final fun toInstantUsingOffset(kotlinx.datetime/OverloadMarker = ...): kotlin.time/Instant // kotlinx.datetime.format/DateTimeComponents.toInstantUsingOffset|toInstantUsingOffset(kotlinx.datetime.OverloadMarker){}[0] final fun toLocalDate(): kotlinx.datetime/LocalDate // kotlinx.datetime.format/DateTimeComponents.toLocalDate|toLocalDate(){}[0] final fun toLocalDateTime(): kotlinx.datetime/LocalDateTime // kotlinx.datetime.format/DateTimeComponents.toLocalDateTime|toLocalDateTime(){}[0] final fun toLocalTime(): kotlinx.datetime/LocalTime // kotlinx.datetime.format/DateTimeComponents.toLocalTime|toLocalTime(){}[0] @@ -461,6 +463,8 @@ final class kotlinx.datetime/LocalTime : kotlin/Comparable(): kotlin/Int // kotlinx.datetime/UtcOffset.totalSeconds.|(){}[0] @@ -493,8 +497,10 @@ open class kotlinx.datetime/TimeZone { // kotlinx.datetime/TimeZone|null[0] open val id // kotlinx.datetime/TimeZone.id|{}id[0] open fun (): kotlin/String // kotlinx.datetime/TimeZone.id.|(){}[0] + final fun (kotlin.time/Instant).toLocalDateTime(): kotlinx.datetime/LocalDateTime // kotlinx.datetime/TimeZone.toLocalDateTime|toLocalDateTime@kotlin.time.Instant(){}[0] final fun (kotlinx.datetime/Instant).toLocalDateTime(): kotlinx.datetime/LocalDateTime // kotlinx.datetime/TimeZone.toLocalDateTime|toLocalDateTime@kotlinx.datetime.Instant(){}[0] final fun (kotlinx.datetime/LocalDateTime).toInstant(): kotlinx.datetime/Instant // kotlinx.datetime/TimeZone.toInstant|toInstant@kotlinx.datetime.LocalDateTime(){}[0] + final fun (kotlinx.datetime/LocalDateTime).toInstant(kotlinx.datetime/OverloadMarker = ...): kotlin.time/Instant // kotlinx.datetime/TimeZone.toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.OverloadMarker){}[0] open fun equals(kotlin/Any?): kotlin/Boolean // kotlinx.datetime/TimeZone.equals|equals(kotlin.Any?){}[0] open fun hashCode(): kotlin/Int // kotlinx.datetime/TimeZone.hashCode|hashCode(){}[0] open fun toString(): kotlin/String // kotlinx.datetime/TimeZone.toString|toString(){}[0] @@ -817,8 +823,40 @@ final val kotlinx.datetime/isoDayNumber // kotlinx.datetime/isoDayNumber|@kotlin final val kotlinx.datetime/number // kotlinx.datetime/number|@kotlinx.datetime.Month{}number[0] final fun (kotlinx.datetime/Month).(): kotlin/Int // kotlinx.datetime/number.|@kotlinx.datetime.Month(){}[0] +final fun (kotlin.time/Clock).kotlinx.datetime/asTimeSource(): kotlin.time/TimeSource.WithComparableMarks // kotlinx.datetime/asTimeSource|asTimeSource@kotlin.time.Clock(){}[0] +final fun (kotlin.time/Clock).kotlinx.datetime/toDeprecatedClock(): kotlinx.datetime/Clock // kotlinx.datetime/toDeprecatedClock|toDeprecatedClock@kotlin.time.Clock(){}[0] +final fun (kotlin.time/Clock).kotlinx.datetime/todayAt(kotlinx.datetime/TimeZone): kotlinx.datetime/LocalDate // kotlinx.datetime/todayAt|todayAt@kotlin.time.Clock(kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Clock).kotlinx.datetime/todayIn(kotlinx.datetime/TimeZone): kotlinx.datetime/LocalDate // kotlinx.datetime/todayIn|todayIn@kotlin.time.Clock(kotlinx.datetime.TimeZone){}[0] final fun (kotlin.time/Duration).kotlinx.datetime/toDateTimePeriod(): kotlinx.datetime/DateTimePeriod // kotlinx.datetime/toDateTimePeriod|toDateTimePeriod@kotlin.time.Duration(){}[0] -final fun (kotlin.time/TimeSource).kotlinx.datetime/asClock(kotlinx.datetime/Instant): kotlinx.datetime/Clock // kotlinx.datetime/asClock|asClock@kotlin.time.TimeSource(kotlinx.datetime.Instant){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/daysUntil(kotlin.time/Instant, kotlinx.datetime/TimeZone): kotlin/Int // kotlinx.datetime/daysUntil|daysUntil@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/format(kotlinx.datetime.format/DateTimeFormat, kotlinx.datetime/UtcOffset = ...): kotlin/String // kotlinx.datetime/format|format@kotlin.time.Instant(kotlinx.datetime.format.DateTimeFormat;kotlinx.datetime.UtcOffset){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlin.time/Instant, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin/Long // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlin.time/Instant, kotlinx.datetime/DateTimeUnit.TimeBased): kotlin/Long // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlin.time/Instant, kotlinx.datetime/TimeZone): kotlinx.datetime/DateTimePeriod // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlin/Int, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin.time/Instant // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlin.Int;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlin/Int, kotlinx.datetime/DateTimeUnit.TimeBased): kotlin.time/Instant // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlin.Int;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlin/Long, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin.time/Instant // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlin.Long;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlin/Long, kotlinx.datetime/DateTimeUnit.TimeBased): kotlin.time/Instant // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlin.Long;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlinx.datetime/DateTimePeriod, kotlinx.datetime/TimeZone): kotlin.time/Instant // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlinx.datetime.DateTimePeriod;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin.time/Instant // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlinx.datetime/DateTimeUnit.TimeBased): kotlin.time/Instant // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/monthsUntil(kotlin.time/Instant, kotlinx.datetime/TimeZone): kotlin/Int // kotlinx.datetime/monthsUntil|monthsUntil@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/offsetIn(kotlinx.datetime/TimeZone): kotlinx.datetime/UtcOffset // kotlinx.datetime/offsetIn|offsetIn@kotlin.time.Instant(kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/periodUntil(kotlin.time/Instant, kotlinx.datetime/TimeZone): kotlinx.datetime/DateTimePeriod // kotlinx.datetime/periodUntil|periodUntil@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/plus(kotlin/Int, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin.time/Instant // kotlinx.datetime/plus|plus@kotlin.time.Instant(kotlin.Int;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/plus(kotlin/Int, kotlinx.datetime/DateTimeUnit.TimeBased): kotlin.time/Instant // kotlinx.datetime/plus|plus@kotlin.time.Instant(kotlin.Int;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/plus(kotlin/Long, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin.time/Instant // kotlinx.datetime/plus|plus@kotlin.time.Instant(kotlin.Long;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/plus(kotlin/Long, kotlinx.datetime/DateTimeUnit.TimeBased): kotlin.time/Instant // kotlinx.datetime/plus|plus@kotlin.time.Instant(kotlin.Long;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/plus(kotlinx.datetime/DateTimePeriod, kotlinx.datetime/TimeZone): kotlin.time/Instant // kotlinx.datetime/plus|plus@kotlin.time.Instant(kotlinx.datetime.DateTimePeriod;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/plus(kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin.time/Instant // kotlinx.datetime/plus|plus@kotlin.time.Instant(kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/plus(kotlinx.datetime/DateTimeUnit.TimeBased): kotlin.time/Instant // kotlinx.datetime/plus|plus@kotlin.time.Instant(kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/toDeprecatedInstant(): kotlinx.datetime/Instant // kotlinx.datetime/toDeprecatedInstant|toDeprecatedInstant@kotlin.time.Instant(){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/toLocalDateTime(kotlinx.datetime/TimeZone): kotlinx.datetime/LocalDateTime // kotlinx.datetime/toLocalDateTime|toLocalDateTime@kotlin.time.Instant(kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/until(kotlin.time/Instant, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin/Long // kotlinx.datetime/until|until@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/until(kotlin.time/Instant, kotlinx.datetime/DateTimeUnit.TimeBased): kotlin/Long // kotlinx.datetime/until|until@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/yearsUntil(kotlin.time/Instant, kotlinx.datetime/TimeZone): kotlin/Int // kotlinx.datetime/yearsUntil|yearsUntil@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant.Companion).kotlinx.datetime/parse(kotlin/CharSequence, kotlinx.datetime.format/DateTimeFormat): kotlin.time/Instant // kotlinx.datetime/parse|parse@kotlin.time.Instant.Companion(kotlin.CharSequence;kotlinx.datetime.format.DateTimeFormat){}[0] +final fun (kotlin.time/TimeSource).kotlinx.datetime/asClock(kotlin.time/Instant): kotlin.time/Clock // kotlinx.datetime/asClock|asClock@kotlin.time.TimeSource(kotlin.time.Instant){}[0] final fun (kotlin/String).kotlinx.datetime/toDatePeriod(): kotlinx.datetime/DatePeriod // kotlinx.datetime/toDatePeriod|toDatePeriod@kotlin.String(){}[0] final fun (kotlin/String).kotlinx.datetime/toDateTimePeriod(): kotlinx.datetime/DateTimePeriod // kotlinx.datetime/toDateTimePeriod|toDateTimePeriod@kotlin.String(){}[0] final fun (kotlin/String).kotlinx.datetime/toInstant(): kotlinx.datetime/Instant // kotlinx.datetime/toInstant|toInstant@kotlin.String(){}[0] @@ -830,6 +868,7 @@ final fun (kotlinx.datetime.format/DateTimeFormat): kotlin/String // kotlinx.datetime/format|format@kotlinx.datetime.LocalDateTime(kotlinx.datetime.format.DateTimeFormat){}[0] final fun (kotlinx.datetime/LocalDateTime).kotlinx.datetime/toInstant(kotlinx.datetime/TimeZone): kotlinx.datetime/Instant // kotlinx.datetime/toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.datetime/LocalDateTime).kotlinx.datetime/toInstant(kotlinx.datetime/TimeZone, kotlinx.datetime/OverloadMarker = ...): kotlin.time/Instant // kotlinx.datetime/toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.TimeZone;kotlinx.datetime.OverloadMarker){}[0] final fun (kotlinx.datetime/LocalDateTime).kotlinx.datetime/toInstant(kotlinx.datetime/UtcOffset): kotlinx.datetime/Instant // kotlinx.datetime/toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.UtcOffset){}[0] +final fun (kotlinx.datetime/LocalDateTime).kotlinx.datetime/toInstant(kotlinx.datetime/UtcOffset, kotlinx.datetime/OverloadMarker = ...): kotlin.time/Instant // kotlinx.datetime/toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.UtcOffset;kotlinx.datetime.OverloadMarker){}[0] final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/atDate(kotlin/Int, kotlin/Int, kotlin/Int): kotlinx.datetime/LocalDateTime // kotlinx.datetime/atDate|atDate@kotlinx.datetime.LocalTime(kotlin.Int;kotlin.Int;kotlin.Int){}[0] final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/atDate(kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Unit = ...): kotlinx.datetime/LocalDateTime // kotlinx.datetime/atDate|atDate@kotlinx.datetime.LocalTime(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Unit){}[0] final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/atDate(kotlin/Int, kotlinx.datetime/Month, kotlin/Int): kotlinx.datetime/LocalDateTime // kotlinx.datetime/atDate|atDate@kotlinx.datetime.LocalTime(kotlin.Int;kotlinx.datetime.Month;kotlin.Int){}[0] final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/atDate(kotlin/Int, kotlinx.datetime/Month, kotlin/Int, kotlin/Unit = ...): kotlinx.datetime/LocalDateTime // kotlinx.datetime/atDate|atDate@kotlinx.datetime.LocalTime(kotlin.Int;kotlinx.datetime.Month;kotlin.Int;kotlin.Unit){}[0] final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/atDate(kotlinx.datetime/LocalDate): kotlinx.datetime/LocalDateTime // kotlinx.datetime/atDate|atDate@kotlinx.datetime.LocalTime(kotlinx.datetime.LocalDate){}[0] final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/format(kotlinx.datetime.format/DateTimeFormat): kotlin/String // kotlinx.datetime/format|format@kotlinx.datetime.LocalTime(kotlinx.datetime.format.DateTimeFormat){}[0] +final fun (kotlinx.datetime/TimeZone).kotlinx.datetime/offsetAt(kotlin.time/Instant): kotlinx.datetime/UtcOffset // kotlinx.datetime/offsetAt|offsetAt@kotlinx.datetime.TimeZone(kotlin.time.Instant){}[0] final fun (kotlinx.datetime/TimeZone).kotlinx.datetime/offsetAt(kotlinx.datetime/Instant): kotlinx.datetime/UtcOffset // kotlinx.datetime/offsetAt|offsetAt@kotlinx.datetime.TimeZone(kotlinx.datetime.Instant){}[0] final fun (kotlinx.datetime/UtcOffset).kotlinx.datetime/asTimeZone(): kotlinx.datetime/FixedOffsetTimeZone // kotlinx.datetime/asTimeZone|asTimeZone@kotlinx.datetime.UtcOffset(){}[0] final fun (kotlinx.datetime/UtcOffset).kotlinx.datetime/format(kotlinx.datetime.format/DateTimeFormat): kotlin/String // kotlinx.datetime/format|format@kotlinx.datetime.UtcOffset(kotlinx.datetime.format.DateTimeFormat){}[0] @@ -902,6 +947,9 @@ final fun kotlinx.datetime/Month(kotlin/Int): kotlinx.datetime/Month // kotlinx. final fun kotlinx.datetime/UtcOffset(): kotlinx.datetime/UtcOffset // kotlinx.datetime/UtcOffset|UtcOffset(){}[0] final fun kotlinx.datetime/UtcOffset(kotlin/Int? = ..., kotlin/Int? = ..., kotlin/Int? = ...): kotlinx.datetime/UtcOffset // kotlinx.datetime/UtcOffset|UtcOffset(kotlin.Int?;kotlin.Int?;kotlin.Int?){}[0] +// Targets: [apple] +final fun (kotlin.time/Instant).kotlinx.datetime/toNSDate(): platform.Foundation/NSDate // kotlinx.datetime/toNSDate|toNSDate@kotlin.time.Instant(){}[0] + // Targets: [apple] final fun (kotlinx.datetime/Instant).kotlinx.datetime/toNSDate(): platform.Foundation/NSDate // kotlinx.datetime/toNSDate|toNSDate@kotlinx.datetime.Instant(){}[0] @@ -917,6 +965,9 @@ final fun (kotlinx.datetime/TimeZone).kotlinx.datetime/toNSTimeZone(): platform. // Targets: [apple] final fun (platform.Foundation/NSDate).kotlinx.datetime/toKotlinInstant(): kotlinx.datetime/Instant // kotlinx.datetime/toKotlinInstant|toKotlinInstant@platform.Foundation.NSDate(){}[0] +// Targets: [apple] +final fun (platform.Foundation/NSDate).kotlinx.datetime/toKotlinInstant(kotlinx.datetime/OverloadMarker = ...): kotlin.time/Instant // kotlinx.datetime/toKotlinInstant|toKotlinInstant@platform.Foundation.NSDate(kotlinx.datetime.OverloadMarker){}[0] + // Targets: [apple] final fun (platform.Foundation/NSTimeZone).kotlinx.datetime/toKotlinTimeZone(): kotlinx.datetime/TimeZone // kotlinx.datetime/toKotlinTimeZone|toKotlinTimeZone@platform.Foundation.NSTimeZone(){}[0] diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 64697efd2..f2bf2e101 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -222,6 +222,10 @@ kotlin { } } } + + compilerOptions { + optIn.add("kotlin.time.ExperimentalTime") + } } tasks { diff --git a/core/common/src/Clock.kt b/core/common/src/Clock.kt index 8e7eb10b3..24cd7a737 100644 --- a/core/common/src/Clock.kt +++ b/core/common/src/Clock.kt @@ -3,62 +3,17 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ +@file:JvmMultifileClass +@file:JvmName("ClockKt") package kotlinx.datetime import kotlin.time.* - -/** - * A source of [Instant] values. - * - * See [Clock.System][Clock.System] for the clock instance that queries the operating system. - * - * It is not recommended to use [Clock.System] directly in the implementation. Instead, you can pass a - * [Clock] explicitly to the necessary functions or classes. - * This way, tests can be written deterministically by providing custom [Clock] implementations - * to the system under test. - */ -public interface Clock { - /** - * Returns the [Instant] corresponding to the current time, according to this clock. - * - * Calling [now] later is not guaranteed to return a larger [Instant]. - * In particular, for [Clock.System], the opposite is completely expected, - * and it must be taken into account. - * See the [System] documentation for details. - * - * Even though [Instant] is defined to be on the UTC-SLS time scale, which enforces a specific way of handling - * leap seconds, [now] is not guaranteed to handle leap seconds in any specific way. - */ - public fun now(): Instant - - /** - * The [Clock] instance that queries the platform-specific system clock as its source of time knowledge. - * - * Successive calls to [now] will not necessarily return increasing [Instant] values, and when they do, - * these increases will not necessarily correspond to the elapsed time. - * - * For example, when using [Clock.System], the following could happen: - * - [now] returns `2023-01-02T22:35:01Z`. - * - The system queries the Internet and recognizes that its clock needs adjusting. - * - [now] returns `2023-01-02T22:32:05Z`. - * - * When you need predictable intervals between successive measurements, consider using [TimeSource.Monotonic]. - * - * For improved testability, you should avoid using [Clock.System] directly in the implementation - * and pass a [Clock] explicitly instead. For example: - * - * @sample kotlinx.datetime.test.samples.ClockSamples.system - * @sample kotlinx.datetime.test.samples.ClockSamples.dependencyInjection - */ - public object System : Clock { - override fun now(): Instant = @Suppress("DEPRECATION_ERROR") Instant.now() - } - - /** A companion object used purely for namespacing. */ - public companion object { - - } -} +import kotlin.time.Clock +import kotlin.time.Instant +import kotlin.time.isDistantFuture +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName +import kotlin.time.Duration.Companion.seconds /** * Returns the current date at the given [time zone][timeZone], according to [this Clock][this]. @@ -108,7 +63,7 @@ private class InstantTimeMark(private val instant: Instant, private val clock: C override fun toString(): String = "InstantTimeMark($instant, $clock)" - private fun Instant.isSaturated() = this == Instant.MAX || this == Instant.MIN + private fun Instant.isSaturated() = this.plus(1.seconds) == this || this.plus((-1).seconds) == this private fun Instant.saturatingAdd(duration: Duration): Instant { if (isSaturated()) { if (duration.isInfinite() && duration.isPositive() != this.isDistantFuture) { diff --git a/core/common/src/DeprecatedClock.kt b/core/common/src/DeprecatedClock.kt new file mode 100644 index 000000000..34e2f67af --- /dev/null +++ b/core/common/src/DeprecatedClock.kt @@ -0,0 +1,164 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +@file:Suppress("DEPRECATION") +@file:JvmMultifileClass +@file:JvmName("ClockKt") +package kotlinx.datetime + +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName +import kotlin.time.ComparableTimeMark +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlin.time.ExperimentalTime +import kotlin.time.TimeSource + +/** + * Creates a [kotlin.time.Clock] (the standard library version of `Clock`) delegating to `this`. + */ +public fun Clock.toStdlibClock(): kotlin.time.Clock = + (this as? DateTimeClock)?.clock ?: StdlibClock(this) + +/** + * Creates a [kotlinx.datetime.Clock] delegating to the version of `Clock` from the standard library. + */ +public fun kotlin.time.Clock.toDeprecatedClock(): Clock = + (this as? StdlibClock)?.clock ?: DateTimeClock(this) + +private class DateTimeClock(val clock: kotlin.time.Clock): kotlinx.datetime.Clock { + override fun now(): Instant = clock.now().toDeprecatedInstant() +} + +private class StdlibClock(val clock: Clock): kotlin.time.Clock { + override fun now(): kotlin.time.Instant = clock.now().toStdlibInstant() +} + +/** + * A source of [Instant] values. + * + * See [Clock.System][Clock.System] for the clock instance that queries the operating system. + * + * It is not recommended to use [Clock.System] directly in the implementation. Instead, you can pass a + * [Clock] explicitly to the necessary functions or classes. + * This way, tests can be written deterministically by providing custom [Clock] implementations + * to the system under test. + */ +@Deprecated( + "Use kotlin.time.Clock instead", + ReplaceWith("kotlin.time.Clock", "kotlin.time.Clock"), + level = DeprecationLevel.WARNING +) +public interface Clock { + /** + * Returns the [Instant] corresponding to the current time, according to this clock. + * + * Calling [now] later is not guaranteed to return a larger [Instant]. + * In particular, for [Clock.System], the opposite is completely expected, + * and it must be taken into account. + * See the [System] documentation for details. + * + * Even though [Instant] is defined to be on the UTC-SLS time scale, which enforces a specific way of handling + * leap seconds, [now] is not guaranteed to handle leap seconds in any specific way. + */ + public fun now(): Instant + + /** + * The [Clock] instance that queries the platform-specific system clock as its source of time knowledge. + * + * Successive calls to [now] will not necessarily return increasing [Instant] values, and when they do, + * these increases will not necessarily correspond to the elapsed time. + * + * For example, when using [Clock.System], the following could happen: + * - [now] returns `2023-01-02T22:35:01Z`. + * - The system queries the Internet and recognizes that its clock needs adjusting. + * - [now] returns `2023-01-02T22:32:05Z`. + * + * When you need predictable intervals between successive measurements, consider using [TimeSource.Monotonic]. + * + * For improved testability, you should avoid using [Clock.System] directly in the implementation + * and pass a [Clock] explicitly instead. For example: + * + * @sample kotlinx.datetime.test.samples.ClockSamples.system + * @sample kotlinx.datetime.test.samples.ClockSamples.dependencyInjection + */ + public object System : Clock { + override fun now(): Instant = kotlin.time.Clock.System.now().toDeprecatedInstant() + } + + /** A companion object used purely for namespacing. */ + public companion object { + + } +} + +/** + * Returns the current date at the given [time zone][timeZone], according to [this Clock][this]. + * + * The time zone is important because the current date is not the same in all time zones at the same instant. + * + * @sample kotlinx.datetime.test.samples.ClockSamples.todayIn + */ +public fun Clock.todayIn(timeZone: TimeZone): LocalDate = + now().toStdlibInstant().toLocalDateTime(timeZone).date + +/** + * Returns a [TimeSource] that uses this [Clock] to mark a time instant and to find the amount of time elapsed since that mark. + * + * **Pitfall**: using this function with [Clock.System] is error-prone + * because [Clock.System] is not well suited for measuring time intervals. + * Please only use this conversion function on the [Clock] instances that are fully controlled programmatically. + */ +@ExperimentalTime +public fun Clock.asTimeSource(): TimeSource.WithComparableMarks = object : TimeSource.WithComparableMarks { + override fun markNow(): ComparableTimeMark = DeprecatedInstantTimeMark(now(), this@asTimeSource) +} + +@ExperimentalTime +private class DeprecatedInstantTimeMark(private val instant: Instant, private val clock: Clock) : ComparableTimeMark { + override fun elapsedNow(): Duration = saturatingDiff(clock.now(), instant) + + override fun plus(duration: Duration): ComparableTimeMark = + DeprecatedInstantTimeMark(instant.saturatingAdd(duration), clock) + override fun minus(duration: Duration): ComparableTimeMark = + DeprecatedInstantTimeMark(instant.saturatingAdd(-duration), clock) + + override fun minus(other: ComparableTimeMark): Duration { + if (other !is DeprecatedInstantTimeMark || other.clock != this.clock) { + throw IllegalArgumentException("Subtracting or comparing time marks from different time sources is not possible: $this and $other") + } + return saturatingDiff(this.instant, other.instant) + } + + override fun equals(other: Any?): Boolean { + return other is DeprecatedInstantTimeMark && this.clock == other.clock && this.instant == other.instant + } + + override fun hashCode(): Int = instant.hashCode() + + override fun toString(): String = "InstantTimeMark($instant, $clock)" + + private fun Instant.isSaturated() = this.plus(1.seconds) == this || this.plus((-1).seconds) == this + private fun Instant.saturatingAdd(duration: Duration): Instant { + if (isSaturated()) { + if (duration.isInfinite() && duration.isPositive() != this.isDistantFuture) { + throw IllegalArgumentException("Summing infinities of different signs") + } + return this + } + return this + duration + } + private fun saturatingDiff(instant1: Instant, instant2: Instant): Duration = when { + instant1 == instant2 -> + Duration.ZERO + instant1.isSaturated() || instant2.isSaturated() -> + (instant1 - instant2) * Double.POSITIVE_INFINITY + else -> + instant1 - instant2 + } +} + +@Deprecated("Use Clock.todayIn instead", ReplaceWith("this.todayIn(timeZone)"), DeprecationLevel.WARNING) +public fun Clock.todayAt(timeZone: TimeZone): LocalDate = todayIn(timeZone) diff --git a/core/common/src/DeprecatedInstant.kt b/core/common/src/DeprecatedInstant.kt new file mode 100644 index 000000000..272952449 --- /dev/null +++ b/core/common/src/DeprecatedInstant.kt @@ -0,0 +1,801 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +@file:Suppress("DEPRECATION") +@file:JvmMultifileClass +@file:JvmName("InstantKt") +package kotlinx.datetime + +import kotlinx.datetime.format.DateTimeComponents +import kotlinx.datetime.format.DateTimeFormat +import kotlinx.datetime.format.format +import kotlinx.datetime.internal.NANOS_PER_ONE +import kotlinx.datetime.internal.clampToInt +import kotlinx.datetime.internal.multiplyAddAndDivide +import kotlinx.datetime.serializers.InstantIso8601Serializer +import kotlinx.serialization.Serializable +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName +import kotlin.time.Duration +import kotlin.time.TimeSource + +/** + * Creates a [kotlin.time.Instant] (the standard library version of `Instant`) identical to `this`. + */ +public fun Instant.toStdlibInstant(): kotlin.time.Instant = + kotlin.time.Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) + +/** + * Creates a [kotlinx.datetime.Instant] identical to the version of `Instant` from the standard library. + */ +public fun kotlin.time.Instant.toDeprecatedInstant(): Instant = + Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) + +@Deprecated( + "Use kotlin.time.Instant instead", + ReplaceWith("kotlin.time.Instant", "kotlin.time.Instant"), + level = DeprecationLevel.WARNING +) +@Serializable(with = InstantIso8601Serializer::class) +public expect class Instant : Comparable { + + /** + * The number of seconds from the epoch instant `1970-01-01T00:00:00Z` rounded down to a [Long] number. + * + * The difference between the rounded number of seconds and the actual number of seconds + * is returned by [nanosecondsOfSecond] property expressed in nanoseconds. + * + * Note that this number doesn't include leap seconds added or removed since the epoch. + * + * @see fromEpochSeconds + * @sample kotlinx.datetime.test.samples.InstantSamples.epochSeconds + */ + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().epochSeconds") + ) + public val epochSeconds: Long + + /** + * The number of nanoseconds by which this instant is later than [epochSeconds] from the epoch instant. + * + * The value is always non-negative and lies in the range `0..999_999_999`. + * + * @see fromEpochSeconds + * @sample kotlinx.datetime.test.samples.InstantSamples.nanosecondsOfSecond + */ + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().nanosecondsOfSecond") + ) + public val nanosecondsOfSecond: Int + + /** + * Returns the number of milliseconds from the epoch instant `1970-01-01T00:00:00Z`. + * + * Any fractional part of a millisecond is rounded toward zero to the whole number of milliseconds. + * + * If the result does not fit in [Long], + * returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. + * + * @see fromEpochMilliseconds + * @sample kotlinx.datetime.test.samples.InstantSamples.toEpochMilliseconds + */ + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().nanosecondsOfSecond") + ) + public fun toEpochMilliseconds(): Long + + /** + * Returns an instant that is the result of adding the specified [duration] to this instant. + * + * If the [duration] is positive, the returned instant is later than this instant. + * If the [duration] is negative, the returned instant is earlier than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * **Pitfall**: [Duration.Companion.days] are multiples of 24 hours and are not calendar-based. + * Consider using the [plus] overload that accepts a multiple of a [DateTimeUnit] instead for calendar-based + * operations instead of using [Duration]. + * For an explanation of why some days are not 24 hours, see [DateTimeUnit.DayBased]. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.plusDuration + */ + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("(this.toStdlibInstant() + duration).toDeprecatedInstant()") + ) + public operator fun plus(duration: Duration): Instant + + /** + * Returns an instant that is the result of subtracting the specified [duration] from this instant. + * + * If the [duration] is positive, the returned instant is earlier than this instant. + * If the [duration] is negative, the returned instant is later than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * **Pitfall**: [Duration.Companion.days] are multiples of 24 hours and are not calendar-based. + * Consider using the [minus] overload that accepts a multiple of a [DateTimeUnit] instead for calendar-based + * operations instead of using [Duration]. + * For an explanation of why some days are not 24 hours, see [DateTimeUnit.DayBased]. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.minusDuration + */ + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("(this.toStdlibInstant() - duration).toDeprecatedInstant()") + ) + public operator fun minus(duration: Duration): Instant + + // questionable + /** + * Returns the [Duration] between two instants: [other] and `this`. + * + * The duration returned is positive if this instant is later than the other, + * and negative if this instant is earlier than the other. + * + * The result is never clamped, but note that for instants that are far apart, + * the value returned may represent the duration between them inexactly due to the loss of precision. + * + * Note that sources of [Instant] values (in particular, [Clock]) are not guaranteed to be in sync with each other + * or even monotonic, so the result of this operation may be negative even if the other instant was observed later + * than this one, or vice versa. + * For measuring time intervals, consider using [TimeSource.Monotonic]. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.minusInstant + */ + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant() - other.toStdlibInstant()") + ) + public operator fun minus(other: Instant): Duration + + /** + * Compares `this` instant with the [other] instant. + * Returns zero if this instant represents the same moment as the other (meaning they are equal to one another), + * a negative number if this instant is earlier than the other, + * and a positive number if this instant is later than the other. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.compareToSample + */ + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().compareTo(other.toStdlibInstant())") + ) + public override operator fun compareTo(other: Instant): Int + + /** + * Converts this instant to the ISO 8601 string representation, for example, `2023-01-02T23:40:57.120Z`. + * + * The representation uses the UTC-SLS time scale instead of UTC. + * In practice, this means that leap second handling will not be readjusted to the UTC. + * Leap seconds will not be added or skipped, so it is impossible to acquire a string + * where the component for seconds is 60, and for any day, it's possible to observe 23:59:59. + * + * @see parse + * @see DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET for a very similar format. The difference is that + * [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] will not add trailing zeros for readability to the + * fractional part of the second. + * @sample kotlinx.datetime.test.samples.InstantSamples.toStringSample + */ + @Suppress("POTENTIALLY_NON_REPORTED_ANNOTATION") + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().toString()") + ) + public override fun toString(): String + + + public companion object { + @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlin.time.Clock"), level = DeprecationLevel.ERROR) + public fun now(): Instant + + /** + * Returns an [Instant] that is [epochMilliseconds] number of milliseconds from the epoch instant `1970-01-01T00:00:00Z`. + * + * Every value of [epochMilliseconds] is guaranteed to be representable as an [Instant]. + * + * Note that [Instant] also supports nanosecond precision via [fromEpochSeconds]. + * + * @see Instant.toEpochMilliseconds + * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochMilliseconds + */ + public fun fromEpochMilliseconds(epochMilliseconds: Long): Instant + + /** + * Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z` + * and the [nanosecondAdjustment] number of nanoseconds from the whole second. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * In any case, it is guaranteed that instants between [DISTANT_PAST] and [DISTANT_FUTURE] can be represented. + * + * [fromEpochMilliseconds] is a similar function for when input data only has millisecond precision. + * + * @see Instant.epochSeconds + * @see Instant.nanosecondsOfSecond + * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochSeconds + */ + public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long = 0): Instant + + /** + * Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z` + * and the [nanosecondAdjustment] number of nanoseconds from the whole second. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * In any case, it is guaranteed that instants between [DISTANT_PAST] and [DISTANT_FUTURE] can be represented. + * + * [fromEpochMilliseconds] is a similar function for when input data only has millisecond precision. + * + * @see Instant.epochSeconds + * @see Instant.nanosecondsOfSecond + * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochSecondsIntNanos + */ + public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant + + /** + * A shortcut for calling [DateTimeFormat.parse], followed by [DateTimeComponents.toInstantUsingOffset]. + * + * Parses a string that represents an instant, including date and time components and a mandatory + * time zone offset and returns the parsed [Instant] value. + * + * The string is considered to represent time on the UTC-SLS time scale instead of UTC. + * In practice, this means that, even if there is a leap second on the given day, it will not affect how the + * time is parsed, even if it's in the last 1000 seconds of the day. + * Instead, even if there is a negative leap second on the given day, 23:59:59 is still considered a valid time. + * 23:59:60 is invalid on UTC-SLS, so parsing it will fail. + * + * If the format is not specified, [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] is used. + * `2023-01-02T23:40:57.120Z` is an example of a string in this format. + * + * @throws IllegalArgumentException if the text cannot be parsed or the boundaries of [Instant] are exceeded. + * + * @see Instant.toString for formatting using the default format. + * @see Instant.format for formatting using a custom format. + * @sample kotlinx.datetime.test.samples.InstantSamples.parsing + */ + public fun parse( + input: CharSequence, + format: DateTimeFormat = DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET + ): Instant + + /** + * An instant value that is far in the past. + * + * All instants in the range `DISTANT_PAST..DISTANT_FUTURE` can be [converted][Instant.toLocalDateTime] to + * [LocalDateTime] without exceptions in every time zone. + * + * [isDistantPast] returns true for this value and all earlier ones. + */ + public val DISTANT_PAST: Instant // -100001-12-31T23:59:59.999999999Z + + /** + * An instant value that is far in the future. + * + * All instants in the range `DISTANT_PAST..DISTANT_FUTURE` can be [converted][Instant.toLocalDateTime] to + * [LocalDateTime] without exceptions in every time zone. + * + * [isDistantFuture] returns true for this value and all later ones. + */ + public val DISTANT_FUTURE: Instant // +100000-01-01T00:00:00Z + + internal val MIN: Instant + internal val MAX: Instant + } +} + +/** + * Returns true if the instant is [Instant.DISTANT_PAST] or earlier. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.isDistantPast + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().isDistantPast", "kotlin.time.isDistantPast") +) +public val Instant.isDistantPast: Boolean + get() = this <= Instant.DISTANT_PAST + +/** + * Returns true if the instant is [Instant.DISTANT_FUTURE] or later. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.isDistantFuture + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().isDistantPast", "kotlin.time.isDistantFuture") +) +public val Instant.isDistantFuture: Boolean + get() = this >= Instant.DISTANT_FUTURE + +/** + * @suppress + */ +@Deprecated("Removed to support more idiomatic code. See https://github.com/Kotlin/kotlinx-datetime/issues/339", ReplaceWith("Instant.parse(this)"), DeprecationLevel.WARNING) +public fun String.toInstant(): Instant = Instant.parse(this) + +/** + * Returns an instant that is the result of adding components of [DateTimePeriod] to this instant. The components are + * added in the order from the largest units to the smallest, i.e., from years to nanoseconds. + * + * - If the [DateTimePeriod] only contains time-based components, please consider adding a [Duration] instead, + * as in `Clock.System.now() + 5.hours`. + * Then, it will not be necessary to pass the [timeZone]. + * - If the [DateTimePeriod] only has a single non-zero component (only the months or only the days), + * please consider using a multiple of [DateTimeUnit.DAY] or [DateTimeUnit.MONTH], like in + * `Clock.System.now().plus(5, DateTimeUnit.DAY, TimeZone.currentSystemDefault())`. + * + * @throws DateTimeArithmeticException if this value or the results of intermediate computations are too large to fit in + * [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.plusPeriod + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(period, timeZone).toDeprecatedInstant()") +) +public expect fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant + +/** + * Returns an instant that is the result of subtracting components of [DateTimePeriod] from this instant. The components + * are subtracted in the order from the largest units to the smallest, i.e., from years to nanoseconds. + * + * - If the [DateTimePeriod] only contains time-based components, please consider subtracting a [Duration] instead, + * as in `Clock.System.now() - 5.hours`. + * Then, it is not necessary to pass the [timeZone]. + * - If the [DateTimePeriod] only has a single non-zero component (only the months or only the days), + * please consider using a multiple of [DateTimeUnit.DAY] or [DateTimeUnit.MONTH], as in + * `Clock.System.now().minus(5, DateTimeUnit.DAY, TimeZone.currentSystemDefault())`. + * + * @throws DateTimeArithmeticException if this value or the results of intermediate computations are too large to fit in + * [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.minusPeriod + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(period, timeZone).toDeprecatedInstant()") +) +public fun Instant.minus(period: DateTimePeriod, timeZone: TimeZone): Instant = + /* An overflow can happen for any component, but we are only worried about nanoseconds, as having an overflow in + any other component means that `plus` will throw due to the minimum value of the numeric type overflowing the + `Instant` limits. */ + if (period.totalNanoseconds != Long.MIN_VALUE) { + val negatedPeriod = with(period) { buildDateTimePeriod(-totalMonths, -days, -totalNanoseconds) } + plus(negatedPeriod, timeZone) + } else { + val negatedPeriod = with(period) { buildDateTimePeriod(-totalMonths, -days, -(totalNanoseconds+1)) } + plus(negatedPeriod, timeZone).plus(1, DateTimeUnit.NANOSECOND) + } + +/** + * Returns a [DateTimePeriod] representing the difference between `this` and [other] instants. + * + * The components of [DateTimePeriod] are calculated so that adding it to `this` instant results in the [other] instant. + * + * All components of the [DateTimePeriod] returned are: + * - Positive or zero if this instant is earlier than the other. + * - Negative or zero if this instant is later than the other. + * - Exactly zero if this instant is equal to the other. + * + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.periodUntil + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().periodUntil(other.toStdlibInstant(), timeZone)") +) +public expect fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod + +/** + * Returns the whole number of the specified date or time [units][unit] between `this` and [other] instants + * in the specified [timeZone]. + * + * The value returned is: + * - Positive or zero if this instant is earlier than the other. + * - Negative or zero if this instant is later than the other. + * - Zero if this instant is equal to the other. + * + * If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. + * + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.untilAsDateTimeUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().until(other.toStdlibInstant(), unit, timeZone)") +) +public expect fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long + +/** + * Returns the whole number of the specified time [units][unit] between `this` and [other] instants. + * + * The value returned is: + * - Positive or zero if this instant is earlier than the other. + * - Negative or zero if this instant is later than the other. + * - Zero if this instant is equal to the other. + * + * If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.untilAsTimeBasedUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().until(other.toStdlibInstant(), unit)") +) +public fun Instant.until(other: Instant, unit: DateTimeUnit.TimeBased): Long = + try { + multiplyAddAndDivide(other.epochSeconds - epochSeconds, + NANOS_PER_ONE.toLong(), + (other.nanosecondsOfSecond - nanosecondsOfSecond).toLong(), + unit.nanoseconds) + } catch (_: ArithmeticException) { + if (this < other) Long.MAX_VALUE else Long.MIN_VALUE + } + +/** + * Returns the number of whole days between two instants in the specified [timeZone]. + * + * If the result does not fit in [Int], returns [Int.MAX_VALUE] for a positive result or [Int.MIN_VALUE] for a negative result. + * + * @see Instant.until + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.daysUntil + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().daysUntil(other.toStdlibInstant(), timeZone)") +) +public fun Instant.daysUntil(other: Instant, timeZone: TimeZone): Int = + until(other, DateTimeUnit.DAY, timeZone).clampToInt() + +/** + * Returns the number of whole months between two instants in the specified [timeZone]. + * + * If the result does not fit in [Int], returns [Int.MAX_VALUE] for a positive result or [Int.MIN_VALUE] for a negative result. + * + * @see Instant.until + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.monthsUntil + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().monthsUntil(other.toStdlibInstant(), timeZone)") +) +public fun Instant.monthsUntil(other: Instant, timeZone: TimeZone): Int = + until(other, DateTimeUnit.MONTH, timeZone).clampToInt() + +/** + * Returns the number of whole years between two instants in the specified [timeZone]. + * + * If the result does not fit in [Int], returns [Int.MAX_VALUE] for a positive result or [Int.MIN_VALUE] for a negative result. + * + * @see Instant.until + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.yearsUntil + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().yearsUntil(other.toStdlibInstant(), timeZone)") +) +public fun Instant.yearsUntil(other: Instant, timeZone: TimeZone): Int = + until(other, DateTimeUnit.YEAR, timeZone).clampToInt() + +/** + * Returns a [DateTimePeriod] representing the difference between [other] and `this` instants. + * + * The components of [DateTimePeriod] are calculated so that adding it back to the `other` instant results in this instant. + * + * All components of the [DateTimePeriod] returned are: + * - Negative or zero if this instant is earlier than the other. + * - Positive or zero if this instant is later than the other. + * - Exactly zero if this instant is equal to the other. + * + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @see Instant.periodUntil + * @sample kotlinx.datetime.test.samples.InstantSamples.minusInstantInZone + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(other.toStdlibInstant(), timeZone)") +) +public fun Instant.minus(other: Instant, timeZone: TimeZone): DateTimePeriod = + other.periodUntil(this, timeZone) + + +/** + * Returns an instant that is the result of adding one [unit] to this instant + * in the specified [timeZone]. + * + * The returned instant is later than this instant. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(1, unit, timeZone).toDeprecatedInstant()") +) +public expect fun Instant.plus(unit: DateTimeUnit, timeZone: TimeZone): Instant + +/** + * Returns an instant that is the result of subtracting one [unit] from this instant + * in the specified [timeZone]. + * + * The returned instant is earlier than this instant. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(1, unit, timeZone).toDeprecatedInstant()") +) +public fun Instant.minus(unit: DateTimeUnit, timeZone: TimeZone): Instant = + plus(-1, unit, timeZone) + +/** + * Returns an instant that is the result of adding one [unit] to this instant. + * + * The returned instant is later than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(1, unit).toDeprecatedInstant()") +) +public fun Instant.plus(unit: DateTimeUnit.TimeBased): Instant = + plus(1L, unit) + +/** + * Returns an instant that is the result of subtracting one [unit] from this instant. + * + * The returned instant is earlier than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(1, unit).toDeprecatedInstant()") +) +public fun Instant.minus(unit: DateTimeUnit.TimeBased): Instant = + plus(-1L, unit) + +/** + * Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant + * in the specified [timeZone]. + * + * If the [value] is positive, the returned instant is later than this instant. + * If the [value] is negative, the returned instant is earlier than this instant. + * + * Note that the time zone does not need to be passed when the [unit] is a time-based unit. + * It is also not needed when adding date-based units to a [LocalDate][LocalDate.plus]. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.plusDateTimeUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit, timeZone).toDeprecatedInstant()") +) +public expect fun Instant.plus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant + +/** + * Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant + * in the specified [timeZone]. + * + * If the [value] is positive, the returned instant is earlier than this instant. + * If the [value] is negative, the returned instant is later than this instant. + * + * Note that the time zone does not need to be passed when the [unit] is a time-based unit. + * It is also not needed when subtracting date-based units from a [LocalDate]. + * + * If the [value] is positive, the returned instant is earlier than this instant. + * If the [value] is negative, the returned instant is later than this instant. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.minusDateTimeUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(value, unit, timeZone).toDeprecatedInstant()") +) +public expect fun Instant.minus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant + +/** + * Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant. + * + * If the [value] is positive, the returned instant is later than this instant. + * If the [value] is negative, the returned instant is earlier than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.plusTimeBasedUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit).toDeprecatedInstant()") +) +public fun Instant.plus(value: Int, unit: DateTimeUnit.TimeBased): Instant = + plus(value.toLong(), unit) + +/** + * Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant. + * + * If the [value] is positive, the returned instant is earlier than this instant. + * If the [value] is negative, the returned instant is later than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.minusTimeBasedUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(value, unit).toDeprecatedInstant()") +) +public fun Instant.minus(value: Int, unit: DateTimeUnit.TimeBased): Instant = + minus(value.toLong(), unit) + +/** + * Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant + * in the specified [timeZone]. + * + * If the [value] is positive, the returned instant is later than this instant. + * If the [value] is negative, the returned instant is earlier than this instant. + * + * Note that the time zone does not need to be passed when the [unit] is a time-based unit. + * It is also not needed when adding date-based units to a [LocalDate]. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.plusDateTimeUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit, timeZone).toDeprecatedInstant()") +) +public expect fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant + +/** + * Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant + * in the specified [timeZone]. + * + * If the [value] is positive, the returned instant is earlier than this instant. + * If the [value] is negative, the returned instant is later than this instant. + * + * Note that the time zone does not need to be passed when the [unit] is a time-based unit. + * It is also not needed when subtracting date-based units from a [LocalDate]. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.minusDateTimeUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(value, unit, timeZone).toDeprecatedInstant()") +) +public fun Instant.minus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant = + if (value != Long.MIN_VALUE) { + plus(-value, unit, timeZone) + } else { + plus(-(value + 1), unit, timeZone).plus(1, unit, timeZone) + } + +/** + * Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant. + * + * If the [value] is positive, the returned instant is later than this instant. + * If the [value] is negative, the returned instant is earlier than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.plusTimeBasedUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit).toDeprecatedInstant()") +) +public expect fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Instant + +/** + * Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant. + * + * If the [value] is positive, the returned instant is earlier than this instant. + * If the [value] is negative, the returned instant is later than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.minusTimeBasedUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(value, unit).toDeprecatedInstant()") +) +public fun Instant.minus(value: Long, unit: DateTimeUnit.TimeBased): Instant = + if (value != Long.MIN_VALUE) { + plus(-value, unit) + } else { + plus(-(value + 1), unit).plus(1, unit) + } + +/** + * Returns the whole number of the specified date or time [units][unit] between [other] and `this` instants + * in the specified [timeZone]. + * + * The value returned is negative or zero if this instant is earlier than the other, + * and positive or zero if this instant is later than the other. + * + * If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. + * + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @see Instant.until for the same operation but with swapped arguments. + * @sample kotlinx.datetime.test.samples.InstantSamples.minusAsDateTimeUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(other.toStdlibInstant(), unit, timeZone)") +) +public fun Instant.minus(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long = + other.until(this, unit, timeZone) + +/** + * Returns the whole number of the specified time [units][unit] between [other] and `this` instants. + * + * The value returned is negative or zero if this instant is earlier than the other, + * and positive or zero if this instant is later than the other. + * + * If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. + * + * @see Instant.until for the same operation but with swapped arguments. + * @sample kotlinx.datetime.test.samples.InstantSamples.minusAsTimeBasedUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(other.toStdlibInstant(), unit)") +) +public fun Instant.minus(other: Instant, unit: DateTimeUnit.TimeBased): Long = + other.until(this, unit) + +/** + * Formats this value using the given [format] using the given [offset]. + * + * Equivalent to calling [DateTimeFormat.format] on [format] and using [DateTimeComponents.setDateTimeOffset] in + * the lambda. + * + * [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] is a format very similar to the one used by [toString]. + * The only difference is that [Instant.toString] adds trailing zeros to the fraction-of-second component so that the + * number of digits after a dot is a multiple of three. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.formatting + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().format(format, offset)") +) +public fun Instant.format(format: DateTimeFormat, offset: UtcOffset = UtcOffset.ZERO): String { + val instant = this + return format.format { setDateTimeOffset(instant.toStdlibInstant(), offset) } +} + +/** + * An instance of this class can not be obtained, and it should not be used. + * + * The purpose of this class is to allow defining functions that return `kotlin.time.Instant`, + * but still keep the functions returning `kotlinx.datetime.Instant` for binary compatibility. + * Kotlin does not allow two functions with the same name and parameter types but different return types, + * so we need to use a trick to achieve this. + * By introducing a fictional parameter of this type, we can pretend that the function has a different signature, + * even though it can only be called exactly the same way as the function without this parameter used to be. + * There is no ambiguity, as the old functions are deprecated and hidden and can not actually be called. + * + * @suppress this class is not meant to be used, so the documentation is only here for the curious reader. + */ +@Deprecated( + "It is meaningless to try to pass an OverloadMarker to a function directly. " + + "All functions accepting it have its instance as a default value.", + level = DeprecationLevel.ERROR +) +public class OverloadMarker private constructor() { + internal companion object { + @Suppress("DEPRECATION_ERROR") + internal val INSTANCE = OverloadMarker() + } +} diff --git a/core/common/src/Instant.kt b/core/common/src/Instant.kt index 3deedd946..f55211d04 100644 --- a/core/common/src/Instant.kt +++ b/core/common/src/Instant.kt @@ -3,435 +3,49 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ +@file:JvmMultifileClass +@file:JvmName("InstantKt") package kotlinx.datetime import kotlinx.datetime.format.* import kotlinx.datetime.internal.* -import kotlinx.datetime.serializers.InstantIso8601Serializer -import kotlinx.datetime.serializers.InstantComponentSerializer -import kotlinx.serialization.Serializable import kotlin.time.* +import kotlin.time.Instant +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName /** - * A moment in time. + * A shortcut for calling [DateTimeFormat.parse], followed by [DateTimeComponents.toInstantUsingOffset]. * - * A point in time must be uniquely identified in a way that is independent of a time zone. - * For example, `1970-01-01, 00:00:00` does not represent a moment in time since this would happen at different times - * in different time zones: someone in Tokyo would think it is already `1970-01-01` several hours earlier than someone in - * Berlin would. To represent such entities, use [LocalDateTime]. - * In contrast, "the moment the clocks in London first showed 00:00 on Jan 1, 2000" is a specific moment - * in time, as is "1970-01-01, 00:00:00 UTC+0", so it can be represented as an [Instant]. + * Parses a string that represents an instant, including date and time components and a mandatory + * time zone offset and returns the parsed [Instant] value. * - * `Instant` uses the UTC-SLS (smeared leap second) time scale. This time scale doesn't contain instants - * corresponding to leap seconds, but instead "smears" positive and negative leap seconds among the last 1000 seconds - * of the day when a leap second happens. + * The string is considered to represent time on the UTC-SLS time scale instead of UTC. + * In practice, this means that, even if there is a leap second on the given day, it will not affect how the + * time is parsed, even if it's in the last 1000 seconds of the day. + * Instead, even if there is a negative leap second on the given day, 23:59:59 is still considered a valid time. + * 23:59:60 is invalid on UTC-SLS, so parsing it will fail. * - * ### Obtaining the current moment + * [Instant.parse] is equivalent to calling this function with the + * [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] format. + * `2023-01-02T23:40:57.120Z` is an example of a string in this format. * - * The [Clock] interface is the primary way to obtain the current moment: + * @throws IllegalArgumentException if the text cannot be parsed or the boundaries of [Instant] are exceeded. * - * ``` - * val clock: Clock = Clock.System - * val instant = clock.now() - * ``` - * - * The [Clock.System] implementation uses the platform-specific system clock to obtain the current moment. - * Note that this clock is not guaranteed to be monotonic, and the user or the system may adjust it at any time, - * so it should not be used for measuring time intervals. - * For that, consider using [TimeSource.Monotonic] and [TimeMark] instead of [Clock.System] and [Instant]. - * - * ### Obtaining human-readable representations - * - * #### Date and time - * - * [Instant] is essentially the number of seconds and nanoseconds since a designated moment in time, - * stored as something like `1709898983.123456789`. - * [Instant] does not contain information about the day or time, as this depends on the time zone. - * To work with this information for a specific time zone, obtain a [LocalDateTime] using [Instant.toLocalDateTime]: - * - * ``` - * val instant = Instant.fromEpochSeconds(1709898983, 123456789) - * instant.toLocalDateTime(TimeZone.of("Europe/Berlin")) // 2024-03-08T12:56:23.123456789 - * instant.toLocalDateTime(TimeZone.UTC) // 2024-03-08T11:56:23.123456789 - * ``` - * - * For values very far in the past or the future, this conversion may fail. - * The specific range of values that can be converted to [LocalDateTime] is unspecified, but at least - * [DISTANT_PAST], [DISTANT_FUTURE], and all values between them are included in that range. - * - * #### Date or time separately - * - * To obtain a [LocalDate] or [LocalTime], first, obtain a [LocalDateTime], and then use its [LocalDateTime.date] - * and [LocalDateTime.time] properties: - * - * ``` - * val instant = Instant.fromEpochSeconds(1709898983, 123456789) - * instant.toLocalDateTime(TimeZone.of("Europe/Berlin")).date // 2024-03-08 - * ``` - * - * ### Arithmetic operations - * - * #### Elapsed-time-based - * - * The [plus] and [minus] operators can be used to add [Duration]s to and subtract them from an [Instant]: - * - * ``` - * Clock.System.now() + 5.seconds // 5 seconds from now - * ``` - * - * Durations can also be represented as multiples of some [time-based datetime unit][DateTimeUnit.TimeBased]: - * - * ``` - * Clock.System.now().plus(4, DateTimeUnit.HOUR) // 4 hours from now - * ``` - * - * Also, there is a [minus] operator that returns the [Duration] representing the difference between two instants: - * - * ``` - * val start = Clock.System.now() - * val concertStart = LocalDateTime(2023, 1, 1, 20, 0, 0).toInstant(TimeZone.of("Europe/Berlin")) - * val timeUntilConcert = concertStart - start - * ``` - * - * #### Calendar-based - * - * Since [Instant] represents a point in time, it is always well-defined what the result of arithmetic operations on it - * is, including the cases when a calendar is used. - * This is not the case for [LocalDateTime], where the result of arithmetic operations depends on the time zone. - * See the [LocalDateTime] documentation for more details. - * - * Adding and subtracting calendar-based units can be done using the [plus] and [minus] operators, - * requiring a [TimeZone]: - * - * ``` - * // One day from now in Berlin - * Clock.System.now().plus(1, DateTimeUnit.DAY, TimeZone.of("Europe/Berlin")) - * - * // A day and two hours short from two months later in Berlin - * Clock.System.now().plus(DateTimePeriod(months = 2, days = -1, hours = -2), TimeZone.of("Europe/Berlin")) - * ``` - * - * The difference between [Instant] values in terms of calendar-based units can be obtained using the [periodUntil] - * method: - * - * ``` - * val start = Clock.System.now() - * val concertStart = LocalDateTime(2023, 1, 1, 20, 0, 0).toInstant(TimeZone.of("Europe/Berlin")) - * val timeUntilConcert = start.periodUntil(concertStart, TimeZone.of("Europe/Berlin")) - * // Two months, three days, four hours, and five minutes until the concert - * ``` - * - * Or the [Instant.until] method, as well as [Instant.daysUntil], [Instant.monthsUntil], - * and [Instant.yearsUntil] extension functions: - * - * ``` - * val start = Clock.System.now() - * val concertStart = LocalDateTime(2023, 1, 1, 20, 0, 0).toInstant(TimeZone.of("Europe/Berlin")) - * val timeUntilConcert = start.until(concertStart, DateTimeUnit.DAY, TimeZone.of("Europe/Berlin")) - * // 63 days until the concert, rounded down - * ``` - * - * ### Platform specifics - * - * On the JVM, there are `Instant.toJavaInstant()` and `java.time.Instant.toKotlinInstant()` - * extension functions to convert between `kotlinx.datetime` and `java.time` objects used for the same purpose. - * Similarly, on the Darwin platforms, there are `Instant.toNSDate()` and `NSDate.toKotlinInstant()` - * extension functions. - * - * ### Construction, serialization, and deserialization - * - * [fromEpochSeconds] can be used to construct an instant from the number of seconds since - * `1970-01-01T00:00:00Z` (the Unix epoch). - * [epochSeconds] and [nanosecondsOfSecond] can be used to obtain the number of seconds and nanoseconds since the epoch. - * - * ``` - * val instant = Instant.fromEpochSeconds(1709898983, 123456789) - * instant.epochSeconds // 1709898983 - * instant.nanosecondsOfSecond // 123456789 - * ``` - * - * [fromEpochMilliseconds] allows constructing an instant from the number of milliseconds since the epoch. - * [toEpochMilliseconds] can be used to obtain the number of milliseconds since the epoch. - * Note that [Instant] supports nanosecond precision, so converting to milliseconds is a lossy operation. - * - * ``` - * val instant1 = Instant.fromEpochSeconds(1709898983, 123456789) - * instant1.nanosecondsOfSecond // 123456789 - * val milliseconds = instant1.toEpochMilliseconds() // 1709898983123 - * val instant2 = Instant.fromEpochMilliseconds(milliseconds) - * instant2.nanosecondsOfSecond // 123000000 - * ``` - * - * [parse] and [toString] methods can be used to obtain an [Instant] from and convert it to a string in the - * ISO 8601 extended format. - * - * ``` - * val instant = Instant.parse("2023-01-02T22:35:01+01:00") - * instant.toString() // 2023-01-02T21:35:01Z - * ``` - * - * During parsing, the UTC offset is not returned separately. - * If the UTC offset is important, use [DateTimeComponents] with [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] to - * parse the string instead. - * - * [Instant.parse] and [Instant.format] also accept custom formats: - * - * ``` - * val customFormat = DateTimeComponents.Format { - * date(LocalDate.Formats.ISO) - * char(' ') - * time(LocalTime.Formats.ISO) - * char(' ') - * offset(UtcOffset.Formats.ISO) - * } - * val instant = Instant.parse("2023-01-02 22:35:01.14 +01:00", customFormat) - * instant.format(customFormat, offset = UtcOffset(hours = 2)) // 2023-01-02 23:35:01.14 +02:00 - * ``` - * - * Additionally, there are several `kotlinx-serialization` serializers for [Instant]: - * - [InstantIso8601Serializer] for the ISO 8601 extended format. - * - [InstantComponentSerializer] for an object with components. - * - * @see LocalDateTime for a user-visible representation of moments in time in an unspecified time zone. + * @see Instant.toString for formatting using the default format. + * @see Instant.format for formatting using a custom format. + * @see Instant.parse for parsing an ISO string without involving `kotlinx-datetime`. + * @sample kotlinx.datetime.test.samples.InstantSamples.parsing */ -@Serializable(with = InstantIso8601Serializer::class) -public expect class Instant : Comparable { - - /** - * The number of seconds from the epoch instant `1970-01-01T00:00:00Z` rounded down to a [Long] number. - * - * The difference between the rounded number of seconds and the actual number of seconds - * is returned by [nanosecondsOfSecond] property expressed in nanoseconds. - * - * Note that this number doesn't include leap seconds added or removed since the epoch. - * - * @see fromEpochSeconds - * @sample kotlinx.datetime.test.samples.InstantSamples.epochSeconds - */ - public val epochSeconds: Long - - /** - * The number of nanoseconds by which this instant is later than [epochSeconds] from the epoch instant. - * - * The value is always non-negative and lies in the range `0..999_999_999`. - * - * @see fromEpochSeconds - * @sample kotlinx.datetime.test.samples.InstantSamples.nanosecondsOfSecond - */ - public val nanosecondsOfSecond: Int - - /** - * Returns the number of milliseconds from the epoch instant `1970-01-01T00:00:00Z`. - * - * Any fractional part of a millisecond is rounded toward zero to the whole number of milliseconds. - * - * If the result does not fit in [Long], - * returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. - * - * @see fromEpochMilliseconds - * @sample kotlinx.datetime.test.samples.InstantSamples.toEpochMilliseconds - */ - public fun toEpochMilliseconds(): Long - - /** - * Returns an instant that is the result of adding the specified [duration] to this instant. - * - * If the [duration] is positive, the returned instant is later than this instant. - * If the [duration] is negative, the returned instant is earlier than this instant. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - * - * **Pitfall**: [Duration.Companion.days] are multiples of 24 hours and are not calendar-based. - * Consider using the [plus] overload that accepts a multiple of a [DateTimeUnit] instead for calendar-based - * operations instead of using [Duration]. - * For an explanation of why some days are not 24 hours, see [DateTimeUnit.DayBased]. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.plusDuration - */ - public operator fun plus(duration: Duration): Instant - - /** - * Returns an instant that is the result of subtracting the specified [duration] from this instant. - * - * If the [duration] is positive, the returned instant is earlier than this instant. - * If the [duration] is negative, the returned instant is later than this instant. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - * - * **Pitfall**: [Duration.Companion.days] are multiples of 24 hours and are not calendar-based. - * Consider using the [minus] overload that accepts a multiple of a [DateTimeUnit] instead for calendar-based - * operations instead of using [Duration]. - * For an explanation of why some days are not 24 hours, see [DateTimeUnit.DayBased]. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.minusDuration - */ - public operator fun minus(duration: Duration): Instant - - // questionable - /** - * Returns the [Duration] between two instants: [other] and `this`. - * - * The duration returned is positive if this instant is later than the other, - * and negative if this instant is earlier than the other. - * - * The result is never clamped, but note that for instants that are far apart, - * the value returned may represent the duration between them inexactly due to the loss of precision. - * - * Note that sources of [Instant] values (in particular, [Clock]) are not guaranteed to be in sync with each other - * or even monotonic, so the result of this operation may be negative even if the other instant was observed later - * than this one, or vice versa. - * For measuring time intervals, consider using [TimeSource.Monotonic]. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.minusInstant - */ - public operator fun minus(other: Instant): Duration - - /** - * Compares `this` instant with the [other] instant. - * Returns zero if this instant represents the same moment as the other (meaning they are equal to one another), - * a negative number if this instant is earlier than the other, - * and a positive number if this instant is later than the other. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.compareToSample - */ - public override operator fun compareTo(other: Instant): Int - - /** - * Converts this instant to the ISO 8601 string representation, for example, `2023-01-02T23:40:57.120Z`. - * - * The representation uses the UTC-SLS time scale instead of UTC. - * In practice, this means that leap second handling will not be readjusted to the UTC. - * Leap seconds will not be added or skipped, so it is impossible to acquire a string - * where the component for seconds is 60, and for any day, it's possible to observe 23:59:59. - * - * @see parse - * @see DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET for a very similar format. The difference is that - * [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] will not add trailing zeros for readability to the - * fractional part of the second. - * @sample kotlinx.datetime.test.samples.InstantSamples.toStringSample - */ - public override fun toString(): String - - - public companion object { - @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.datetime.Clock"), level = DeprecationLevel.ERROR) - public fun now(): Instant - - /** - * Returns an [Instant] that is [epochMilliseconds] number of milliseconds from the epoch instant `1970-01-01T00:00:00Z`. - * - * Every value of [epochMilliseconds] is guaranteed to be representable as an [Instant]. - * - * Note that [Instant] also supports nanosecond precision via [fromEpochSeconds]. - * - * @see Instant.toEpochMilliseconds - * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochMilliseconds - */ - public fun fromEpochMilliseconds(epochMilliseconds: Long): Instant - - /** - * Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z` - * and the [nanosecondAdjustment] number of nanoseconds from the whole second. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - * In any case, it is guaranteed that instants between [DISTANT_PAST] and [DISTANT_FUTURE] can be represented. - * - * [fromEpochMilliseconds] is a similar function for when input data only has millisecond precision. - * - * @see Instant.epochSeconds - * @see Instant.nanosecondsOfSecond - * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochSeconds - */ - public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long = 0): Instant - - /** - * Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z` - * and the [nanosecondAdjustment] number of nanoseconds from the whole second. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - * In any case, it is guaranteed that instants between [DISTANT_PAST] and [DISTANT_FUTURE] can be represented. - * - * [fromEpochMilliseconds] is a similar function for when input data only has millisecond precision. - * - * @see Instant.epochSeconds - * @see Instant.nanosecondsOfSecond - * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochSecondsIntNanos - */ - public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant - - /** - * A shortcut for calling [DateTimeFormat.parse], followed by [DateTimeComponents.toInstantUsingOffset]. - * - * Parses a string that represents an instant, including date and time components and a mandatory - * time zone offset and returns the parsed [Instant] value. - * - * The string is considered to represent time on the UTC-SLS time scale instead of UTC. - * In practice, this means that, even if there is a leap second on the given day, it will not affect how the - * time is parsed, even if it's in the last 1000 seconds of the day. - * Instead, even if there is a negative leap second on the given day, 23:59:59 is still considered a valid time. - * 23:59:60 is invalid on UTC-SLS, so parsing it will fail. - * - * If the format is not specified, [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] is used. - * `2023-01-02T23:40:57.120Z` is an example of a string in this format. - * - * @throws IllegalArgumentException if the text cannot be parsed or the boundaries of [Instant] are exceeded. - * - * @see Instant.toString for formatting using the default format. - * @see Instant.format for formatting using a custom format. - * @sample kotlinx.datetime.test.samples.InstantSamples.parsing - */ - public fun parse( - input: CharSequence, - format: DateTimeFormat = DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET - ): Instant - - /** - * An instant value that is far in the past. - * - * All instants in the range `DISTANT_PAST..DISTANT_FUTURE` can be [converted][Instant.toLocalDateTime] to - * [LocalDateTime] without exceptions in every time zone. - * - * [isDistantPast] returns true for this value and all earlier ones. - */ - public val DISTANT_PAST: Instant // -100001-12-31T23:59:59.999999999Z - - /** - * An instant value that is far in the future. - * - * All instants in the range `DISTANT_PAST..DISTANT_FUTURE` can be [converted][Instant.toLocalDateTime] to - * [LocalDateTime] without exceptions in every time zone. - * - * [isDistantFuture] returns true for this value and all later ones. - */ - public val DISTANT_FUTURE: Instant // +100000-01-01T00:00:00Z - - internal val MIN: Instant - internal val MAX: Instant - } +public fun Instant.Companion.parse( + input: CharSequence, + format: DateTimeFormat, +): Instant = try { + format.parse(input).toInstantUsingOffset() +} catch (e: IllegalArgumentException) { + throw DateTimeFormatException("Failed to parse an instant from '$input'", e) } -/** - * Returns true if the instant is [Instant.DISTANT_PAST] or earlier. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.isDistantPast - */ -public val Instant.isDistantPast: Boolean - get() = this <= Instant.DISTANT_PAST - -/** - * Returns true if the instant is [Instant.DISTANT_FUTURE] or later. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.isDistantFuture - */ -public val Instant.isDistantFuture: Boolean - get() = this >= Instant.DISTANT_FUTURE - -/** - * @suppress - */ -@Deprecated("Removed to support more idiomatic code. See https://github.com/Kotlin/kotlinx-datetime/issues/339", ReplaceWith("Instant.parse(this)"), DeprecationLevel.WARNING) -public fun String.toInstant(): Instant = Instant.parse(this) - /** * Returns an instant that is the result of adding components of [DateTimePeriod] to this instant. The components are * added in the order from the largest units to the smallest, i.e., from years to nanoseconds. diff --git a/core/common/src/TimeZone.kt b/core/common/src/TimeZone.kt index 23767380d..8a98b4f56 100644 --- a/core/common/src/TimeZone.kt +++ b/core/common/src/TimeZone.kt @@ -10,6 +10,7 @@ package kotlinx.datetime import kotlinx.datetime.serializers.* import kotlinx.serialization.Serializable +import kotlin.time.Instant /** * A time zone, provides the conversion between [Instant] and [LocalDateTime] values @@ -126,6 +127,24 @@ public expect open class TimeZone { */ public fun Instant.toLocalDateTime(): LocalDateTime + /** + * Return the civil datetime value that this instant has in the time zone provided as an implicit receiver. + * + * Note that while this conversion is unambiguous, the inverse ([LocalDateTime.toInstant]) + * is not necessarily so. + * + * @see LocalDateTime.toInstant + * @see Instant.offsetIn + * @throws DateTimeArithmeticException if this value is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.TimeZoneSamples.toLocalDateTimeWithTwoReceivers + */ + @Suppress("DEPRECATION") + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().toLocalDateTime()") + ) + public fun kotlinx.datetime.Instant.toLocalDateTime(): LocalDateTime + /** * Returns an instant that corresponds to this civil datetime value in the time zone provided as an implicit receiver. * @@ -141,7 +160,13 @@ public expect open class TimeZone { * @see Instant.toLocalDateTime * @sample kotlinx.datetime.test.samples.TimeZoneSamples.toInstantWithTwoReceivers */ - public fun LocalDateTime.toInstant(): Instant + @Suppress("DEPRECATION_ERROR") + public fun LocalDateTime.toInstant(youShallNotPass: OverloadMarker = OverloadMarker.INSTANCE): Instant + + @PublishedApi + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") + @kotlin.internal.LowPriorityInOverloadResolution + internal fun LocalDateTime.toInstant(): kotlinx.datetime.Instant } /** @@ -195,6 +220,14 @@ public typealias ZoneOffset = FixedOffsetTimeZone */ public expect fun TimeZone.offsetAt(instant: Instant): UtcOffset +@Suppress("DEPRECATION") +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.offsetAt(instant.toStdlibInstant())") +) +public fun TimeZone.offsetAt(instant: kotlinx.datetime.Instant): UtcOffset = + offsetAt(instant.toStdlibInstant()) + /** * Returns a civil datetime value that this instant has in the specified [timeZone]. * @@ -208,6 +241,14 @@ public expect fun TimeZone.offsetAt(instant: Instant): UtcOffset */ public expect fun Instant.toLocalDateTime(timeZone: TimeZone): LocalDateTime +@Suppress("DEPRECATION") +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().toLocalDateTime(timeZone)") +) +public fun kotlinx.datetime.Instant.toLocalDateTime(timeZone: TimeZone): LocalDateTime = + toStdlibInstant().toLocalDateTime(timeZone) + /** * Returns a civil datetime value that this instant has in the specified [UTC offset][offset]. * @@ -221,6 +262,14 @@ public expect fun Instant.toLocalDateTime(timeZone: TimeZone): LocalDateTime */ internal expect fun Instant.toLocalDateTime(offset: UtcOffset): LocalDateTime +@Suppress("DEPRECATION") +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().toLocalDateTime(offset)") +) +public fun kotlinx.datetime.Instant.toLocalDateTime(offset: UtcOffset): LocalDateTime = + toStdlibInstant().toLocalDateTime(offset) + /** * Finds the offset from UTC the specified [timeZone] has at this instant of physical time. * @@ -235,6 +284,14 @@ internal expect fun Instant.toLocalDateTime(offset: UtcOffset): LocalDateTime public fun Instant.offsetIn(timeZone: TimeZone): UtcOffset = timeZone.offsetAt(this) +@Suppress("DEPRECATION") +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().offsetIn(timeZone)") +) +public fun kotlinx.datetime.Instant.offsetIn(timeZone: TimeZone): UtcOffset = + timeZone.offsetAt(toStdlibInstant()) + /** * Returns an instant that corresponds to this civil datetime value in the specified [timeZone]. * @@ -250,7 +307,14 @@ public fun Instant.offsetIn(timeZone: TimeZone): UtcOffset = * @see Instant.toLocalDateTime * @sample kotlinx.datetime.test.samples.TimeZoneSamples.localDateTimeToInstantInZone */ -public expect fun LocalDateTime.toInstant(timeZone: TimeZone): Instant +@Suppress("DEPRECATION_ERROR") +public expect fun LocalDateTime.toInstant(timeZone: TimeZone, youShallNotPass: OverloadMarker = OverloadMarker.INSTANCE): Instant + +@PublishedApi +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") +@kotlin.internal.LowPriorityInOverloadResolution +internal fun LocalDateTime.toInstant(timeZone: TimeZone): kotlinx.datetime.Instant = + toInstant(timeZone).toDeprecatedInstant() /** * Returns an instant that corresponds to this civil datetime value that happens at the specified [UTC offset][offset]. @@ -258,7 +322,14 @@ public expect fun LocalDateTime.toInstant(timeZone: TimeZone): Instant * @see Instant.toLocalDateTime * @sample kotlinx.datetime.test.samples.TimeZoneSamples.localDateTimeToInstantInOffset */ -public expect fun LocalDateTime.toInstant(offset: UtcOffset): Instant +@Suppress("DEPRECATION_ERROR") +public expect fun LocalDateTime.toInstant(offset: UtcOffset, youShallNotPass: OverloadMarker = OverloadMarker.INSTANCE): Instant + +@PublishedApi +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") +@kotlin.internal.LowPriorityInOverloadResolution +internal fun LocalDateTime.toInstant(offset: UtcOffset): kotlinx.datetime.Instant = + toInstant(offset).toDeprecatedInstant() /** * Returns an instant that corresponds to the start of this date in the specified [timeZone]. @@ -273,4 +344,11 @@ public expect fun LocalDateTime.toInstant(offset: UtcOffset): Instant * * @sample kotlinx.datetime.test.samples.TimeZoneSamples.atStartOfDayIn */ -public expect fun LocalDate.atStartOfDayIn(timeZone: TimeZone): Instant +@Suppress("DEPRECATION_ERROR") +public expect fun LocalDate.atStartOfDayIn(timeZone: TimeZone, youShallNotPass: OverloadMarker = OverloadMarker.INSTANCE): Instant + +@PublishedApi +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") +@kotlin.internal.LowPriorityInOverloadResolution +internal fun LocalDate.atStartOfDayIn(timeZone: TimeZone): kotlinx.datetime.Instant = + atStartOfDayIn(timeZone).toDeprecatedInstant() diff --git a/core/common/src/format/DateTimeComponents.kt b/core/common/src/format/DateTimeComponents.kt index 67e430673..f79c91ff8 100644 --- a/core/common/src/format/DateTimeComponents.kt +++ b/core/common/src/format/DateTimeComponents.kt @@ -12,6 +12,7 @@ import kotlinx.datetime.internal.format.* import kotlinx.datetime.internal.format.parser.Copyable import kotlinx.datetime.internal.safeMultiply import kotlin.reflect.* +import kotlin.time.Instant /** * A collection of datetime fields used specifically for parsing and formatting. @@ -244,6 +245,15 @@ public class DateTimeComponents internal constructor(internal val contents: Date year = year!! + ((instant.epochSeconds / SECONDS_PER_10000_YEARS) * 10000).toInt() } + @Suppress("DEPRECATION") + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.setDateTimeOffset(instant.toStdlibInstant(), utcOffset)") + ) + public fun setDateTimeOffset(instant: kotlinx.datetime.Instant, utcOffset: UtcOffset) { + setDateTimeOffset(instant.toStdlibInstant(), utcOffset) + } + /** * Writes the contents of the specified [localDateTime] and [utcOffset] to this [DateTimeComponents]. * @@ -481,7 +491,8 @@ public class DateTimeComponents internal constructor(internal val contents: Date * with one another. * @sample kotlinx.datetime.test.samples.format.DateTimeComponentsSamples.toInstantUsingOffset */ - public fun toInstantUsingOffset(): Instant { + @Suppress("DEPRECATION_ERROR") + public fun toInstantUsingOffset(youShallNotPass: OverloadMarker = OverloadMarker.INSTANCE): Instant { val offset = toUtcOffset() val time = toLocalTime() val truncatedDate = contents.date.copy() @@ -499,10 +510,16 @@ public class DateTimeComponents internal constructor(internal val contents: Date } catch (e: ArithmeticException) { throw DateTimeFormatException("The parsed date is outside the range representable by Instant", e) } - if (totalSeconds < Instant.MIN.epochSeconds || totalSeconds > Instant.MAX.epochSeconds) + val result = Instant.fromEpochSeconds(totalSeconds, nanosecond ?: 0) + if (result.epochSeconds != totalSeconds) throw DateTimeFormatException("The parsed date is outside the range representable by Instant") - return Instant.fromEpochSeconds(totalSeconds, nanosecond ?: 0) + return result } + + @PublishedApi + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") + @kotlin.internal.LowPriorityInOverloadResolution + internal fun toInstantUsingOffset(): kotlinx.datetime.Instant = toInstantUsingOffset().toDeprecatedInstant() } /** diff --git a/core/common/src/serializers/InstantSerializers.kt b/core/common/src/serializers/DeprecatedInstantSerializers.kt similarity index 86% rename from core/common/src/serializers/InstantSerializers.kt rename to core/common/src/serializers/DeprecatedInstantSerializers.kt index c64bdf475..094a9d829 100644 --- a/core/common/src/serializers/InstantSerializers.kt +++ b/core/common/src/serializers/DeprecatedInstantSerializers.kt @@ -3,6 +3,7 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ +@file:Suppress("DEPRECATION") package kotlinx.datetime.serializers import kotlinx.datetime.Instant @@ -18,6 +19,11 @@ import kotlinx.serialization.encoding.* * @see Instant.toString * @see Instant.parse */ +@Deprecated( + "kotlinx.datetime.Instant is superseded by kotlin.time.Instant, " + + "whose serializers are provided by kotlinx.serialization.", + level = DeprecationLevel.WARNING, +) public object InstantIso8601Serializer : KSerializer { override val descriptor: SerialDescriptor = @@ -37,6 +43,11 @@ public object InstantIso8601Serializer : KSerializer { * * JSON example: `{"epochSeconds":1607505416,"nanosecondsOfSecond":124000}` */ +@Deprecated( + "kotlinx.datetime.Instant is superseded by kotlin.time.Instant, " + + "whose serializers are provided by kotlinx.serialization.", + level = DeprecationLevel.WARNING, +) public object InstantComponentSerializer : KSerializer { override val descriptor: SerialDescriptor = diff --git a/core/common/test/ClockTimeSourceTest.kt b/core/common/test/ClockTimeSourceTest.kt index 75e30106d..3db2fce99 100644 --- a/core/common/test/ClockTimeSourceTest.kt +++ b/core/common/test/ClockTimeSourceTest.kt @@ -11,6 +11,8 @@ import kotlin.time.* import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.Duration.Companion.seconds +import kotlin.time.Clock +import kotlin.time.Instant @OptIn(ExperimentalTime::class) @Suppress("DEPRECATION") @@ -45,7 +47,7 @@ class ClockTimeSourceTest { clock.instant -= 2.days assertEquals(-1.days, mark.elapsedNow()) - clock.instant = Instant.MAX + clock.instant = Instant.fromEpochSeconds(Long.MAX_VALUE) assertEquals(Duration.INFINITE, mark.elapsedNow()) } diff --git a/core/common/test/DeprecatedClockTimeSourceTest.kt b/core/common/test/DeprecatedClockTimeSourceTest.kt new file mode 100644 index 000000000..85a453ee5 --- /dev/null +++ b/core/common/test/DeprecatedClockTimeSourceTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2019-2023 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +@file:Suppress("DEPRECATION") +package kotlinx.datetime.test + +import kotlinx.datetime.* +import kotlin.test.* +import kotlin.time.ExperimentalTime +import kotlin.time.Duration +import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.nanoseconds + +@OptIn(ExperimentalTime::class) +class DeprecatedClockTimeSourceTest { + @Test + fun arithmetic() { + val timeSource = Clock.System.asTimeSource() + val mark0 = timeSource.markNow() + + val markPast = mark0 - 1.days + val markFuture = mark0 + 1.days + + assertTrue(markPast < mark0) + assertTrue(markFuture > mark0) + assertEquals(mark0, markPast + 1.days) + assertEquals(2.days, markFuture - markPast) + } + + @Test + fun elapsed() { + val clock = object : Clock { + var instant = Clock.System.now() + override fun now(): Instant = instant + } + val timeSource = clock.asTimeSource() + val mark = timeSource.markNow() + assertEquals(Duration.ZERO, mark.elapsedNow()) + + clock.instant += 1.days + assertEquals(1.days, mark.elapsedNow()) + + clock.instant -= 2.days + assertEquals(-1.days, mark.elapsedNow()) + + clock.instant = Instant.fromEpochSeconds(Long.MAX_VALUE) + assertEquals(Duration.INFINITE, mark.elapsedNow()) + } + + @Test + fun differentSources() { + val mark1 = Clock.System.asTimeSource().markNow() + val mark2 = object : Clock { + override fun now(): Instant = Instant.DISTANT_FUTURE + }.asTimeSource().markNow() + assertNotEquals(mark1, mark2) + assertFailsWith { mark1 - mark2 } + assertFailsWith { mark1 compareTo mark2 } + } + + @Test + fun saturation() { + val mark0 = Clock.System.asTimeSource().markNow() + + val markFuture = mark0 + Duration.INFINITE + val markPast = mark0 - Duration.INFINITE + + for (delta in listOf(Duration.ZERO, 1.nanoseconds, 1.days)) { + assertEquals(markFuture, markFuture - delta) + assertEquals(markFuture, markFuture + delta) + + assertEquals(markPast, markPast - delta) + assertEquals(markPast, markPast + delta) + } + val infinitePairs = listOf(markFuture to markPast, markFuture to mark0, mark0 to markPast) + for ((later, earlier) in infinitePairs) { + assertEquals(Duration.INFINITE, later - earlier) + assertEquals(-Duration.INFINITE, earlier - later) + } + assertEquals(Duration.ZERO, markFuture - markFuture) + assertEquals(Duration.ZERO, markPast - markPast) + + assertFailsWith { markFuture - Duration.INFINITE } + assertFailsWith { markPast + Duration.INFINITE } + } +} diff --git a/core/common/test/DeprecatedInstantTest.kt b/core/common/test/DeprecatedInstantTest.kt new file mode 100644 index 000000000..b8523ae26 --- /dev/null +++ b/core/common/test/DeprecatedInstantTest.kt @@ -0,0 +1,640 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +@file:Suppress("DEPRECATION") +package kotlinx.datetime.test + +import kotlinx.datetime.* +import kotlinx.datetime.format.* +import kotlinx.datetime.internal.* +import kotlin.random.* +import kotlin.test.* +import kotlin.time.Duration +import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.nanoseconds +import kotlin.time.Duration.Companion.seconds + +class DeprecatedInstantTest { + + @Test + fun testNow() { + val instant = Clock.System.now() + val millis = instant.toEpochMilliseconds() + + assertTrue(millis > 1_500_000_000_000L) + + println(instant) + println(instant.toEpochMilliseconds()) + + val millisInstant = Instant.fromEpochMilliseconds(millis) + + assertEquals(millis, millisInstant.toEpochMilliseconds()) + + val notEqualInstant = Instant.fromEpochMilliseconds(millis + 1) + assertNotEquals(notEqualInstant, instant) + } + + @Test + fun instantArithmetic() { + val instant = Clock.System.now().toEpochMilliseconds().let { Instant.fromEpochMilliseconds(it) } // round to millis + val diffMillis = Random.nextLong(1000, 1_000_000_000) + val diff = diffMillis.milliseconds + + val nextInstant = (instant.toEpochMilliseconds() + diffMillis).let { Instant.fromEpochMilliseconds(it) } + + assertEquals(diff, nextInstant - instant) + assertEquals(nextInstant, instant + diff) + assertEquals(instant, nextInstant - diff) + + println("this: $instant, next: $nextInstant, diff: ${diff.toIsoString()}") + } + + @Test + fun instantToLocalDTConversion() { + val now = Clock.System.now() + println(now.toLocalDateTime(TimeZone.UTC)) + println(now.toLocalDateTime(TimeZone.currentSystemDefault())) + } + + /* Based on the ThreeTenBp project. + * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos + */ + @Test + fun parseIsoString() { + val instants = arrayOf( + Triple("1970-01-01T00:00:00Z", 0, 0), + Triple("1970-01-01t00:00:00Z", 0, 0), + Triple("1970-01-01T00:00:00z", 0, 0), + Triple("1970-01-01T00:00:00.0Z", 0, 0), + Triple("1970-01-01T00:00:00.000000000Z", 0, 0), + Triple("1970-01-01T00:00:00.000000001Z", 0, 1), + Triple("1970-01-01T00:00:00.100000000Z", 0, 100000000), + Triple("1970-01-01T00:00:01Z", 1, 0), + Triple("1970-01-01T00:01:00Z", 60, 0), + Triple("1970-01-01T00:01:01Z", 61, 0), + Triple("1970-01-01T00:01:01.000000001Z", 61, 1), + Triple("1970-01-01T01:00:00.000000000Z", 3600, 0), + Triple("1970-01-01T01:01:01.000000001Z", 3661, 1), + Triple("1970-01-02T01:01:01.100000000Z", 90061, 100000000)) + instants.forEach { + val (str, seconds, nanos) = it + val instant = Instant.parse(str) + assertEquals(seconds.toLong() * 1000 + nanos / 1000000, instant.toEpochMilliseconds()) + } + + assertInvalidFormat { Instant.parse("1970-01-01T23:59:60Z")} + assertInvalidFormat { Instant.parse("1970-01-01T24:00:00Z")} + assertInvalidFormat { Instant.parse("1970-01-01T23:59Z")} + assertInvalidFormat { Instant.parse("x") } + assertInvalidFormat { Instant.parse("12020-12-31T23:59:59.000000000Z") } + // this string represents an Instant that is currently larger than Instant.MAX any of the implementations: + assertInvalidFormat { Instant.parse("+1000000001-12-31T23:59:59.000000000Z") } + } + + @Test + fun parseStringsWithOffsets() { + val strings = arrayOf( + Pair("2020-01-01T00:01:01.02+18:00", "2019-12-31T06:01:01.020Z"), + Pair("2020-01-01T00:01:01.123456789-17:59:59", "2020-01-01T18:01:00.123456789Z"), + Pair("2020-01-01T00:01:01.010203040+17:59:59", "2019-12-31T06:01:02.010203040Z"), + Pair("2020-01-01T00:01:01.010203040+17:59", "2019-12-31T06:02:01.010203040Z"), + Pair("2020-01-01T00:01:01+00", "2020-01-01T00:01:01Z"), + ) + strings.forEach { (str, strInZ) -> + val instant = Instant.parse(str) + assertEquals(Instant.parse(strInZ), instant, str) + assertEquals(strInZ, instant.toString(), str) + } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+18:01") } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+1801") } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+0") } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+") } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01") } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+000000") } + + val instants = listOf( + Instant.DISTANT_FUTURE, + Instant.DISTANT_PAST, + Instant.fromEpochSeconds(0, 0)) + + val offsetStrings = listOf( + "Z", + "+03:12:14", + "-03:12:14", + "+02:35", + "-02:35", + "+04", + "-04", + ) + + val offsetFormat = UtcOffset.Format { + optional("Z") { + offsetHours() + optional { + char(':'); offsetMinutesOfHour() + optional { char(':'); offsetSecondsOfMinute() } + } + } + } + val offsets = offsetStrings.map { UtcOffset.parse(it, offsetFormat) } + + for (instant in instants) { + for (offsetIx in offsets.indices) { + val str = instant.format(DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET, offsets[offsetIx]) + val offsetString = offsets[offsetIx].toString() + assertEquals(offsetString, offsetString.commonSuffixWith(str)) + assertEquals(instant, Instant.parse(str, DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET)) + assertEquals(instant, Instant.parse(str)) + } + } + } + + @Test + fun instantCalendarArithmetic() { + val zone = TimeZone.of("Europe/Berlin") + + fun expectBetween(instant1: Instant, instant2: Instant, expected: Long, unit: DateTimeUnit) { + assertEquals(expected, instant1.until(instant2, unit, zone), "i1.until(i2)") + assertEquals(expected, -instant2.until(instant1, unit, zone), "i2.until(i1)") + assertEquals(expected, instant2.minus(instant1, unit, zone), "i2.minus(i1)") + assertEquals(expected, -instant1.minus(instant2, unit, zone), "i1.minus(i2)") + + for (timeUnit in listOf(DateTimeUnit.MICROSECOND, DateTimeUnit.MILLISECOND, DateTimeUnit.SECOND, DateTimeUnit.MINUTE, DateTimeUnit.HOUR)) { + val diff = instant2.minus(instant1, timeUnit, zone) + assertEquals(instant2 - instant1, timeUnit.duration * diff.toDouble()) + assertEquals(instant2, instant1.plus(diff, timeUnit, zone)) + assertEquals(instant1, instant2.minus(diff, timeUnit, zone)) + assertEquals(instant2, instant1.plus(diff, timeUnit)) + assertEquals(instant1, instant2.minus(diff, timeUnit)) + } + } + + val instant1 = LocalDateTime(2019, Month.OCTOBER, 27, 2, 59).toInstant(zone).toDeprecatedInstant() + checkComponents(instant1.toLocalDateTime(zone), 2019, 10, 27, 2, 59) + + val instant2 = instant1.plus(DateTimePeriod(hours = 24), zone) + checkComponents(instant2.toLocalDateTime(zone), 2019, 10, 28, 1, 59) + expectBetween(instant1, instant2, 24, DateTimeUnit.HOUR) + assertEquals(instant1, instant2.minus(DateTimePeriod(hours = 24), zone)) + + val instant3 = instant1.plus(1, DateTimeUnit.DAY, zone) + checkComponents(instant3.toLocalDateTime(zone), 2019, 10, 28, 2, 59) + expectBetween(instant1, instant3, 25, DateTimeUnit.HOUR) + expectBetween(instant1, instant3, 1, DateTimeUnit.DAY) + assertEquals(1, instant1.daysUntil(instant3, zone)) + assertEquals(instant1.minus(1, DateTimeUnit.HOUR), instant2.minus(1, DateTimeUnit.DAY, zone)) + + val instant4 = instant1.plus(14, DateTimeUnit.MONTH, zone) + checkComponents(instant4.toLocalDateTime(zone), 2020, 12, 27, 2, 59) + expectBetween(instant1, instant4, 1, DateTimeUnit.YEAR) + expectBetween(instant1, instant4, 4, DateTimeUnit.QUARTER) + expectBetween(instant1, instant4, 14, DateTimeUnit.MONTH) + expectBetween(instant1, instant4, 61, DateTimeUnit.WEEK) + expectBetween(instant1, instant4, 366 + 31 + 30, DateTimeUnit.DAY) + expectBetween(instant1, instant4, (366 + 31 + 30) * 24 + 1, DateTimeUnit.HOUR) + assertEquals(instant1.plus(1, DateTimeUnit.HOUR), instant4.minus(14, DateTimeUnit.MONTH, zone)) + + val period = DateTimePeriod(days = 1, hours = 1) + val instant5 = instant1.plus(period, zone) + checkComponents(instant5.toLocalDateTime(zone), 2019, 10, 28, 3, 59) + assertEquals(period, instant1.periodUntil(instant5, zone)) + assertEquals(period, instant5.minus(instant1, zone)) + assertEquals(26.hours, instant5.minus(instant1)) + assertEquals(instant1.plus(1, DateTimeUnit.HOUR), instant5.minus(period, zone)) + + val instant6 = instant1.plus(23, DateTimeUnit.HOUR, zone) + checkComponents(instant6.toLocalDateTime(zone), 2019, 10, 28, 0, 59) + expectBetween(instant1, instant6, 23, DateTimeUnit.HOUR) + expectBetween(instant1, instant6, 0, DateTimeUnit.DAY) + assertEquals(instant1, instant6.minus(23, DateTimeUnit.HOUR, zone)) + } + + @Test + fun addingMultiplesOf2_32() { + val pow2_32 = 1L shl 32 + val instant1 = Instant.fromEpochSeconds(0) + val instant2 = instant1.plus(pow2_32, DateTimeUnit.NANOSECOND, TimeZone.UTC) + assertEquals(pow2_32 / NANOS_PER_ONE, instant2.epochSeconds) + assertEquals(pow2_32 % NANOS_PER_ONE, instant2.nanosecondsOfSecond.toLong()) + + val instant3 = instant1.plus(pow2_32, DateTimeUnit.SECOND, TimeZone.UTC) + assertEquals(pow2_32, instant3.epochSeconds) + } + + @Test + fun unitMultiplesUntil() { + val unit1000days = DateTimeUnit.DAY * 1000 + val unit4years = DateTimeUnit.YEAR * 4 // longer than 1000-DAY + + val zone = TimeZone.UTC + val min = LocalDateTime.MIN.toInstant(zone) + val max = LocalDateTime.MAX.toInstant(zone) + val diffDays = min.until(max, unit1000days, zone) + val diffYears = min.until(max, unit4years, zone) + assertTrue(diffDays in 0..Int.MAX_VALUE, "difference in $unit1000days should fit in Int, was $diffDays") + assertTrue(diffDays > diffYears, "difference in $unit1000days unit must be more than in $unit4years unit, was $diffDays $diffYears") + + val unit500ns = DateTimeUnit.NANOSECOND * 500 + val start = Instant.parse("1700-01-01T00:00:00Z") + val end = start.plus(300, DateTimeUnit.YEAR, zone) + val diffNs = start.until(end, unit500ns, zone) + val diffUs = start.until(end, DateTimeUnit.MICROSECOND, zone) + assertEquals(diffUs * 2, diffNs) + + assertEquals(end, start.plus(diffNs, unit500ns, zone)) + assertEquals(start, end.plus(-diffUs, DateTimeUnit.MICROSECOND, zone)) + } + + @Test + fun instantOffset() { + val zone = TimeZone.of("Europe/Berlin") + val instant1 = LocalDateTime(2019, 10, 27, 2, 59, 0, 0).toInstant(zone) + val ldt1 = instant1.toLocalDateTime(zone) + val offset1 = instant1.offsetIn(zone) + checkComponents(ldt1, 2019, 10, 27, 2, 59) + assertEquals(instant1, ldt1.toInstant(offset1)) + + val instant2 = instant1 + 1.hours + val ldt2 = instant2.toLocalDateTime(zone) + val offset2 = instant2.offsetIn(zone) + assertEquals(ldt1, ldt2) + assertEquals(instant2, ldt2.toInstant(offset2)) + assertNotEquals(offset1, offset2) + assertEquals(offset1.totalSeconds.seconds, offset2.totalSeconds.seconds + 1.hours) + + val instant3 = instant2 - 2.hours + val offset3 = instant3.offsetIn(zone) + assertEquals(offset1, offset3) + + // without the minus, this test fails on JVM + (Instant.MAX - (2 * 365).days).offsetIn(zone) + } + + @Test + fun changingTimeZoneRules() { + val start = Instant.parse("1991-01-25T23:15:15.855Z") + val end = Instant.parse("2006-04-24T22:07:32.561Z") + val diff = start.periodUntil(end, TimeZone.of("Europe/Moscow")) + val end2 = start.plus(diff, TimeZone.of("Europe/Moscow")) + assertEquals(end, end2) + } + + @Test + fun diffInvariant() { + repeat(STRESS_TEST_ITERATIONS) { + val millis1 = Random.nextLong(2_000_000_000_000L) + val millis2 = Random.nextLong(2_000_000_000_000L) + val instant1 = Instant.fromEpochMilliseconds(millis1) + val instant2 = Instant.fromEpochMilliseconds(millis2) + + val diff = instant1.periodUntil(instant2, TimeZone.currentSystemDefault()) + val instant3 = instant1.plus(diff, TimeZone.currentSystemDefault()) + + if (instant2 != instant3) + println("start: $instant1, end: $instant2, start + diff: $instant3, diff: $diff") + } + } + + @Test + fun diffInvariantSameAsDate() { + repeat(STRESS_TEST_ITERATIONS) { + val millis1 = Random.nextLong(2_000_000_000_000L) + val millis2 = Random.nextLong(2_000_000_000_000L) + with(TimeZone.UTC) TZ@ { + val date1 = Instant.fromEpochMilliseconds(millis1).toLocalDateTime().date + val date2 = Instant.fromEpochMilliseconds(millis2).toLocalDateTime().date + val instant1 = date1.atStartOfDayIn(this@TZ) + val instant2 = date2.atStartOfDayIn(this@TZ) + + val diff1 = instant1.periodUntil(instant2, this@TZ) + val diff2 = date1.periodUntil(date2) + + if (diff1 != diff2) + throw AssertionError( + "start: $instant1, end: $instant2, diff by instants: $diff1, diff by dates: $diff2" + ) + } + } + } + + + @Test + fun zoneDependentDiff() { + val instant1 = Instant.parse("2019-04-01T00:00:00Z") + val instant2 = Instant.parse("2019-05-01T04:00:00Z") + + for (zone in (-12..12 step 3).map { h -> TimeZone.of("${if (h >= 0) "+" else ""}$h") }) { + val dt1 = instant1.toLocalDateTime(zone) + val dt2 = instant2.toLocalDateTime(zone) + val diff = instant1.periodUntil(instant2, zone) + println("diff between $dt1 and $dt2 at zone $zone: $diff") + } + } + + /* Based on the ThreeTenBp project. + * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos + */ + @Test + fun nanosecondAdjustment() { + for (i in -2..2L) { + for (j in 0..9) { + val t: Instant = Instant.fromEpochSeconds(i, j) + val t2: Instant = Instant.fromEpochSeconds(i, j.toLong()) + assertEquals(i, t.epochSeconds) + assertEquals(j, t.nanosecondsOfSecond) + assertEquals(t, t2) + } + for (j in -10..-1) { + val t: Instant = Instant.fromEpochSeconds(i, j) + val t2: Instant = Instant.fromEpochSeconds(i, j.toLong()) + assertEquals(i - 1, t.epochSeconds) + assertEquals(j + 1000000000, t.nanosecondsOfSecond) + assertEquals(t, t2) + } + for (j in 999_999_990..999_999_999) { + val t: Instant = Instant.fromEpochSeconds(i, j) + val t2: Instant = Instant.fromEpochSeconds(i, j.toLong()) + assertEquals(i, t.epochSeconds) + assertEquals(j, t.nanosecondsOfSecond) + assertEquals(t, t2) + } + } + val t = Instant.fromEpochSeconds(0, Int.MAX_VALUE) + assertEquals((Int.MAX_VALUE / 1_000_000_000).toLong(), t.epochSeconds) + assertEquals(Int.MAX_VALUE % 1_000_000_000, t.nanosecondsOfSecond) + val t2 = Instant.fromEpochSeconds(0, Long.MAX_VALUE) + assertEquals(Long.MAX_VALUE / 1_000_000_000, t2.epochSeconds) + assertEquals((Long.MAX_VALUE % 1_000_000_000).toInt(), t2.nanosecondsOfSecond) + } + + /* Based on the ThreeTenBp project. + * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos + */ + @Test + fun strings() { + assertEquals("0000-01-02T00:00:00Z", LocalDateTime(0, 1, 2, 0, 0, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("0000-01-01T12:30:00Z", LocalDateTime(0, 1, 1, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("0000-01-01T00:00:00.000000001Z", LocalDateTime(0, 1, 1, 0, 0, 0, 1).toInstant(TimeZone.UTC).toString()) + assertEquals("0000-01-01T00:00:00Z", LocalDateTime(0, 1, 1, 0, 0, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-0001-12-31T23:59:59.999999999Z", LocalDateTime(-1, 12, 31, 23, 59, 59, 999999999).toInstant(TimeZone.UTC).toString()) + assertEquals("-0001-12-31T12:30:00Z", LocalDateTime(-1, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-0001-12-30T12:30:00Z", LocalDateTime(-1, 12, 30, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-9999-01-02T12:30:00Z", LocalDateTime(-9999, 1, 2, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-9999-01-01T12:30:00Z", LocalDateTime(-9999, 1, 1, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-9999-01-01T00:00:00Z", LocalDateTime(-9999, 1, 1, 0, 0, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-10000-12-31T23:59:59.999999999Z", LocalDateTime(-10000, 12, 31, 23, 59, 59, 999999999).toInstant(TimeZone.UTC).toString()) + assertEquals("-10000-12-31T12:30:00Z", LocalDateTime(-10000, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-10000-12-30T12:30:00Z", LocalDateTime(-10000, 12, 30, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-15000-12-31T12:30:00Z", LocalDateTime(-15000, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-19999-01-02T12:30:00Z", LocalDateTime(-19999, 1, 2, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-19999-01-01T12:30:00Z", LocalDateTime(-19999, 1, 1, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-19999-01-01T00:00:00Z", LocalDateTime(-19999, 1, 1, 0, 0, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-20000-12-31T23:59:59.999999999Z", LocalDateTime(-20000, 12, 31, 23, 59, 59, 999999999).toInstant(TimeZone.UTC).toString()) + assertEquals("-20000-12-31T12:30:00Z", LocalDateTime(-20000, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-20000-12-30T12:30:00Z", LocalDateTime(-20000, 12, 30, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-25000-12-31T12:30:00Z", LocalDateTime(-25000, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("9999-12-30T12:30:00Z", LocalDateTime(9999, 12, 30, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("9999-12-31T12:30:00Z", LocalDateTime(9999, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("9999-12-31T23:59:59.999999999Z", LocalDateTime(9999, 12, 31, 23, 59, 59, 999999999).toInstant(TimeZone.UTC).toString()) + assertEquals("+10000-01-01T00:00:00Z", LocalDateTime(10000, 1, 1, 0, 0, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+10000-01-01T12:30:00Z", LocalDateTime(10000, 1, 1, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+10000-01-02T12:30:00Z", LocalDateTime(10000, 1, 2, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+15000-12-31T12:30:00Z", LocalDateTime(15000, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-30T12:30:00Z", LocalDateTime(19999, 12, 30, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T12:30:00Z", LocalDateTime(19999, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.999999999Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 999999999).toInstant(TimeZone.UTC).toString()) + assertEquals("+20000-01-01T00:00:00Z", LocalDateTime(20000, 1, 1, 0, 0, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+20000-01-01T12:30:00Z", LocalDateTime(20000, 1, 1, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+20000-01-02T12:30:00Z", LocalDateTime(20000, 1, 2, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+25000-12-31T12:30:00Z", LocalDateTime(25000, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.009999999Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 9999999).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.999999Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 999999000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.009999Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 9999000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.123Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 123000000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.100Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 100000000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.020Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 20000000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.003Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 3000000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.000400Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 400000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.000050Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 50000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.000006Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 6000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.000000700Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 700).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.000000080Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 80).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.000000009Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 9).toInstant(TimeZone.UTC).toString()) + } + + @Test + fun distantPastAndFuture() { + val distantFutureString = "+100000-01-01T00:00:00Z" + val distantPastString = "-100001-12-31T23:59:59.999999999Z" + assertEquals(distantFutureString, Instant.DISTANT_FUTURE.toString()) + assertEquals(Instant.DISTANT_FUTURE, distantFutureString.toInstant()) + assertEquals(distantPastString, Instant.DISTANT_PAST.toString()) + assertEquals(Instant.DISTANT_PAST, distantPastString.toInstant()) + assertTrue(Instant.DISTANT_PAST.isDistantPast) + assertTrue(Instant.DISTANT_FUTURE.isDistantFuture) + assertFalse(Instant.DISTANT_PAST.isDistantFuture) + assertFalse(Instant.DISTANT_FUTURE.isDistantPast) + assertFalse((Instant.DISTANT_PAST + 1.nanoseconds).isDistantPast) + assertFalse((Instant.DISTANT_FUTURE - 1.nanoseconds).isDistantFuture) + assertTrue((Instant.DISTANT_PAST - 1.nanoseconds).isDistantPast) + assertTrue((Instant.DISTANT_FUTURE + 1.nanoseconds).isDistantFuture) + assertTrue(Instant.MAX.isDistantFuture) + assertFalse(Instant.MAX.isDistantPast) + assertTrue(Instant.MIN.isDistantPast) + assertFalse(Instant.MIN.isDistantFuture) + } + +} + +class DeprecatedInstantRangeTest { + private val UTC = TimeZone.UTC + private val maxValidInstant = LocalDateTime.MAX.toInstant(UTC).toDeprecatedInstant() + private val minValidInstant = LocalDateTime.MIN.toInstant(UTC).toDeprecatedInstant() + + private val largePositiveLongs = listOf(Long.MAX_VALUE, Long.MAX_VALUE - 1, Long.MAX_VALUE - 50) + private val largeNegativeLongs = listOf(Long.MIN_VALUE, Long.MIN_VALUE + 1, Long.MIN_VALUE + 50) + + private val largePositiveInstants = listOf(Instant.MAX, Instant.MAX - 1.seconds, Instant.MAX - 50.seconds) + private val largeNegativeInstants = listOf(Instant.MIN, Instant.MIN + 1.seconds, Instant.MIN + 50.seconds) + + private val smallInstants = listOf( + Instant.fromEpochMilliseconds(0), + Instant.fromEpochMilliseconds(1003), + Instant.fromEpochMilliseconds(253112) + ) + + + @Test + fun epochMillisecondsClamping() { + /* Any number of milliseconds in Long is representable as an Instant */ + for (instant in largePositiveInstants) { + assertEquals(Long.MAX_VALUE, instant.toEpochMilliseconds(), "$instant") + } + for (instant in largeNegativeInstants) { + assertEquals(Long.MIN_VALUE, instant.toEpochMilliseconds(), "$instant") + } + for (milliseconds in largePositiveLongs + largeNegativeLongs) { + assertEquals(milliseconds, Instant.fromEpochMilliseconds(milliseconds).toEpochMilliseconds(), + "$milliseconds") + } + } + + @Test + fun epochSecondsClamping() { + // fromEpochSeconds + // On all platforms Long.MAX_VALUE of seconds is not a valid instant. + for (seconds in largePositiveLongs) { + assertEquals(Instant.MAX, Instant.fromEpochSeconds(seconds, 35)) + } + for (seconds in largeNegativeLongs) { + assertEquals(Instant.MIN, Instant.fromEpochSeconds(seconds, 35)) + } + for (instant in largePositiveInstants + smallInstants + largeNegativeInstants) { + assertEquals(instant, Instant.fromEpochSeconds(instant.epochSeconds, instant.nanosecondsOfSecond.toLong())) + } + } + + @Test + fun durationArithmeticClamping() { + val longDurations = listOf(Duration.INFINITE) + + for (duration in longDurations) { + for (instant in smallInstants + largeNegativeInstants + largePositiveInstants) { + assertEquals(Instant.MAX, instant + duration) + } + for (instant in smallInstants + largeNegativeInstants + largePositiveInstants) { + assertEquals(Instant.MIN, instant - duration) + } + } + assertEquals(Instant.MAX, (Instant.MAX - 4.seconds) + 5.seconds) + assertEquals(Instant.MIN, (Instant.MIN + 10.seconds) - 12.seconds) + } + + @Test + fun periodArithmeticOutOfRange() { + // Instant.plus(DateTimePeriod(), TimeZone) + // Arithmetic overflow + for (instant in largePositiveInstants) { + assertArithmeticFails("$instant") { instant.plus(DateTimePeriod(nanoseconds = Long.MAX_VALUE), UTC) } + } + for (instant in largeNegativeInstants) { + assertArithmeticFails("$instant") { instant.plus(DateTimePeriod(nanoseconds = Long.MIN_VALUE), UTC) } + } + // Arithmetic overflow in an Int + for (instant in smallInstants + listOf(maxValidInstant)) { + assertEquals(instant.epochSeconds + Int.MIN_VALUE, + instant.plus(Int.MIN_VALUE, DateTimeUnit.SECOND, UTC).epochSeconds) + assertEquals(instant.epochSeconds - Int.MAX_VALUE, + instant.minus(Int.MAX_VALUE, DateTimeUnit.SECOND, UTC).epochSeconds) + } + for (instant in smallInstants + listOf(minValidInstant)) { + assertEquals(instant.epochSeconds + Int.MAX_VALUE, + instant.plus(Int.MAX_VALUE, DateTimeUnit.SECOND, UTC).epochSeconds) + assertEquals(instant.epochSeconds - Int.MIN_VALUE, + instant.minus(Int.MIN_VALUE, DateTimeUnit.SECOND, UTC).epochSeconds) + } + // Overflowing a LocalDateTime in input + maxValidInstant.plus(DateTimePeriod(nanoseconds = -1), UTC) + minValidInstant.plus(DateTimePeriod(nanoseconds = 1), UTC) + assertArithmeticFails { (maxValidInstant + 1.nanoseconds).plus(DateTimePeriod(nanoseconds = -2), UTC) } + assertArithmeticFails { (minValidInstant - 1.nanoseconds).plus(DateTimePeriod(nanoseconds = 2), UTC) } + // Overflowing a LocalDateTime in result + assertArithmeticFails { maxValidInstant.plus(DateTimePeriod(nanoseconds = 1), UTC) } + assertArithmeticFails { minValidInstant.plus(DateTimePeriod(nanoseconds = -1), UTC) } + // Overflowing a LocalDateTime in intermediate computations + assertArithmeticFails { maxValidInstant.plus(DateTimePeriod(days = 1, nanoseconds = -1_000_000_001), UTC) } + assertArithmeticFails { maxValidInstant.plus(DateTimePeriod(months = 1, days = -48), UTC) } + } + + @Test + fun unitArithmeticOutOfRange() { + // Instant.plus(Long, DateTimeUnit, TimeZone) + // Arithmetic overflow + for (instant in smallInstants + largeNegativeInstants + largePositiveInstants) { + assertArithmeticFails("$instant") { instant.plus(Long.MAX_VALUE, DateTimeUnit.SECOND, UTC) } + assertArithmeticFails("$instant") { instant.plus(Long.MIN_VALUE, DateTimeUnit.SECOND, UTC) } + assertArithmeticFails("$instant") { instant.plus(Long.MAX_VALUE, DateTimeUnit.YEAR, UTC) } + assertArithmeticFails("$instant") { instant.plus(Long.MIN_VALUE, DateTimeUnit.YEAR, UTC) } + } + for (instant in smallInstants) { + instant.plus(2 * Int.MAX_VALUE.toLong(), DateTimeUnit.DAY, UTC) + instant.plus(2 * Int.MIN_VALUE.toLong(), DateTimeUnit.DAY, UTC) + instant.plus(2 * Int.MAX_VALUE.toLong(), DateTimeUnit.MONTH, UTC) + instant.plus(2 * Int.MIN_VALUE.toLong(), DateTimeUnit.MONTH, UTC) + } + // Overflowing a LocalDateTime in input + maxValidInstant.plus(-1, DateTimeUnit.NANOSECOND, UTC) + minValidInstant.plus(1, DateTimeUnit.NANOSECOND, UTC) + assertArithmeticFails { (maxValidInstant + 1.nanoseconds).plus(-2, DateTimeUnit.NANOSECOND, UTC) } + assertArithmeticFails { (minValidInstant - 1.nanoseconds).plus(2, DateTimeUnit.NANOSECOND, UTC) } + // Overflowing a LocalDateTime in result + assertArithmeticFails { maxValidInstant.plus(1, DateTimeUnit.NANOSECOND, UTC) } + assertArithmeticFails { maxValidInstant.plus(1, DateTimeUnit.YEAR, UTC) } + assertArithmeticFails { minValidInstant.plus(-1, DateTimeUnit.NANOSECOND, UTC) } + assertArithmeticFails { minValidInstant.plus(-1, DateTimeUnit.YEAR, UTC) } + } + + @Test + fun timeBasedUnitArithmeticOutOfRange() { + // Instant.plus(Long, DateTimeUnit.TimeBased) + // Arithmetic overflow + for (instant in smallInstants + largeNegativeInstants + largePositiveInstants) { + assertEquals(Instant.MAX, instant.plus(Long.MAX_VALUE, DateTimeUnit.SECOND)) + assertEquals(Instant.MIN, instant.plus(Long.MIN_VALUE, DateTimeUnit.SECOND)) + } + // Overflow of Instant boundaries + for (instant in smallInstants + largeNegativeInstants + largePositiveInstants) { + assertEquals(Instant.MAX, instant.plus(Instant.MAX.epochSeconds - instant.epochSeconds + 1, DateTimeUnit.SECOND)) + assertEquals(Instant.MIN, instant.plus(Instant.MIN.epochSeconds - instant.epochSeconds - 1, DateTimeUnit.SECOND)) + } + } + + + @Test + fun periodUntilOutOfRange() { + // Instant.periodUntil + maxValidInstant.periodUntil(maxValidInstant, UTC) + assertArithmeticFails { (maxValidInstant + 1.nanoseconds).periodUntil(maxValidInstant, UTC) } + assertArithmeticFails { minValidInstant.periodUntil(minValidInstant - 1.nanoseconds, UTC) } + } + + @Test + fun unitsUntilClamping() { + // Arithmetic overflow of the resulting number + assertEquals(Long.MAX_VALUE, minValidInstant.until(maxValidInstant, DateTimeUnit.NANOSECOND, UTC)) + assertEquals(Long.MIN_VALUE, maxValidInstant.until(minValidInstant, DateTimeUnit.NANOSECOND, UTC)) + assertEquals(Long.MAX_VALUE, minValidInstant.until(maxValidInstant, DateTimeUnit.NANOSECOND)) + assertEquals(Long.MIN_VALUE, maxValidInstant.until(minValidInstant, DateTimeUnit.NANOSECOND)) + } + + @Test + fun unitsUntilOutOfRange() { + // Instant.until + // Overflowing a LocalDateTime in input + assertArithmeticFails { (maxValidInstant + 1.nanoseconds).until(maxValidInstant, DateTimeUnit.NANOSECOND, UTC) } + assertArithmeticFails { maxValidInstant.until(maxValidInstant + 1.nanoseconds, DateTimeUnit.NANOSECOND, UTC) } + // Overloads without a TimeZone should not fail on overflowing a LocalDateTime + (maxValidInstant + 1.nanoseconds).until(maxValidInstant, DateTimeUnit.NANOSECOND) + maxValidInstant.until(maxValidInstant + 1.nanoseconds, DateTimeUnit.NANOSECOND) + } + + // https://github.com/Kotlin/kotlinx-datetime/issues/263 + @Test + fun addSmallDurationsToLargeInstants() { + for (smallDuration in listOf(1.nanoseconds, 999_999.nanoseconds, 1.seconds - 1.nanoseconds)) { + assertEquals(expected = Instant.MAX, actual = Instant.MAX + smallDuration) + assertEquals(expected = Instant.MIN, actual = Instant.MIN - smallDuration) + } + } + + @Test + fun subtractInstants() { + val max = Instant.fromEpochSeconds(31494816403199L) + val min = Instant.fromEpochSeconds(-31619119219200L) + assertEquals(max.epochSeconds - min.epochSeconds, (max - min).inWholeSeconds) + } +} diff --git a/core/common/test/InstantTest.kt b/core/common/test/InstantTest.kt index 8bc41f019..082c2f30e 100644 --- a/core/common/test/InstantTest.kt +++ b/core/common/test/InstantTest.kt @@ -16,6 +16,10 @@ import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.Duration.Companion.seconds +import kotlin.time.Clock +import kotlin.time.Instant +import kotlin.time.isDistantFuture +import kotlin.time.isDistantPast class InstantTest { @@ -59,99 +63,6 @@ class InstantTest { println(now.toLocalDateTime(TimeZone.currentSystemDefault())) } - /* Based on the ThreeTenBp project. - * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos - */ - @Test - fun parseIsoString() { - val instants = arrayOf( - Triple("1970-01-01T00:00:00Z", 0, 0), - Triple("1970-01-01t00:00:00Z", 0, 0), - Triple("1970-01-01T00:00:00z", 0, 0), - Triple("1970-01-01T00:00:00.0Z", 0, 0), - Triple("1970-01-01T00:00:00.000000000Z", 0, 0), - Triple("1970-01-01T00:00:00.000000001Z", 0, 1), - Triple("1970-01-01T00:00:00.100000000Z", 0, 100000000), - Triple("1970-01-01T00:00:01Z", 1, 0), - Triple("1970-01-01T00:01:00Z", 60, 0), - Triple("1970-01-01T00:01:01Z", 61, 0), - Triple("1970-01-01T00:01:01.000000001Z", 61, 1), - Triple("1970-01-01T01:00:00.000000000Z", 3600, 0), - Triple("1970-01-01T01:01:01.000000001Z", 3661, 1), - Triple("1970-01-02T01:01:01.100000000Z", 90061, 100000000)) - instants.forEach { - val (str, seconds, nanos) = it - val instant = Instant.parse(str) - assertEquals(seconds.toLong() * 1000 + nanos / 1000000, instant.toEpochMilliseconds()) - } - - assertInvalidFormat { Instant.parse("1970-01-01T23:59:60Z")} - assertInvalidFormat { Instant.parse("1970-01-01T24:00:00Z")} - assertInvalidFormat { Instant.parse("1970-01-01T23:59Z")} - assertInvalidFormat { Instant.parse("x") } - assertInvalidFormat { Instant.parse("12020-12-31T23:59:59.000000000Z") } - // this string represents an Instant that is currently larger than Instant.MAX any of the implementations: - assertInvalidFormat { Instant.parse("+1000000001-12-31T23:59:59.000000000Z") } - } - - @Test - fun parseStringsWithOffsets() { - val strings = arrayOf( - Pair("2020-01-01T00:01:01.02+18:00", "2019-12-31T06:01:01.020Z"), - Pair("2020-01-01T00:01:01.123456789-17:59:59", "2020-01-01T18:01:00.123456789Z"), - Pair("2020-01-01T00:01:01.010203040+17:59:59", "2019-12-31T06:01:02.010203040Z"), - Pair("2020-01-01T00:01:01.010203040+17:59", "2019-12-31T06:02:01.010203040Z"), - Pair("2020-01-01T00:01:01+00", "2020-01-01T00:01:01Z"), - ) - strings.forEach { (str, strInZ) -> - val instant = Instant.parse(str) - assertEquals(Instant.parse(strInZ), instant, str) - assertEquals(strInZ, instant.toString(), str) - } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+18:01") } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+1801") } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+0") } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+") } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01") } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+000000") } - - val instants = listOf( - Instant.DISTANT_FUTURE, - Instant.DISTANT_PAST, - Instant.fromEpochSeconds(0, 0)) - - val offsetStrings = listOf( - "Z", - "+03:12:14", - "-03:12:14", - "+02:35", - "-02:35", - "+04", - "-04", - ) - - val offsetFormat = UtcOffset.Format { - optional("Z") { - offsetHours() - optional { - char(':'); offsetMinutesOfHour() - optional { char(':'); offsetSecondsOfMinute() } - } - } - } - val offsets = offsetStrings.map { UtcOffset.parse(it, offsetFormat) } - - for (instant in instants) { - for (offsetIx in offsets.indices) { - val str = instant.format(DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET, offsets[offsetIx]) - val offsetString = offsets[offsetIx].toString() - assertEquals(offsetString, offsetString.commonSuffixWith(str)) - assertEquals(instant, Instant.parse(str, DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET)) - assertEquals(instant, Instant.parse(str)) - } - } - } - @Test fun instantCalendarArithmetic() { val zone = TimeZone.of("Europe/Berlin") @@ -428,9 +339,9 @@ class InstantTest { val distantFutureString = "+100000-01-01T00:00:00Z" val distantPastString = "-100001-12-31T23:59:59.999999999Z" assertEquals(distantFutureString, Instant.DISTANT_FUTURE.toString()) - assertEquals(Instant.DISTANT_FUTURE, distantFutureString.toInstant()) + assertEquals(Instant.DISTANT_FUTURE, distantFutureString.let(Instant::parse)) assertEquals(distantPastString, Instant.DISTANT_PAST.toString()) - assertEquals(Instant.DISTANT_PAST, distantPastString.toInstant()) + assertEquals(Instant.DISTANT_PAST, distantPastString.let(Instant::parse)) assertTrue(Instant.DISTANT_PAST.isDistantPast) assertTrue(Instant.DISTANT_FUTURE.isDistantFuture) assertFalse(Instant.DISTANT_PAST.isDistantFuture) @@ -638,3 +549,9 @@ class InstantRangeTest { assertEquals(max.epochSeconds - min.epochSeconds, (max - min).inWholeSeconds) } } + +private val maxInstant = Instant.fromEpochSeconds(Long.MAX_VALUE) +private val minInstant = Instant.fromEpochSeconds(Long.MIN_VALUE) + +internal val Instant.Companion.MAX get() = maxInstant +internal val Instant.Companion.MIN get() = minInstant diff --git a/core/common/test/LocalDateTest.kt b/core/common/test/LocalDateTest.kt index f6ded203e..60f4f841b 100644 --- a/core/common/test/LocalDateTest.kt +++ b/core/common/test/LocalDateTest.kt @@ -7,6 +7,7 @@ package kotlinx.datetime.test import kotlinx.datetime.* import kotlinx.datetime.internal.* +import kotlin.time.Clock import kotlin.random.* import kotlin.test.* diff --git a/core/common/test/LocalDateTimeTest.kt b/core/common/test/LocalDateTimeTest.kt index fff490a30..07aed3d80 100644 --- a/core/common/test/LocalDateTimeTest.kt +++ b/core/common/test/LocalDateTimeTest.kt @@ -6,7 +6,8 @@ package kotlinx.datetime.test import kotlinx.datetime.* -import kotlinx.datetime.Clock +import kotlin.time.Clock +import kotlin.time.Instant import kotlin.test.* import kotlin.time.* import kotlin.time.Duration.Companion.hours @@ -50,8 +51,8 @@ class LocalDateTimeTest { val diff = with(TimeZone.UTC) { ldt2.toInstant() - ldt1.toInstant() } assertEquals(with(Duration) { 1.hours + 7.minutes - 15.seconds + 400100.microseconds }, diff) - assertFailsWith { (Instant.MAX - 3.days).toLocalDateTime(TimeZone.UTC) } - assertFailsWith { (Instant.MIN + 6.hours).toLocalDateTime(TimeZone.UTC) } + assertFailsWith { (Instant.fromEpochSeconds(Long.MAX_VALUE) - 3.days).toLocalDateTime(TimeZone.UTC) } + assertFailsWith { (Instant.fromEpochSeconds(Long.MIN_VALUE) + 6.hours).toLocalDateTime(TimeZone.UTC) } } @Test @@ -67,7 +68,7 @@ class LocalDateTimeTest { val instant = Instant.parse("2019-10-01T18:43:15.100500Z") val datetime = instant.toLocalDateTime(TimeZone.UTC) checkComponents(datetime, 2019, 10, 1, 18, 43, 15, 100500000) - assertFailsWith { Instant.MAX.toLocalDateTime(TimeZone.UTC) } + assertFailsWith { Instant.fromEpochSeconds(Long.MAX_VALUE).toLocalDateTime(TimeZone.UTC) } } @Test diff --git a/core/common/test/ReadmeTest.kt b/core/common/test/ReadmeTest.kt index a6d8636a1..880afc3f5 100644 --- a/core/common/test/ReadmeTest.kt +++ b/core/common/test/ReadmeTest.kt @@ -9,6 +9,8 @@ import kotlinx.datetime.* import kotlinx.datetime.format.* import kotlin.test.* import kotlin.time.* +import kotlin.time.Clock +import kotlin.time.Instant /** * Tests the code snippets in the README.md file. diff --git a/core/common/test/TimeZoneTest.kt b/core/common/test/TimeZoneTest.kt index 24c6d9e87..027e25e6a 100644 --- a/core/common/test/TimeZoneTest.kt +++ b/core/common/test/TimeZoneTest.kt @@ -9,6 +9,8 @@ package kotlinx.datetime.test import kotlinx.datetime.* import kotlin.test.* +import kotlin.time.Clock +import kotlin.time.Instant class TimeZoneTest { diff --git a/core/common/test/format/DateTimeComponentsFormatTest.kt b/core/common/test/format/DateTimeComponentsFormatTest.kt index 86d2c3a81..79a085691 100644 --- a/core/common/test/format/DateTimeComponentsFormatTest.kt +++ b/core/common/test/format/DateTimeComponentsFormatTest.kt @@ -10,6 +10,7 @@ import kotlinx.datetime.format.* import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty import kotlin.test.* +import kotlin.time.Instant class DateTimeComponentsFormatTest { @Test diff --git a/core/common/test/format/DateTimeComponentsTest.kt b/core/common/test/format/DateTimeComponentsTest.kt index 8575c601b..3c9e3859b 100644 --- a/core/common/test/format/DateTimeComponentsTest.kt +++ b/core/common/test/format/DateTimeComponentsTest.kt @@ -8,6 +8,7 @@ package kotlinx.datetime.test.format import kotlinx.datetime.* import kotlinx.datetime.format.* import kotlin.test.* +import kotlin.time.Clock class DateTimeComponentsTest { @Test diff --git a/core/common/test/samples/ClockSamples.kt b/core/common/test/samples/ClockSamples.kt index 70683efa5..278fa9e0f 100644 --- a/core/common/test/samples/ClockSamples.kt +++ b/core/common/test/samples/ClockSamples.kt @@ -9,6 +9,8 @@ import kotlinx.datetime.* import kotlin.test.* import kotlin.time.Duration.Companion.seconds import kotlin.time.TestTimeSource +import kotlin.time.Clock +import kotlin.time.Instant class ClockSamples { @Test diff --git a/core/common/test/samples/DayOfWeekSamples.kt b/core/common/test/samples/DayOfWeekSamples.kt index c8393ae98..4c491fcc1 100644 --- a/core/common/test/samples/DayOfWeekSamples.kt +++ b/core/common/test/samples/DayOfWeekSamples.kt @@ -7,6 +7,7 @@ package kotlinx.datetime.test.samples import kotlinx.datetime.* import kotlin.test.* +import kotlin.time.Clock class DayOfWeekSamples { diff --git a/core/common/test/samples/InstantSamples.kt b/core/common/test/samples/InstantSamples.kt index 8fec354b7..cbb574354 100644 --- a/core/common/test/samples/InstantSamples.kt +++ b/core/common/test/samples/InstantSamples.kt @@ -10,6 +10,10 @@ import kotlinx.datetime.format.* import kotlin.random.* import kotlin.test.* import kotlin.time.Duration.Companion.hours +import kotlin.time.Clock +import kotlin.time.Instant +import kotlin.time.isDistantFuture +import kotlin.time.isDistantPast class InstantSamples { diff --git a/core/common/test/samples/MonthSamples.kt b/core/common/test/samples/MonthSamples.kt index 70128845a..a2ab2edd1 100644 --- a/core/common/test/samples/MonthSamples.kt +++ b/core/common/test/samples/MonthSamples.kt @@ -7,6 +7,7 @@ package kotlinx.datetime.test.samples import kotlinx.datetime.* import kotlin.test.* +import kotlin.time.Clock class MonthSamples { diff --git a/core/common/test/samples/TimeZoneSamples.kt b/core/common/test/samples/TimeZoneSamples.kt index 71ef13fb6..a777f4d98 100644 --- a/core/common/test/samples/TimeZoneSamples.kt +++ b/core/common/test/samples/TimeZoneSamples.kt @@ -8,6 +8,8 @@ package kotlinx.datetime.test.samples import kotlinx.datetime.* import kotlinx.datetime.format.* import kotlin.test.* +import kotlin.time.Instant +import kotlin.time.Clock class TimeZoneSamples { diff --git a/core/common/test/samples/format/DateTimeComponentsSamples.kt b/core/common/test/samples/format/DateTimeComponentsSamples.kt index fbfd5d19c..3c2939e0c 100644 --- a/core/common/test/samples/format/DateTimeComponentsSamples.kt +++ b/core/common/test/samples/format/DateTimeComponentsSamples.kt @@ -8,6 +8,7 @@ package kotlinx.datetime.test.samples.format import kotlinx.datetime.* import kotlinx.datetime.format.* import kotlin.test.* +import kotlin.time.Instant class DateTimeComponentsSamples { diff --git a/core/commonJs/src/internal/Platform.kt b/core/commonJs/src/internal/Platform.kt index c5ba347b7..940777826 100644 --- a/core/commonJs/src/internal/Platform.kt +++ b/core/commonJs/src/internal/Platform.kt @@ -8,6 +8,7 @@ package kotlinx.datetime.internal import kotlinx.datetime.* import kotlinx.datetime.UtcOffset import kotlinx.datetime.internal.JSJoda.ZoneId +import kotlin.time.Instant private val tzdb: Result = runCatching { /** @@ -139,10 +140,7 @@ internal fun rulesForId(zoneId: String): TimeZoneRules? = tzdb.getOrThrow()?.rul internal actual fun getAvailableZoneIds(): Set = tzdb.getOrThrow()?.availableTimeZoneIds() ?: setOf("UTC") -internal actual fun currentTime(): Instant = Instant.fromEpochMilliseconds(Date().getTime().toLong()) - internal external class Date() { constructor(milliseconds: Double) - fun getTime(): Double fun getTimezoneOffset(): Double } diff --git a/core/commonJs/test/JsJodaTimezoneTest.kt b/core/commonJs/test/JsJodaTimezoneTest.kt index 138ed28ac..db316b779 100644 --- a/core/commonJs/test/JsJodaTimezoneTest.kt +++ b/core/commonJs/test/JsJodaTimezoneTest.kt @@ -12,6 +12,7 @@ import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds import kotlinx.datetime.test.JSJoda.Instant as jtInstant import kotlinx.datetime.test.JSJoda.ZoneId as jtZoneId +import kotlin.time.Instant class JsJodaTimezoneTest { @Test diff --git a/core/commonKotlin/src/DeprecatedInstant.kt b/core/commonKotlin/src/DeprecatedInstant.kt new file mode 100644 index 000000000..30d73cce9 --- /dev/null +++ b/core/commonKotlin/src/DeprecatedInstant.kt @@ -0,0 +1,355 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +@file:Suppress("DEPRECATION") +@file:JvmMultifileClass +@file:JvmName("InstantKt") +package kotlinx.datetime + +import kotlinx.datetime.format.* +import kotlinx.datetime.internal.* +import kotlinx.datetime.internal.MILLIS_PER_ONE +import kotlinx.datetime.internal.NANOS_PER_MILLI +import kotlinx.datetime.internal.NANOS_PER_ONE +import kotlinx.datetime.internal.safeAdd +import kotlinx.datetime.internal.safeMultiply +import kotlinx.datetime.serializers.InstantIso8601Serializer +import kotlinx.serialization.Serializable +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName +import kotlin.time.Duration +import kotlin.time.Duration.Companion.nanoseconds +import kotlin.time.Duration.Companion.seconds + +/** + * The minimum supported epoch second. + */ +private const val MIN_SECOND = -31557014167219200L // -1000000000-01-01T00:00:00Z + +/** + * The maximum supported epoch second. + */ +private const val MAX_SECOND = 31556889864403199L // +1000000000-12-31T23:59:59Z + +@Deprecated( + "Use kotlin.time.Instant instead", + ReplaceWith("kotlin.time.Instant", "kotlin.time.Instant"), + level = DeprecationLevel.WARNING +) +@Serializable(with = InstantIso8601Serializer::class) +public actual class Instant internal constructor( + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().epochSeconds") + ) + public actual val epochSeconds: Long, + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().nanosecondsOfSecond") + ) + public actual val nanosecondsOfSecond: Int +) : Comparable { + + init { + require(epochSeconds in MIN_SECOND..MAX_SECOND) { "Instant exceeds minimum or maximum instant" } + } + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().nanosecondsOfSecond") + ) + // org.threeten.bp.Instant#toEpochMilli + public actual fun toEpochMilliseconds(): Long = try { + if (epochSeconds >= 0) { + val millis = safeMultiply(epochSeconds, MILLIS_PER_ONE.toLong()) + safeAdd(millis, (nanosecondsOfSecond / NANOS_PER_MILLI).toLong()) + } else { + // prevent an overflow in seconds * 1000 + // instead of going form the second farther away from 0 + // going toward 0 + // we go from the second closer to 0 away from 0 + // that way we always stay in the valid long range + // seconds + 1 can not overflow because it is negative + val millis = safeMultiply(epochSeconds + 1, MILLIS_PER_ONE.toLong()) + safeAdd(millis, (nanosecondsOfSecond / NANOS_PER_MILLI - MILLIS_PER_ONE).toLong()) + } + } catch (_: ArithmeticException) { + if (epochSeconds > 0) Long.MAX_VALUE else Long.MIN_VALUE + } + + // org.threeten.bp.Instant#plus(long, long) + /** + * @throws ArithmeticException if arithmetic overflow occurs + * @throws IllegalArgumentException if the boundaries of Instant are overflown + */ + internal fun plus(secondsToAdd: Long, nanosToAdd: Long): Instant { + if ((secondsToAdd or nanosToAdd) == 0L) { + return this + } + val newEpochSeconds: Long = safeAdd(safeAdd(epochSeconds, secondsToAdd), (nanosToAdd / NANOS_PER_ONE)) + val newNanosToAdd = nanosToAdd % NANOS_PER_ONE + val nanoAdjustment = (nanosecondsOfSecond + newNanosToAdd) // safe int+NANOS_PER_ONE + return fromEpochSecondsThrowing(newEpochSeconds, nanoAdjustment) + } + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("(this.toStdlibInstant() + duration).toDeprecatedInstant()") + ) + public actual operator fun plus(duration: Duration): Instant = duration.toComponents { secondsToAdd, nanosecondsToAdd -> + try { + plus(secondsToAdd, nanosecondsToAdd.toLong()) + } catch (_: IllegalArgumentException) { + if (duration.isPositive()) MAX else MIN + } catch (_: ArithmeticException) { + if (duration.isPositive()) MAX else MIN + } + } + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("(this.toStdlibInstant() - duration).toDeprecatedInstant()") + ) + public actual operator fun minus(duration: Duration): Instant = plus(-duration) + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant() - other.toStdlibInstant()") + ) + public actual operator fun minus(other: Instant): Duration = + (this.epochSeconds - other.epochSeconds).seconds + // won't overflow given the instant bounds + (this.nanosecondsOfSecond - other.nanosecondsOfSecond).nanoseconds + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().compareTo(other.toStdlibInstant())") + ) + actual override fun compareTo(other: Instant): Int { + val s = epochSeconds.compareTo(other.epochSeconds) + if (s != 0) { + return s + } + return nanosecondsOfSecond.compareTo(other.nanosecondsOfSecond) + } + + override fun equals(other: Any?): Boolean = + this === other || other is Instant && this.epochSeconds == other.epochSeconds && this.nanosecondsOfSecond == other.nanosecondsOfSecond + + // org.threeten.bp.Instant#hashCode + override fun hashCode(): Int = + (epochSeconds xor (epochSeconds ushr 32)).toInt() + 51 * nanosecondsOfSecond + + @Suppress("POTENTIALLY_NON_REPORTED_ANNOTATION") + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().toString()") + ) + // org.threeten.bp.format.DateTimeFormatterBuilder.InstantPrinterParser#print + actual override fun toString(): String = format(ISO_DATE_TIME_OFFSET_WITH_TRAILING_ZEROS) + + public actual companion object { + internal actual val MIN = Instant(MIN_SECOND, 0) + internal actual val MAX = Instant(MAX_SECOND, 999_999_999) + + @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlin.time.Clock"), level = DeprecationLevel.ERROR) + public actual fun now(): Instant = Clock.System.now() + + // org.threeten.bp.Instant#ofEpochMilli + public actual fun fromEpochMilliseconds(epochMilliseconds: Long): Instant { + val epochSeconds = epochMilliseconds.floorDiv(MILLIS_PER_ONE.toLong()) + val nanosecondsOfSecond = (epochMilliseconds.mod(MILLIS_PER_ONE.toLong()) * NANOS_PER_MILLI).toInt() + return when { + epochSeconds < MIN_SECOND -> MIN + epochSeconds > MAX_SECOND -> MAX + else -> fromEpochSeconds(epochSeconds, nanosecondsOfSecond) + } + } + + /** + * @throws ArithmeticException if arithmetic overflow occurs + * @throws IllegalArgumentException if the boundaries of Instant are overflown + */ + private fun fromEpochSecondsThrowing(epochSeconds: Long, nanosecondAdjustment: Long): Instant { + val secs = safeAdd(epochSeconds, nanosecondAdjustment.floorDiv(NANOS_PER_ONE.toLong())) + val nos = nanosecondAdjustment.mod(NANOS_PER_ONE.toLong()).toInt() + return Instant(secs, nos) + } + + // org.threeten.bp.Instant#ofEpochSecond(long, long) + public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long): Instant = + try { + fromEpochSecondsThrowing(epochSeconds, nanosecondAdjustment) + } catch (_: ArithmeticException) { + if (epochSeconds > 0) MAX else MIN + } catch (_: IllegalArgumentException) { + if (epochSeconds > 0) MAX else MIN + } + + public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant = + fromEpochSeconds(epochSeconds, nanosecondAdjustment.toLong()) + + public actual fun parse(input: CharSequence, format: DateTimeFormat): Instant = try { + format.parse(input).toInstantUsingOffset().toDeprecatedInstant() + } catch (e: IllegalArgumentException) { + throw DateTimeFormatException("Failed to parse an instant from '$input'", e) + } + + @Deprecated("This overload is only kept for binary compatibility", level = DeprecationLevel.HIDDEN) + public fun parse(isoString: String): Instant = parse(input = isoString) + + public actual val DISTANT_PAST: Instant = fromEpochSeconds(DISTANT_PAST_SECONDS, 999_999_999) + + public actual val DISTANT_FUTURE: Instant = fromEpochSeconds(DISTANT_FUTURE_SECONDS, 0) + } + +} + +private fun Instant.toZonedDateTimeFailing(zone: TimeZone): ZonedDateTime = try { + toZonedDateTime(zone) +} catch (e: IllegalArgumentException) { + throw DateTimeArithmeticException("Can not convert instant $this to LocalDateTime to perform computations", e) +} + +/** + * @throws IllegalArgumentException if the [Instant] exceeds the boundaries of [LocalDateTime] + */ +private fun Instant.toZonedDateTime(zone: TimeZone): ZonedDateTime { + val currentOffset = zone.offsetAtImpl(this.toStdlibInstant()) + return ZonedDateTime(toStdlibInstant().toLocalDateTimeImpl(currentOffset), zone, currentOffset) +} + +/** Check that [Instant] fits in [ZonedDateTime]. + * This is done on the results of computations for consistency with other platforms. + */ +private fun Instant.check(zone: TimeZone): Instant = this@check.also { + toZonedDateTimeFailing(zone) +} + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(period, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant = try { + with(period) { + val withDate = toZonedDateTimeFailing(timeZone) + .run { if (totalMonths != 0L) plus(totalMonths, DateTimeUnit.MONTH) else this } + .run { if (days != 0) plus(days.toLong(), DateTimeUnit.DAY) else this } + withDate.toDeprecatedInstant() + .run { if (totalNanoseconds != 0L) plus(0, totalNanoseconds).check(timeZone) else this } + }.check(timeZone) +} catch (e: ArithmeticException) { + throw DateTimeArithmeticException("Arithmetic overflow when adding CalendarPeriod to an Instant", e) +} catch (e: IllegalArgumentException) { + throw DateTimeArithmeticException("Boundaries of Instant exceeded when adding CalendarPeriod", e) +} + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(1, unit, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.plus(unit: DateTimeUnit, timeZone: TimeZone): Instant = + plus(1L, unit, timeZone) +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.plus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant = + plus(value.toLong(), unit, timeZone) +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(value, unit, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.minus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant = + plus(-value.toLong(), unit, timeZone) +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant = try { + when (unit) { + is DateTimeUnit.DateBased -> + toZonedDateTimeFailing(timeZone).plus(value, unit).toDeprecatedInstant() + is DateTimeUnit.TimeBased -> + check(timeZone).plus(value, unit).check(timeZone) + } +} catch (e: ArithmeticException) { + throw DateTimeArithmeticException("Arithmetic overflow when adding to an Instant", e) +} catch (e: IllegalArgumentException) { + throw DateTimeArithmeticException("Boundaries of Instant exceeded when adding a value", e) +} + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit).toDeprecatedInstant()") +) +public actual fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Instant = + try { + multiplyAndDivide(value, unit.nanoseconds, NANOS_PER_ONE.toLong()).let { (seconds, nanoseconds) -> + plus(seconds, nanoseconds) + } + } catch (_: ArithmeticException) { + if (value > 0) Instant.MAX else Instant.MIN + } catch (_: IllegalArgumentException) { + if (value > 0) Instant.MAX else Instant.MIN + } + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().periodUntil(other.toStdlibInstant(), timeZone)") +) +public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod { + var thisLdt = toZonedDateTimeFailing(timeZone) + val otherLdt = other.toZonedDateTimeFailing(timeZone) + + val months = thisLdt.until(otherLdt, DateTimeUnit.MONTH) // `until` on dates never fails + thisLdt = thisLdt.plus(months, DateTimeUnit.MONTH) // won't throw: thisLdt + months <= otherLdt, which is known to be valid + val days = thisLdt.until(otherLdt, DateTimeUnit.DAY) // `until` on dates never fails + thisLdt = thisLdt.plus(days, DateTimeUnit.DAY) // won't throw: thisLdt + days <= otherLdt + val nanoseconds = thisLdt.until(otherLdt, DateTimeUnit.NANOSECOND) // |otherLdt - thisLdt| < 24h + + return buildDateTimePeriod(months, days.toInt(), nanoseconds) +} + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().until(other.toStdlibInstant(), unit, timeZone)") +) +public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long = + when (unit) { + is DateTimeUnit.DateBased -> + toZonedDateTimeFailing(timeZone).dateTime.until(other.toZonedDateTimeFailing(timeZone).dateTime, unit) + .toLong() + is DateTimeUnit.TimeBased -> { + check(timeZone); other.check(timeZone) + until(other, unit) + } + } + +private fun ZonedDateTime.toDeprecatedInstant(): Instant = + Instant.fromEpochSeconds(dateTime.toEpochSecond(offset), dateTime.nanosecond) + +private val ISO_DATE_TIME_OFFSET_WITH_TRAILING_ZEROS = DateTimeComponents.Format { + date(ISO_DATE) + alternativeParsing({ + char('t') + }) { + char('T') + } + hour() + char(':') + minute() + char(':') + second() + optional { + char('.') + secondFractionInternal(1, 9, FractionalSecondDirective.GROUP_BY_THREE) + } + isoOffset( + zOnZero = true, + useSeparator = true, + outputMinute = WhenToOutput.IF_NONZERO, + outputSecond = WhenToOutput.IF_NONZERO + ) +} diff --git a/core/commonKotlin/src/Instant.kt b/core/commonKotlin/src/Instant.kt index b4cff744c..bcb78979b 100644 --- a/core/commonKotlin/src/Instant.kt +++ b/core/commonKotlin/src/Instant.kt @@ -8,152 +8,11 @@ package kotlinx.datetime -import kotlinx.datetime.format.* +import kotlin.time.Instant import kotlinx.datetime.internal.* -import kotlinx.datetime.serializers.InstantIso8601Serializer -import kotlinx.serialization.Serializable -import kotlin.time.* import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.Duration.Companion.seconds -/** - * The minimum supported epoch second. - */ -private const val MIN_SECOND = -31557014167219200L // -1000000000-01-01T00:00:00Z - -/** - * The maximum supported epoch second. - */ -private const val MAX_SECOND = 31556889864403199L // +1000000000-12-31T23:59:59 - -@Serializable(with = InstantIso8601Serializer::class) -public actual class Instant internal constructor(public actual val epochSeconds: Long, public actual val nanosecondsOfSecond: Int) : Comparable { - - init { - require(epochSeconds in MIN_SECOND..MAX_SECOND) { "Instant exceeds minimum or maximum instant" } - } - - // org.threeten.bp.Instant#toEpochMilli - public actual fun toEpochMilliseconds(): Long = try { - if (epochSeconds >= 0) { - val millis = safeMultiply(epochSeconds, MILLIS_PER_ONE.toLong()) - safeAdd(millis, (nanosecondsOfSecond / NANOS_PER_MILLI).toLong()) - } else { - // prevent an overflow in seconds * 1000 - // instead of going form the second farther away from 0 - // going toward 0 - // we go from the second closer to 0 away from 0 - // that way we always stay in the valid long range - // seconds + 1 can not overflow because it is negative - val millis = safeMultiply(epochSeconds + 1, MILLIS_PER_ONE.toLong()) - safeAdd(millis, (nanosecondsOfSecond / NANOS_PER_MILLI - MILLIS_PER_ONE).toLong()) - } - } catch (_: ArithmeticException) { - if (epochSeconds > 0) Long.MAX_VALUE else Long.MIN_VALUE - } - - // org.threeten.bp.Instant#plus(long, long) - /** - * @throws ArithmeticException if arithmetic overflow occurs - * @throws IllegalArgumentException if the boundaries of Instant are overflown - */ - internal fun plus(secondsToAdd: Long, nanosToAdd: Long): Instant { - if ((secondsToAdd or nanosToAdd) == 0L) { - return this - } - val newEpochSeconds: Long = safeAdd(safeAdd(epochSeconds, secondsToAdd), (nanosToAdd / NANOS_PER_ONE)) - val newNanosToAdd = nanosToAdd % NANOS_PER_ONE - val nanoAdjustment = (nanosecondsOfSecond + newNanosToAdd) // safe int+NANOS_PER_ONE - return fromEpochSecondsThrowing(newEpochSeconds, nanoAdjustment) - } - - public actual operator fun plus(duration: Duration): Instant = duration.toComponents { secondsToAdd, nanosecondsToAdd -> - try { - plus(secondsToAdd, nanosecondsToAdd.toLong()) - } catch (_: IllegalArgumentException) { - if (duration.isPositive()) MAX else MIN - } catch (_: ArithmeticException) { - if (duration.isPositive()) MAX else MIN - } - } - - public actual operator fun minus(duration: Duration): Instant = plus(-duration) - - public actual operator fun minus(other: Instant): Duration = - (this.epochSeconds - other.epochSeconds).seconds + // won't overflow given the instant bounds - (this.nanosecondsOfSecond - other.nanosecondsOfSecond).nanoseconds - - actual override fun compareTo(other: Instant): Int { - val s = epochSeconds.compareTo(other.epochSeconds) - if (s != 0) { - return s - } - return nanosecondsOfSecond.compareTo(other.nanosecondsOfSecond) - } - - override fun equals(other: Any?): Boolean = - this === other || other is Instant && this.epochSeconds == other.epochSeconds && this.nanosecondsOfSecond == other.nanosecondsOfSecond - - // org.threeten.bp.Instant#hashCode - override fun hashCode(): Int = - (epochSeconds xor (epochSeconds ushr 32)).toInt() + 51 * nanosecondsOfSecond - - // org.threeten.bp.format.DateTimeFormatterBuilder.InstantPrinterParser#print - actual override fun toString(): String = format(ISO_DATE_TIME_OFFSET_WITH_TRAILING_ZEROS) - - public actual companion object { - internal actual val MIN = Instant(MIN_SECOND, 0) - internal actual val MAX = Instant(MAX_SECOND, 999_999_999) - - @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.datetime.Clock"), level = DeprecationLevel.ERROR) - public actual fun now(): Instant = currentTime() - - // org.threeten.bp.Instant#ofEpochMilli - public actual fun fromEpochMilliseconds(epochMilliseconds: Long): Instant { - val epochSeconds = epochMilliseconds.floorDiv(MILLIS_PER_ONE.toLong()) - val nanosecondsOfSecond = (epochMilliseconds.mod(MILLIS_PER_ONE.toLong()) * NANOS_PER_MILLI).toInt() - return fromEpochSeconds(epochSeconds, nanosecondsOfSecond) - } - - /** - * @throws ArithmeticException if arithmetic overflow occurs - * @throws IllegalArgumentException if the boundaries of Instant are overflown - */ - private fun fromEpochSecondsThrowing(epochSeconds: Long, nanosecondAdjustment: Long): Instant { - val secs = safeAdd(epochSeconds, nanosecondAdjustment.floorDiv(NANOS_PER_ONE.toLong())) - val nos = nanosecondAdjustment.mod(NANOS_PER_ONE.toLong()).toInt() - return Instant(secs, nos) - } - - // org.threeten.bp.Instant#ofEpochSecond(long, long) - public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long): Instant = - try { - fromEpochSecondsThrowing(epochSeconds, nanosecondAdjustment) - } catch (_: ArithmeticException) { - if (epochSeconds > 0) MAX else MIN - } catch (_: IllegalArgumentException) { - if (epochSeconds > 0) MAX else MIN - } - - public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant = - fromEpochSeconds(epochSeconds, nanosecondAdjustment.toLong()) - - public actual fun parse(input: CharSequence, format: DateTimeFormat): Instant = try { - format.parse(input).toInstantUsingOffset() - } catch (e: IllegalArgumentException) { - throw DateTimeFormatException("Failed to parse an instant from '$input'", e) - } - - @Deprecated("This overload is only kept for binary compatibility", level = DeprecationLevel.HIDDEN) - public fun parse(isoString: String): Instant = parse(input = isoString) - - public actual val DISTANT_PAST: Instant = fromEpochSeconds(DISTANT_PAST_SECONDS, 999_999_999) - - public actual val DISTANT_FUTURE: Instant = fromEpochSeconds(DISTANT_FUTURE_SECONDS, 0) - } - -} - private fun Instant.toZonedDateTimeFailing(zone: TimeZone): ZonedDateTime = try { toZonedDateTime(zone) } catch (e: IllegalArgumentException) { @@ -181,7 +40,14 @@ public actual fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Inst .run { if (totalMonths != 0L) plus(totalMonths, DateTimeUnit.MONTH) else this } .run { if (days != 0) plus(days.toLong(), DateTimeUnit.DAY) else this } withDate.toInstant() - .run { if (totalNanoseconds != 0L) plus(0, totalNanoseconds).check(timeZone) else this } + .run { + if (totalNanoseconds != 0L) + // we don't add nanoseconds directly, as `totalNanoseconds.nanoseconds` can hit `Duration.INFINITE` + plus((totalNanoseconds / NANOS_PER_ONE).seconds) + .plus((totalNanoseconds % NANOS_PER_ONE).nanoseconds) + .check(timeZone) + else this + } }.check(timeZone) } catch (e: ArithmeticException) { throw DateTimeArithmeticException("Arithmetic overflow when adding CalendarPeriod to an Instant", e) @@ -212,12 +78,12 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZo public actual fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Instant = try { multiplyAndDivide(value, unit.nanoseconds, NANOS_PER_ONE.toLong()).let { (seconds, nanoseconds) -> - plus(seconds, nanoseconds) + plus(seconds.seconds).plus(nanoseconds.nanoseconds) } } catch (_: ArithmeticException) { - if (value > 0) Instant.MAX else Instant.MIN + Instant.fromEpochSeconds(if (value > 0) Long.MAX_VALUE else Long.MIN_VALUE) } catch (_: IllegalArgumentException) { - if (value > 0) Instant.MAX else Instant.MIN + Instant.fromEpochSeconds(if (value > 0) Long.MAX_VALUE else Long.MIN_VALUE) } public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod { @@ -243,27 +109,3 @@ public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: Ti until(other, unit) } } - -private val ISO_DATE_TIME_OFFSET_WITH_TRAILING_ZEROS = DateTimeComponents.Format { - date(ISO_DATE) - alternativeParsing({ - char('t') - }) { - char('T') - } - hour() - char(':') - minute() - char(':') - second() - optional { - char('.') - secondFractionInternal(1, 9, FractionalSecondDirective.GROUP_BY_THREE) - } - isoOffset( - zOnZero = true, - useSeparator = true, - outputMinute = WhenToOutput.IF_NONZERO, - outputSecond = WhenToOutput.IF_NONZERO - ) -} diff --git a/core/commonKotlin/src/TimeZone.kt b/core/commonKotlin/src/TimeZone.kt index 4aa6aa5e3..6fb495cac 100644 --- a/core/commonKotlin/src/TimeZone.kt +++ b/core/commonKotlin/src/TimeZone.kt @@ -12,6 +12,7 @@ import kotlinx.datetime.format.* import kotlinx.datetime.internal.* import kotlinx.datetime.serializers.* import kotlinx.serialization.Serializable +import kotlin.time.Instant @Serializable(with = TimeZoneSerializer::class) public actual open class TimeZone internal constructor() { @@ -80,7 +81,24 @@ public actual open class TimeZone internal constructor() { get() = error("Should be overridden") public actual fun Instant.toLocalDateTime(): LocalDateTime = instantToLocalDateTime(this) - public actual fun LocalDateTime.toInstant(): Instant = localDateTimeToInstant(this) + + @Suppress("DEPRECATION_ERROR") + public actual fun LocalDateTime.toInstant(youShallNotPass: OverloadMarker): Instant = + localDateTimeToInstant(this) + + @Suppress("DEPRECATION") + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().toLocalDateTime()") + ) + public actual fun kotlinx.datetime.Instant.toLocalDateTime(): LocalDateTime = + toStdlibInstant().toLocalDateTime() + + @PublishedApi + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") + @kotlin.internal.LowPriorityInOverloadResolution + internal actual fun LocalDateTime.toInstant(): kotlinx.datetime.Instant = + toInstant(this@TimeZone).toDeprecatedInstant() internal open fun atStartOfDay(date: LocalDate): Instant = error("Should be overridden") //value.atStartOfDay(date) internal open fun offsetAtImpl(instant: Instant): UtcOffset = error("Should be overridden") @@ -147,13 +165,16 @@ internal fun Instant.toLocalDateTimeImpl(offset: UtcOffset): LocalDateTime { return LocalDateTime(date, time) } -public actual fun LocalDateTime.toInstant(timeZone: TimeZone): Instant = +@Suppress("DEPRECATION_ERROR") +public actual fun LocalDateTime.toInstant(timeZone: TimeZone, youShallNotPass: OverloadMarker): Instant = timeZone.localDateTimeToInstant(this) -public actual fun LocalDateTime.toInstant(offset: UtcOffset): Instant = - Instant(this.toEpochSecond(offset), this.nanosecond) +@Suppress("DEPRECATION_ERROR") +public actual fun LocalDateTime.toInstant(offset: UtcOffset, youShallNotPass: OverloadMarker): Instant = + Instant.fromEpochSeconds(this.toEpochSecond(offset), this.nanosecond) -public actual fun LocalDate.atStartOfDayIn(timeZone: TimeZone): Instant = +@Suppress("DEPRECATION_ERROR") +public actual fun LocalDate.atStartOfDayIn(timeZone: TimeZone, youShallNotPass: OverloadMarker): Instant = timeZone.atStartOfDay(this) private val lenientOffsetFormat = UtcOffsetFormat.build { diff --git a/core/commonKotlin/src/ZonedDateTime.kt b/core/commonKotlin/src/ZonedDateTime.kt index effd5acd0..1847d8ca4 100644 --- a/core/commonKotlin/src/ZonedDateTime.kt +++ b/core/commonKotlin/src/ZonedDateTime.kt @@ -8,6 +8,8 @@ package kotlinx.datetime +import kotlin.time.Instant + internal class ZonedDateTime(val dateTime: LocalDateTime, private val zone: TimeZone, val offset: UtcOffset) { /** * @throws IllegalArgumentException if the result exceeds the boundaries @@ -46,7 +48,7 @@ internal class ZonedDateTime(val dateTime: LocalDateTime, private val zone: Time } internal fun ZonedDateTime.toInstant(): Instant = - Instant(dateTime.toEpochSecond(offset), dateTime.nanosecond) + Instant.fromEpochSeconds(dateTime.toEpochSecond(offset), dateTime.nanosecond) // org.threeten.bp.ZonedDateTime#until diff --git a/core/commonKotlin/src/internal/MonthDayTime.kt b/core/commonKotlin/src/internal/MonthDayTime.kt index 287dc9e5f..09b47e28e 100644 --- a/core/commonKotlin/src/internal/MonthDayTime.kt +++ b/core/commonKotlin/src/internal/MonthDayTime.kt @@ -6,6 +6,7 @@ package kotlinx.datetime.internal import kotlinx.datetime.* +import kotlin.time.Instant /** * A rule expressing how to create a date in a given year. diff --git a/core/commonKotlin/src/internal/OffsetInfo.kt b/core/commonKotlin/src/internal/OffsetInfo.kt index a89946da8..c769e919d 100644 --- a/core/commonKotlin/src/internal/OffsetInfo.kt +++ b/core/commonKotlin/src/internal/OffsetInfo.kt @@ -6,6 +6,7 @@ package kotlinx.datetime.internal import kotlinx.datetime.* +import kotlin.time.Instant internal sealed interface OffsetInfo { data class Gap( @@ -43,4 +44,3 @@ internal fun OffsetInfo(transitionInstant: Instant, offsetBefore: UtcOffset, off } else { OffsetInfo.Overlap(transitionInstant, offsetBefore, offsetAfter) } - diff --git a/core/commonKotlin/src/internal/Platform.kt b/core/commonKotlin/src/internal/Platform.kt index 8227af253..d941eeded 100644 --- a/core/commonKotlin/src/internal/Platform.kt +++ b/core/commonKotlin/src/internal/Platform.kt @@ -5,7 +5,6 @@ package kotlinx.datetime.internal -import kotlinx.datetime.Instant import kotlinx.datetime.TimeZone internal expect fun timeZoneById(zoneId: String): TimeZone @@ -13,5 +12,3 @@ internal expect fun timeZoneById(zoneId: String): TimeZone internal expect fun getAvailableZoneIds(): Set internal expect fun currentSystemDefaultZone(): Pair - -internal expect fun currentTime(): Instant diff --git a/core/commonKotlin/src/internal/RegionTimeZone.kt b/core/commonKotlin/src/internal/RegionTimeZone.kt index d4e7bc6b6..335402b29 100644 --- a/core/commonKotlin/src/internal/RegionTimeZone.kt +++ b/core/commonKotlin/src/internal/RegionTimeZone.kt @@ -6,6 +6,7 @@ package kotlinx.datetime.internal import kotlinx.datetime.* +import kotlin.time.Instant internal class RegionTimeZone(private val tzid: TimeZoneRules, override val id: String) : TimeZone() { diff --git a/core/commonKotlin/src/internal/TimeZoneRules.kt b/core/commonKotlin/src/internal/TimeZoneRules.kt index 0d56aceb7..aa8332b4d 100644 --- a/core/commonKotlin/src/internal/TimeZoneRules.kt +++ b/core/commonKotlin/src/internal/TimeZoneRules.kt @@ -5,8 +5,11 @@ package kotlinx.datetime.internal -import kotlinx.datetime.* +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.UtcOffset +import kotlinx.datetime.toLocalDateTime import kotlin.math.* +import kotlin.time.Instant internal class TimeZoneRules( /** diff --git a/core/commonKotlin/test/ThreeTenBpInstantTest.kt b/core/commonKotlin/test/ThreeTenBpInstantTest.kt deleted file mode 100644 index baf538cef..000000000 --- a/core/commonKotlin/test/ThreeTenBpInstantTest.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2019-2020 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ -/* Based on the ThreeTenBp project. - * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos - */ - -package kotlinx.datetime.test - -import kotlinx.datetime.* -import kotlin.test.* - -class ThreeTenBpInstantTest { - - @Test - fun instantComparisons() { - val instants = arrayOf( - Instant.fromEpochSeconds(-2L, 0), - Instant.fromEpochSeconds(-2L, 999999998), - Instant.fromEpochSeconds(-2L, 999999999), - Instant.fromEpochSeconds(-1L, 0), - Instant.fromEpochSeconds(-1L, 1), - Instant.fromEpochSeconds(-1L, 999999998), - Instant.fromEpochSeconds(-1L, 999999999), - Instant.fromEpochSeconds(0L, 0), - Instant.fromEpochSeconds(0L, 1), - Instant.fromEpochSeconds(0L, 2), - Instant.fromEpochSeconds(0L, 999999999), - Instant.fromEpochSeconds(1L, 0), - Instant.fromEpochSeconds(2L, 0) - ) - for (i in instants.indices) { - val a = instants[i] - for (j in instants.indices) { - val b = instants[j] - when { - i < j -> { - assertTrue(a < b, "$a <=> $b") - assertNotEquals(a, b, "$a <=> $b") - } - i > j -> { - assertTrue(a > b, "$a <=> $b") - assertNotEquals(a, b, "$a <=> $b") - } - else -> { - assertEquals(0, a.compareTo(b), "$a <=> $b") - assertEquals(a, b, "$a <=> $b") - } - } - } - } - } - - @Test - fun instantEquals() { - val test5a: Instant = Instant.fromEpochSeconds(5L, 20) - val test5b: Instant = Instant.fromEpochSeconds(5L, 20) - val test5n: Instant = Instant.fromEpochSeconds(5L, 30) - val test6: Instant = Instant.fromEpochSeconds(6L, 20) - assertEquals(true, test5a == test5a) - assertEquals(true, test5a == test5b) - assertEquals(false, test5a == test5n) - assertEquals(false, test5a == test6) - assertEquals(true, test5b == test5a) - assertEquals(true, test5b == test5b) - assertEquals(false, test5b == test5n) - assertEquals(false, test5b == test6) - assertEquals(false, test5n == test5a) - assertEquals(false, test5n == test5b) - assertEquals(true, test5n == test5n) - assertEquals(false, test5n == test6) - assertEquals(false, test6 == test5a) - assertEquals(false, test6 == test5b) - assertEquals(false, test6 == test5n) - assertEquals(true, test6 == test6) - } - - @Test - fun toEpochMilliseconds() { - assertEquals(Instant.fromEpochSeconds(1L, 1000000).toEpochMilliseconds(), 1001L) - assertEquals(Instant.fromEpochSeconds(1L, 2000000).toEpochMilliseconds(), 1002L) - assertEquals(Instant.fromEpochSeconds(1L, 567).toEpochMilliseconds(), 1000L) - assertEquals(Instant.fromEpochSeconds(Long.MAX_VALUE / 1_000_000).toEpochMilliseconds(), Long.MAX_VALUE / 1_000_000 * 1000) - assertEquals(Instant.fromEpochSeconds(Long.MIN_VALUE / 1_000_000).toEpochMilliseconds(), Long.MIN_VALUE / 1_000_000 * 1000) - assertEquals(Instant.fromEpochSeconds(0L, -1000000).toEpochMilliseconds(), -1L) - assertEquals(Instant.fromEpochSeconds(0L, 1000000).toEpochMilliseconds(), 1) - assertEquals(Instant.fromEpochSeconds(0L, 999999).toEpochMilliseconds(), 0) - assertEquals(Instant.fromEpochSeconds(0L, 1).toEpochMilliseconds(), 0) - assertEquals(Instant.fromEpochSeconds(0L, 0).toEpochMilliseconds(), 0) - assertEquals(Instant.fromEpochSeconds(0L, -1).toEpochMilliseconds(), -1L) - assertEquals(Instant.fromEpochSeconds(0L, -999999).toEpochMilliseconds(), -1L) - assertEquals(Instant.fromEpochSeconds(0L, -1000000).toEpochMilliseconds(), -1L) - assertEquals(Instant.fromEpochSeconds(0L, -1000001).toEpochMilliseconds(), -2L) - } -} diff --git a/core/commonKotlin/test/TimeZoneRulesTest.kt b/core/commonKotlin/test/TimeZoneRulesTest.kt index c4666a62b..516a0fa21 100644 --- a/core/commonKotlin/test/TimeZoneRulesTest.kt +++ b/core/commonKotlin/test/TimeZoneRulesTest.kt @@ -8,6 +8,7 @@ package kotlinx.datetime.test import kotlinx.datetime.* import kotlinx.datetime.internal.* import kotlin.test.* +import kotlin.time.Instant class TimeZoneRulesTest { @Test diff --git a/core/darwin/src/Converters.kt b/core/darwin/src/Converters.kt index cf785dead..afe31a370 100644 --- a/core/darwin/src/Converters.kt +++ b/core/darwin/src/Converters.kt @@ -10,6 +10,7 @@ package kotlinx.datetime import kotlinx.cinterop.* import kotlinx.datetime.internal.NANOS_PER_ONE import platform.Foundation.* +import kotlin.time.Instant /** * Converts the [Instant] to an instance of [NSDate]. @@ -25,6 +26,10 @@ public fun Instant.toNSDate(): NSDate { return NSDate.dateWithTimeIntervalSince1970(secs) } +@PublishedApi +@Suppress("DEPRECATION_ERROR") +internal fun kotlinx.datetime.Instant.toNSDate(): NSDate = toStdlibInstant().toNSDate() + /** * Converts the [NSDate] to the corresponding [Instant]. * @@ -33,13 +38,20 @@ public fun Instant.toNSDate(): NSDate { * For example, if the [NSDate] only has millisecond or microsecond precision logically, * due to conversion artifacts in [Double] values, the result may include non-zero nanoseconds. */ -public fun NSDate.toKotlinInstant(): Instant { +@Suppress("DEPRECATION_ERROR") +public fun NSDate.toKotlinInstant(youShallNotPass: OverloadMarker = OverloadMarker.INSTANCE): Instant { val secs = timeIntervalSince1970() val fullSeconds = secs.toLong() val nanos = (secs - fullSeconds) * NANOS_PER_ONE return Instant.fromEpochSeconds(fullSeconds, nanos.toLong()) } +@PublishedApi +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") +@kotlin.internal.LowPriorityInOverloadResolution +internal fun NSDate.toKotlinInstant(): kotlinx.datetime.Instant = + toKotlinInstant().toDeprecatedInstant() + /** * Converts the [TimeZone] to [NSTimeZone]. * diff --git a/core/darwin/test/ConvertersTest.kt b/core/darwin/test/ConvertersTest.kt index 9efe07750..0ad290fa2 100644 --- a/core/darwin/test/ConvertersTest.kt +++ b/core/darwin/test/ConvertersTest.kt @@ -11,6 +11,8 @@ import platform.Foundation.* import kotlin.math.* import kotlin.random.* import kotlin.test.* +import kotlin.time.Clock +import kotlin.time.Instant class ConvertersTest { diff --git a/core/js/src/Converters.kt b/core/js/src/DeprecatedConverters.kt similarity index 95% rename from core/js/src/Converters.kt rename to core/js/src/DeprecatedConverters.kt index a4a8c9df9..7ac3e9fbd 100644 --- a/core/js/src/Converters.kt +++ b/core/js/src/DeprecatedConverters.kt @@ -3,6 +3,7 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ +@file:Suppress("DEPRECATION") package kotlinx.datetime import kotlin.js.* diff --git a/core/js/test/JsConverterTest.kt b/core/js/test/DeprecatedJsConverterTest.kt similarity index 93% rename from core/js/test/JsConverterTest.kt rename to core/js/test/DeprecatedJsConverterTest.kt index de5eeb6f3..3e34d673c 100644 --- a/core/js/test/JsConverterTest.kt +++ b/core/js/test/DeprecatedJsConverterTest.kt @@ -3,13 +3,14 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ +@file:Suppress("DEPRECATION") package kotlinx.datetime.test import kotlinx.datetime.* import kotlin.js.* import kotlin.test.* -class JsConverterTest { +class DeprecatedJsConverterTest { @Test fun toJSDateTest() { val releaseInstant = Instant.parse("2016-02-15T00:00:00Z") diff --git a/core/jvm/src/Converters.kt b/core/jvm/src/Converters.kt index 688b0f84a..029f4cb3a 100644 --- a/core/jvm/src/Converters.kt +++ b/core/jvm/src/Converters.kt @@ -8,12 +8,17 @@ package kotlinx.datetime /** * Converts this [kotlinx.datetime.Instant][Instant] value to a [java.time.Instant][java.time.Instant] value. */ -public fun Instant.toJavaInstant(): java.time.Instant = this.value +@PublishedApi +@Suppress("DEPRECATION") +internal fun Instant.toJavaInstant(): java.time.Instant = this.value /** * Converts this [java.time.Instant][java.time.Instant] value to a [kotlinx.datetime.Instant][Instant] value. */ -public fun java.time.Instant.toKotlinInstant(): Instant = Instant(this) +@PublishedApi +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") +@kotlin.internal.LowPriorityInOverloadResolution +internal fun java.time.Instant.toKotlinInstant(): Instant = Instant(this) /** diff --git a/core/jvm/src/DeprecatedInstant.kt b/core/jvm/src/DeprecatedInstant.kt new file mode 100644 index 000000000..8929d4055 --- /dev/null +++ b/core/jvm/src/DeprecatedInstant.kt @@ -0,0 +1,253 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +@file:Suppress("DEPRECATION") +@file:JvmMultifileClass +@file:JvmName("InstantJvmKt") +package kotlinx.datetime + +import kotlinx.datetime.format.DateTimeComponents +import kotlinx.datetime.format.DateTimeFormat +import kotlinx.datetime.internal.NANOS_PER_ONE +import kotlinx.datetime.internal.multiplyAndDivide +import kotlinx.datetime.internal.safeMultiply +import kotlinx.datetime.serializers.InstantIso8601Serializer +import kotlinx.serialization.Serializable +import java.time.Clock +import java.time.DateTimeException +import java.time.temporal.ChronoUnit +import kotlin.time.Duration +import kotlin.time.Duration.Companion.nanoseconds +import kotlin.time.Duration.Companion.seconds + +@Deprecated( + "Use kotlin.time.Instant instead", + ReplaceWith("kotlin.time.Instant", "kotlin.time.Instant"), + level = DeprecationLevel.WARNING +) +@Serializable(with = InstantIso8601Serializer::class) +public actual class Instant internal constructor(internal val value: java.time.Instant) : Comparable { + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().epochSeconds") + ) + public actual val epochSeconds: Long + get() = value.epochSecond + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().nanosecondsOfSecond") + ) + public actual val nanosecondsOfSecond: Int + get() = value.nano + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().nanosecondsOfSecond") + ) + public actual fun toEpochMilliseconds(): Long = try { + value.toEpochMilli() + } catch (e: ArithmeticException) { + if (value.isAfter(java.time.Instant.EPOCH)) Long.MAX_VALUE else Long.MIN_VALUE + } + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("(this.toStdlibInstant() + duration).toDeprecatedInstant()") + ) + public actual operator fun plus(duration: Duration): Instant = duration.toComponents { seconds, nanoseconds -> + try { + Instant(value.plusSeconds(seconds).plusNanos(nanoseconds.toLong())) + } catch (e: java.lang.Exception) { + if (e !is ArithmeticException && e !is DateTimeException) throw e + if (duration.isPositive()) MAX else MIN + } + } + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("(this.toStdlibInstant() - duration).toDeprecatedInstant()") + ) + public actual operator fun minus(duration: Duration): Instant = plus(-duration) + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant() - other.toStdlibInstant()") + ) + public actual operator fun minus(other: Instant): Duration = + (this.value.epochSecond - other.value.epochSecond).seconds + // won't overflow given the instant bounds + (this.value.nano - other.value.nano).nanoseconds + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().compareTo(other.toStdlibInstant())") + ) + public actual override operator fun compareTo(other: Instant): Int = this.value.compareTo(other.value) + + override fun equals(other: Any?): Boolean = + (this === other) || (other is Instant && this.value == other.value) + + override fun hashCode(): Int = value.hashCode() + + @Suppress("POTENTIALLY_NON_REPORTED_ANNOTATION") + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().toString()") + ) + actual override fun toString(): String = value.toString() + + public actual companion object { + @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlin.time.Clock"), level = DeprecationLevel.ERROR) + public actual fun now(): Instant = + Instant(Clock.systemUTC().instant()) + + public actual fun fromEpochMilliseconds(epochMilliseconds: Long): Instant = + Instant(java.time.Instant.ofEpochMilli(epochMilliseconds)) + + // TODO: implement a custom parser to 1) help DCE get rid of the formatting machinery 2) move Instant to stdlib + public actual fun parse(input: CharSequence, format: DateTimeFormat): Instant = try { + /** + * Can't use built-in Java Time's handling of `Instant.parse` because it supports 24:00:00 and + * 23:59:60, and also doesn't support non-`Z` UTC offsets on older JDKs. + * Can't use custom Java Time's formats because Java 8 doesn't support the UTC offset format with + * optional minutes and seconds and `:` between them: + * https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html#appendOffset-java.lang.String-java.lang.String- + */ + format.parse(input).toInstantUsingOffset().toDeprecatedInstant() + } catch (e: IllegalArgumentException) { + throw DateTimeFormatException("Failed to parse an instant from '$input'", e) + } + + @Deprecated("This overload is only kept for binary compatibility", level = DeprecationLevel.HIDDEN) + public fun parse(isoString: String): Instant = parse(input = isoString) + + public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long): Instant = try { + Instant(java.time.Instant.ofEpochSecond(epochSeconds, nanosecondAdjustment)) + } catch (e: Exception) { + if (e !is ArithmeticException && e !is DateTimeException) throw e + if (epochSeconds > 0) MAX else MIN + } + + public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant = + fromEpochSeconds(epochSeconds, nanosecondAdjustment.toLong()) + + public actual val DISTANT_PAST: Instant = Instant(java.time.Instant.ofEpochSecond(DISTANT_PAST_SECONDS, 999_999_999)) + public actual val DISTANT_FUTURE: Instant = Instant(java.time.Instant.ofEpochSecond(DISTANT_FUTURE_SECONDS, 0)) + + internal actual val MIN: Instant = Instant(java.time.Instant.MIN) + internal actual val MAX: Instant = Instant(java.time.Instant.MAX) + } +} + +private fun Instant.atZone(zone: TimeZone): java.time.ZonedDateTime = try { + value.atZone(zone.zoneId) +} catch (e: DateTimeException) { + throw DateTimeArithmeticException(e) +} + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(period, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant { + try { + val thisZdt = atZone(timeZone) + return with(period) { + thisZdt + .run { if (totalMonths != 0L) plusMonths(totalMonths) else this } + .run { if (days != 0) plusDays(days.toLong()) else this } + .run { if (totalNanoseconds != 0L) plusNanos(totalNanoseconds) else this } + }.toInstant().let(::Instant) + } catch (e: DateTimeException) { + throw DateTimeArithmeticException(e) + } +} + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(1, unit, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.plus(unit: DateTimeUnit, timeZone: TimeZone): Instant = + plus(1L, unit, timeZone) + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.plus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant = + plus(value.toLong(), unit, timeZone) + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(value, unit, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.minus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant = + plus(-value.toLong(), unit, timeZone) + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant = + try { + val thisZdt = atZone(timeZone) + when (unit) { + is DateTimeUnit.TimeBased -> + plus(value, unit).value.also { it.atZone(timeZone.zoneId) } + is DateTimeUnit.DayBased -> + thisZdt.plusDays(safeMultiply(value, unit.days.toLong())).toInstant() + is DateTimeUnit.MonthBased -> + thisZdt.plusMonths(safeMultiply(value, unit.months.toLong())).toInstant() + }.let(::Instant) + } catch (e: Exception) { + if (e !is DateTimeException && e !is ArithmeticException) throw e + throw DateTimeArithmeticException("Instant $this cannot be represented as local date when adding $value $unit to it", e) + } + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit).toDeprecatedInstant()") +) +public actual fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Instant = + try { + multiplyAndDivide(value, unit.nanoseconds, NANOS_PER_ONE.toLong()).let { (d, r) -> + Instant(this.value.plusSeconds(d).plusNanos(r)) + } + } catch (e: Exception) { + if (e !is DateTimeException && e !is ArithmeticException) throw e + if (value > 0) Instant.MAX else Instant.MIN + } + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().periodUntil(other.toStdlibInstant(), timeZone)") +) +public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod { + var thisZdt = this.atZone(timeZone) + val otherZdt = other.atZone(timeZone) + + val months = thisZdt.until(otherZdt, ChronoUnit.MONTHS); thisZdt = thisZdt.plusMonths(months) + val days = thisZdt.until(otherZdt, ChronoUnit.DAYS); thisZdt = thisZdt.plusDays(days) + val nanoseconds = thisZdt.until(otherZdt, ChronoUnit.NANOS) + + return buildDateTimePeriod(months, days.toInt(), nanoseconds) +} + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().until(other.toStdlibInstant(), unit, timeZone)") +) +public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long = try { + val thisZdt = this.atZone(timeZone) + val otherZdt = other.atZone(timeZone) + when(unit) { + is DateTimeUnit.TimeBased -> until(other, unit) + is DateTimeUnit.DayBased -> thisZdt.until(otherZdt, ChronoUnit.DAYS) / unit.days + is DateTimeUnit.MonthBased -> thisZdt.until(otherZdt, ChronoUnit.MONTHS) / unit.months + } +} catch (e: DateTimeException) { + throw DateTimeArithmeticException(e) +} catch (e: ArithmeticException) { + if (this.value < other.value) Long.MAX_VALUE else Long.MIN_VALUE +} diff --git a/core/jvm/src/Instant.kt b/core/jvm/src/Instant.kt index 78f82ed8b..0ec2f6fc9 100644 --- a/core/jvm/src/Instant.kt +++ b/core/jvm/src/Instant.kt @@ -2,108 +2,23 @@ * Copyright 2019-2020 JetBrains s.r.o. * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ +@file:JvmMultifileClass @file:JvmName("InstantJvmKt") package kotlinx.datetime -import kotlinx.datetime.format.* import kotlinx.datetime.internal.safeMultiply import kotlinx.datetime.internal.* -import kotlinx.datetime.serializers.InstantIso8601Serializer -import kotlinx.serialization.Serializable import java.time.DateTimeException import java.time.temporal.* -import kotlin.time.* +import kotlin.time.Instant +import kotlin.time.toJavaInstant +import kotlin.time.toKotlinInstant import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.Duration.Companion.seconds -import java.time.Instant as jtInstant -import java.time.Clock as jtClock - -@Serializable(with = InstantIso8601Serializer::class) -public actual class Instant internal constructor( - internal val value: jtInstant -) : Comparable { - - public actual val epochSeconds: Long - get() = value.epochSecond - public actual val nanosecondsOfSecond: Int - get() = value.nano - - public actual fun toEpochMilliseconds(): Long = try { - value.toEpochMilli() - } catch (e: ArithmeticException) { - if (value.isAfter(java.time.Instant.EPOCH)) Long.MAX_VALUE else Long.MIN_VALUE - } - - public actual operator fun plus(duration: Duration): Instant = duration.toComponents { seconds, nanoseconds -> - try { - Instant(value.plusSeconds(seconds).plusNanos(nanoseconds.toLong())) - } catch (e: java.lang.Exception) { - if (e !is ArithmeticException && e !is DateTimeException) throw e - if (duration.isPositive()) MAX else MIN - } - } - - public actual operator fun minus(duration: Duration): Instant = plus(-duration) - - public actual operator fun minus(other: Instant): Duration = - (this.value.epochSecond - other.value.epochSecond).seconds + // won't overflow given the instant bounds - (this.value.nano - other.value.nano).nanoseconds - - public actual override operator fun compareTo(other: Instant): Int = this.value.compareTo(other.value) - - override fun equals(other: Any?): Boolean = - (this === other) || (other is Instant && this.value == other.value) - - override fun hashCode(): Int = value.hashCode() - - actual override fun toString(): String = value.toString() - - public actual companion object { - @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.datetime.Clock"), level = DeprecationLevel.ERROR) - public actual fun now(): Instant = - Instant(jtClock.systemUTC().instant()) - - public actual fun fromEpochMilliseconds(epochMilliseconds: Long): Instant = - Instant(jtInstant.ofEpochMilli(epochMilliseconds)) - - // TODO: implement a custom parser to 1) help DCE get rid of the formatting machinery 2) move Instant to stdlib - public actual fun parse(input: CharSequence, format: DateTimeFormat): Instant = try { - /** - * Can't use built-in Java Time's handling of `Instant.parse` because it supports 24:00:00 and - * 23:59:60, and also doesn't support non-`Z` UTC offsets on older JDKs. - * Can't use custom Java Time's formats because Java 8 doesn't support the UTC offset format with - * optional minutes and seconds and `:` between them: - * https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html#appendOffset-java.lang.String-java.lang.String- - */ - format.parse(input).toInstantUsingOffset() - } catch (e: IllegalArgumentException) { - throw DateTimeFormatException("Failed to parse an instant from '$input'", e) - } - - @Deprecated("This overload is only kept for binary compatibility", level = DeprecationLevel.HIDDEN) - public fun parse(isoString: String): Instant = parse(input = isoString) - - public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long): Instant = try { - Instant(jtInstant.ofEpochSecond(epochSeconds, nanosecondAdjustment)) - } catch (e: Exception) { - if (e !is ArithmeticException && e !is DateTimeException) throw e - if (epochSeconds > 0) MAX else MIN - } - - public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant = - fromEpochSeconds(epochSeconds, nanosecondAdjustment.toLong()) - - public actual val DISTANT_PAST: Instant = Instant(jtInstant.ofEpochSecond(DISTANT_PAST_SECONDS, 999_999_999)) - public actual val DISTANT_FUTURE: Instant = Instant(jtInstant.ofEpochSecond(DISTANT_FUTURE_SECONDS, 0)) - - internal actual val MIN: Instant = Instant(jtInstant.MIN) - internal actual val MAX: Instant = Instant(jtInstant.MAX) - } -} private fun Instant.atZone(zone: TimeZone): java.time.ZonedDateTime = try { - value.atZone(zone.zoneId) + toJavaInstant().atZone(zone.zoneId) } catch (e: DateTimeException) { throw DateTimeArithmeticException(e) } @@ -116,7 +31,7 @@ public actual fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Inst .run { if (totalMonths != 0L) plusMonths(totalMonths) else this } .run { if (days != 0) plusDays(days.toLong()) else this } .run { if (totalNanoseconds != 0L) plusNanos(totalNanoseconds) else this } - }.toInstant().let(::Instant) + }.toInstant().toKotlinInstant() } catch (e: DateTimeException) { throw DateTimeArithmeticException(e) } @@ -137,12 +52,12 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZo val thisZdt = atZone(timeZone) when (unit) { is DateTimeUnit.TimeBased -> - plus(value, unit).value.also { it.atZone(timeZone.zoneId) } + plus(value, unit).toJavaInstant().also { it.atZone(timeZone.zoneId) } is DateTimeUnit.DayBased -> thisZdt.plusDays(safeMultiply(value, unit.days.toLong())).toInstant() is DateTimeUnit.MonthBased -> thisZdt.plusMonths(safeMultiply(value, unit.months.toLong())).toInstant() - }.let(::Instant) + }.toKotlinInstant() } catch (e: Exception) { if (e !is DateTimeException && e !is ArithmeticException) throw e throw DateTimeArithmeticException("Instant $this cannot be represented as local date when adding $value $unit to it", e) @@ -151,11 +66,11 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZo public actual fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Instant = try { multiplyAndDivide(value, unit.nanoseconds, NANOS_PER_ONE.toLong()).let { (d, r) -> - Instant(this.value.plusSeconds(d).plusNanos(r)) + this.plus(d.seconds).plus(r.nanoseconds) } } catch (e: Exception) { if (e !is DateTimeException && e !is ArithmeticException) throw e - if (value > 0) Instant.MAX else Instant.MIN + Instant.fromEpochSeconds(if (value > 0) Long.MAX_VALUE else Long.MIN_VALUE) } public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod { @@ -180,5 +95,5 @@ public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: Ti } catch (e: DateTimeException) { throw DateTimeArithmeticException(e) } catch (e: ArithmeticException) { - if (this.value < other.value) Long.MAX_VALUE else Long.MIN_VALUE + if (this < other) Long.MAX_VALUE else Long.MIN_VALUE } diff --git a/core/jvm/src/TimeZoneJvm.kt b/core/jvm/src/TimeZoneJvm.kt index cd90993ef..c9f68cdba 100644 --- a/core/jvm/src/TimeZoneJvm.kt +++ b/core/jvm/src/TimeZoneJvm.kt @@ -13,6 +13,9 @@ import kotlinx.serialization.Serializable import java.time.DateTimeException import java.time.ZoneId import java.time.ZoneOffset as jtZoneOffset +import kotlin.time.Instant +import kotlin.time.toJavaInstant +import kotlin.time.toKotlinInstant @Serializable(with = TimeZoneSerializer::class) public actual open class TimeZone internal constructor(internal val zoneId: ZoneId) { @@ -21,7 +24,23 @@ public actual open class TimeZone internal constructor(internal val zoneId: Zone // experimental member-extensions public actual fun Instant.toLocalDateTime(): LocalDateTime = toLocalDateTime(this@TimeZone) - public actual fun LocalDateTime.toInstant(): Instant = toInstant(this@TimeZone) + + @Suppress("DEPRECATION_ERROR") + public actual fun LocalDateTime.toInstant(youShallNotPass: OverloadMarker): Instant = toInstant(this@TimeZone) + + @Suppress("DEPRECATION") + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().toLocalDateTime()") + ) + public actual fun kotlinx.datetime.Instant.toLocalDateTime(): LocalDateTime = + toStdlibInstant().toLocalDateTime() + + @PublishedApi + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") + @kotlin.internal.LowPriorityInOverloadResolution + internal actual fun LocalDateTime.toInstant(): kotlinx.datetime.Instant = + toInstant(this@TimeZone).toDeprecatedInstant() actual override fun equals(other: Any?): Boolean = (this === other) || (other is TimeZone && this.zoneId == other.zoneId) @@ -74,26 +93,29 @@ internal constructor(public actual val offset: UtcOffset, zoneId: ZoneId): TimeZ } public actual fun TimeZone.offsetAt(instant: Instant): UtcOffset = - zoneId.rules.getOffset(instant.value).let(::UtcOffset) + zoneId.rules.getOffset(instant.toJavaInstant()).let(::UtcOffset) public actual fun Instant.toLocalDateTime(timeZone: TimeZone): LocalDateTime = try { - java.time.LocalDateTime.ofInstant(this.value, timeZone.zoneId).let(::LocalDateTime) + java.time.LocalDateTime.ofInstant(this.toJavaInstant(), timeZone.zoneId).let(::LocalDateTime) } catch (e: DateTimeException) { throw DateTimeArithmeticException(e) } internal actual fun Instant.toLocalDateTime(offset: UtcOffset): LocalDateTime = try { - java.time.LocalDateTime.ofInstant(this.value, offset.zoneOffset).let(::LocalDateTime) + java.time.LocalDateTime.ofInstant(this.toJavaInstant(), offset.zoneOffset).let(::LocalDateTime) } catch (e: DateTimeException) { throw DateTimeArithmeticException(e) } -public actual fun LocalDateTime.toInstant(timeZone: TimeZone): Instant = - this.value.atZone(timeZone.zoneId).toInstant().let(::Instant) +@Suppress("DEPRECATION_ERROR") +public actual fun LocalDateTime.toInstant(timeZone: TimeZone, youShallNotPass: OverloadMarker): Instant = + this.value.atZone(timeZone.zoneId).toInstant().toKotlinInstant() -public actual fun LocalDateTime.toInstant(offset: UtcOffset): Instant = - this.value.toInstant(offset.zoneOffset).let(::Instant) +@Suppress("DEPRECATION_ERROR") +public actual fun LocalDateTime.toInstant(offset: UtcOffset, youShallNotPass: OverloadMarker): Instant = + this.value.toInstant(offset.zoneOffset).toKotlinInstant() -public actual fun LocalDate.atStartOfDayIn(timeZone: TimeZone): Instant = - this.value.atStartOfDay(timeZone.zoneId).toInstant().let(::Instant) +@Suppress("DEPRECATION_ERROR") +public actual fun LocalDate.atStartOfDayIn(timeZone: TimeZone, youShallNotPass: OverloadMarker): Instant = + this.value.atStartOfDay(timeZone.zoneId).toInstant().toKotlinInstant() diff --git a/core/jvm/test/ConvertersTest.kt b/core/jvm/test/ConvertersTest.kt index 06b7e2239..26d05ec8e 100644 --- a/core/jvm/test/ConvertersTest.kt +++ b/core/jvm/test/ConvertersTest.kt @@ -14,6 +14,8 @@ import java.time.LocalDate as JTLocalDate import java.time.Period as JTPeriod import java.time.ZoneId import java.time.ZoneOffset as JTZoneOffset +import kotlin.time.Instant +import kotlin.time.* class ConvertersTest { diff --git a/core/jvm/test/InstantParsing.kt b/core/jvm/test/InstantParsing.kt index 4c29b81e9..a50ee5213 100644 --- a/core/jvm/test/InstantParsing.kt +++ b/core/jvm/test/InstantParsing.kt @@ -2,6 +2,8 @@ package kotlinx.datetime import kotlinx.datetime.format.* import kotlin.test.* +import kotlin.time.Instant +import kotlin.time.* class InstantParsing { @Test diff --git a/core/native/src/internal/Platform.kt b/core/native/src/internal/Platform.kt deleted file mode 100644 index 63b890610..000000000 --- a/core/native/src/internal/Platform.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2019-2024 JetBrains s.r.o. and contributors. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.datetime.internal - -import kotlinx.cinterop.* -import kotlinx.datetime.Instant -import platform.posix.* - -@OptIn(ExperimentalForeignApi::class, UnsafeNumber::class) -internal actual fun currentTime(): Instant = memScoped { - val tm = alloc() - val error = clock_gettime(CLOCK_REALTIME.convert(), tm.ptr) - check(error == 0) { "Error when reading the system clock: ${strerror(errno)?.toKString() ?: "Unknown error"}" } - try { - require(tm.tv_nsec in 0 until NANOS_PER_ONE) - Instant(tm.tv_sec.convert(), tm.tv_nsec.convert()) - } catch (e: IllegalArgumentException) { - throw IllegalStateException("The readings from the system clock (${tm.tv_sec} seconds, ${tm.tv_nsec} nanoseconds) are not representable as an Instant") - } -} \ No newline at end of file diff --git a/core/tzdbOnFilesystem/test/TimeZoneRulesCompleteTest.kt b/core/tzdbOnFilesystem/test/TimeZoneRulesCompleteTest.kt index 43437708a..f50942db6 100644 --- a/core/tzdbOnFilesystem/test/TimeZoneRulesCompleteTest.kt +++ b/core/tzdbOnFilesystem/test/TimeZoneRulesCompleteTest.kt @@ -11,6 +11,7 @@ import kotlinx.datetime.internal.* import platform.posix.* import kotlin.io.encoding.* import kotlin.test.* +import kotlin.time.Instant class TimeZoneRulesCompleteTest { @OptIn(ExperimentalEncodingApi::class) diff --git a/core/wasmWasi/src/internal/Platform.kt b/core/wasmWasi/src/internal/Platform.kt deleted file mode 100644 index 1403cae7d..000000000 --- a/core/wasmWasi/src/internal/Platform.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2019-2024 JetBrains s.r.o. and contributors. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.datetime.internal - -import kotlinx.datetime.Instant -import kotlin.wasm.WasmImport -import kotlin.wasm.unsafe.UnsafeWasmMemoryApi -import kotlin.wasm.unsafe.withScopedMemoryAllocator - -/** - * Return the time value of a clock. Note: This is similar to `clock_gettime` in POSIX. - */ -@WasmImport("wasi_snapshot_preview1", "clock_time_get") -private external fun wasiRawClockTimeGet(clockId: Int, precision: Long, resultPtr: Int): Int - -private const val CLOCKID_REALTIME = 0 - -@OptIn(UnsafeWasmMemoryApi::class) -private fun clockTimeGet(): Long = withScopedMemoryAllocator { allocator -> - val rp0 = allocator.allocate(8) - val ret = wasiRawClockTimeGet( - clockId = CLOCKID_REALTIME, - precision = 1, - resultPtr = rp0.address.toInt() - ) - if (ret == 0) { - rp0.loadLong() - } else { - error("WASI call failed with $ret") - } -} - -internal actual fun currentTime(): Instant = clockTimeGet().let { time -> - // Instant.MAX and Instant.MIN are never going to be exceeded using just the Long number of nanoseconds - Instant(time.floorDiv(NANOS_PER_ONE.toLong()), time.mod(NANOS_PER_ONE.toLong()).toInt()) -} diff --git a/core/windows/test/TimeZoneRulesCompleteTest.kt b/core/windows/test/TimeZoneRulesCompleteTest.kt index 6352494f8..12d4c7964 100644 --- a/core/windows/test/TimeZoneRulesCompleteTest.kt +++ b/core/windows/test/TimeZoneRulesCompleteTest.kt @@ -14,6 +14,8 @@ import platform.windows.* import kotlin.test.* import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Instant +import kotlin.time.Clock class TimeZoneRulesCompleteTest { diff --git a/gradle.properties b/gradle.properties index ac136ccf0..37fd4168d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ versionSuffix=SNAPSHOT tzdbVersion=2025a -defaultKotlinVersion=2.1.0 +defaultKotlinVersion=2.1.20-RC dokkaVersion=1.9.20 serializationVersion=1.6.2 benchmarksVersion=0.7.2 diff --git a/js-without-timezones/common/test/TimezonesWithoutDatabaseTest.kt b/js-without-timezones/common/test/TimezonesWithoutDatabaseTest.kt index 153b505cd..c496881f5 100644 --- a/js-without-timezones/common/test/TimezonesWithoutDatabaseTest.kt +++ b/js-without-timezones/common/test/TimezonesWithoutDatabaseTest.kt @@ -7,7 +7,10 @@ package kotlinx.datetime.test import kotlinx.datetime.* import kotlin.test.* +import kotlin.time.Instant +import kotlin.time.Clock +@OptIn(kotlin.time.ExperimentalTime::class) class TimezonesWithoutDatabaseTest { @Test fun system() { diff --git a/serialization/common/test/InstantSerializationTest.kt b/serialization/common/test/DeprecatedInstantSerializationTest.kt similarity index 97% rename from serialization/common/test/InstantSerializationTest.kt rename to serialization/common/test/DeprecatedInstantSerializationTest.kt index dea5c2dbd..daaa054e2 100644 --- a/serialization/common/test/InstantSerializationTest.kt +++ b/serialization/common/test/DeprecatedInstantSerializationTest.kt @@ -2,6 +2,8 @@ * Copyright 2019-2020 JetBrains s.r.o. * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ + +@file:Suppress("DEPRECATION") package kotlinx.datetime.serialization.test import kotlinx.datetime.* @@ -10,7 +12,7 @@ import kotlinx.serialization.* import kotlinx.serialization.json.* import kotlin.test.* -class InstantSerializationTest { +class DeprecatedInstantSerializationTest { private fun iso8601Serialization(serializer: KSerializer) { for ((instant, json) in listOf( diff --git a/serialization/common/test/IntegrationTest.kt b/serialization/common/test/IntegrationTest.kt index 0452a10fd..e792e442c 100644 --- a/serialization/common/test/IntegrationTest.kt +++ b/serialization/common/test/IntegrationTest.kt @@ -3,13 +3,13 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ +@file:Suppress("DEPRECATION") package kotlinx.datetime.serialization.test import kotlinx.datetime.* import kotlinx.datetime.serializers.* import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.modules.SerializersModule @@ -127,4 +127,4 @@ class IntegrationTest { assertEquals(dummyValue, format.decodeFromString(json)) assertEquals(json, format.encodeToString(dummyValue)) } -} \ No newline at end of file +} diff --git a/settings.gradle.kts b/settings.gradle.kts index ab7a6af57..a1dad22f8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,7 @@ pluginManagement { repositories { maven(url = "https://maven.pkg.jetbrains.space/kotlin/p/kotlinx/maven") + mavenLocal() mavenCentral() gradlePluginPortal() }