-
Notifications
You must be signed in to change notification settings - Fork 109
Previous/next date and/or time with the given form #325
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
One practical use case for such functionality: the OCPP specification. It mandates that ISO date/time strings don't have more than 3 decimal points:
In other words: no more than millisecond precision. While working with We could use JSR-310 ( |
Update: I found a single-line workaround for forcibly reducing the precision to millisecond-level in serialized ISO date/time strings, at least for timestamps originating from the system clock: import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
// Will have 6 digits behind the decimal point on Kotlin/JVM, but 3 digits on Kotlin/JS and Kotlin/Native
println(Clock.System.now())
// Will have 3 digits behind the decimal point on Kotlin/JVM, Kotlin/JS and Kotlin/Native
println(Instant.fromEpochMilliseconds(Clock.System.now().toEpochMilliseconds())) So Kotlin/JVM appears to appears to be the only Kotlin platform that creates |
@volkert-fastned, since your problem is with system interoperability, the upcoming API for parsing and formatting may solve it: #343 |
I've the use case where I want to check whether a day ( LE: here's the equivalent code using Java API (in Kotlin) of what I'm trying to do fun isCurrentDay(timestamp: Long): Boolean {
val instant = Instant.ofEpochMilli(timestamp)
val currentLocalDate = LocalDate.now(ZoneId.systemDefault())
val timestampLocalDate = instant.atZone(ZoneId.systemDefault()).toLocalDate()
return currentLocalDate == timestampLocalDate
}
fun isCurrentWeek(timestamp: Long): Boolean {
val instant = Instant.ofEpochMilli(timestamp)
val currentLocalDate = LocalDate.now(ZoneId.systemDefault())
val timestampLocalDate = instant.atZone(ZoneId.systemDefault()).toLocalDate()
val currentWeekStart = currentLocalDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
val currentWeekEnd = currentWeekStart.plusDays(6)
return timestampLocalDate in currentWeekStart..currentWeekEnd
}
fun isCurrentMonth(timestamp: Long): Boolean {
val instant = Instant.ofEpochMilli(timestamp)
val currentLocalDate = LocalDate.now(ZoneId.systemDefault())
val timestampLocalDate = instant.atZone(ZoneId.systemDefault()).toLocalDate()
val currentMonthStart = currentLocalDate.with(TemporalAdjusters.firstDayOfMonth())
val currentMonthEnd = currentMonthStart.plusDays(
(currentLocalDate.lengthOfMonth() - 1).toLong()
)
return timestampLocalDate in currentMonthStart..currentMonthEnd
} |
@alghe-global, here are some kotlinx-datetime implementations: import kotlinx.datetime.*
fun LocalDate.isSameDay(timestamp: Long): Boolean =
this == timestampToDateInSystemTimeZone(timestamp)
fun LocalDate.isSameWeek(timestamp: Long): Boolean =
previousOrSame(DayOfWeek.MONDAY) ==
timestampToDateInSystemTimeZone(timestamp).previousOrSame(DayOfWeek.MONDAY)
fun LocalDate.isSameMonth(timestamp: Long): Boolean {
val timestampLocalDate = timestampToDateInSystemTimeZone(timestamp)
return year == timestampLocalDate.year && month == timestampLocalDate.month
}
/*
// After https://github.com/Kotlin/kotlinx-datetime/pull/457:
fun LocalDate.isSameMonth(timestamp: Long): Boolean =
yearMonth == timestampToDateInSystemTimeZone(timestamp).yearMonth
*/
private fun timestampToDateInSystemTimeZone(timestampMillis: Long) =
Instant.fromEpochMilliseconds(timestampMillis).toLocalDateTime(TimeZone.currentSystemDefault()).date
// https://github.com/Kotlin/kotlinx-datetime/issues/129#issuecomment-1152301045
private fun LocalDate.previousOrSame(requiredDayOfWeek: DayOfWeek): LocalDate =
minus((dayOfWeek.isoDayNumber - requiredDayOfWeek.isoDayNumber).mod(7), DateTimeUnit.DAY) The "current week" use case would indeed benefit from temporal-adjuster-style functionality, but as a workaround, you could copy this implementation of |
Another missing use case of ZonedDateTime is something like this which we do allot of billing to make sure we doing close to midnight as possible:
To get this, we have to convert to JodaDateTime to be able to work out the midnight in that time zone, LocalDateTime or Instant doens't know this without being timezone aware. |
@chrisjenx, did you try https://kotlinlang.org/api/kotlinx-datetime/kotlinx-datetime/kotlinx.datetime/at-start-of-day-in.html ? Also, there is no need to repeat your message in several threads, we are reading everything that's submitted to the issue tracker. |
That only works on getting an Instant -> LocalDateTime -> LocalDate -> atTimeWithStartOfDay -> Instant Thankfully we send timezones separately in our api requests otherwise this would be impossible, then we also want todo things like this ALL the time esp in testing:
Which obviously is messy as instant isn't zoned. I know you keep shooting it down, but there needs to be a comparable DateTime/ZonedDateTime to Joda/Java8 API's. Also I cross posted as wasn't sure which ticket it should go in |
If this is too verbose and your business domain has a shorter name for this operation, you can add an extension function with that name in your code.
Sure, it's impossible to correctly perform this operation without knowing both the instant and the time zone, no matter what API you are using. What is your point here?
Could you describe this use case in more detail? If there is no 07:00 at that time zone at that moment, what should be returned? Say, if 06:30 jumps directly to 07:30, should the result be 08:30, 07:30, or something else, and why?
I think "shooting it down" is an emotionally charged and incorrect description, as I personally am fully in favor of introducing |
I'm not the first person to share this sentiment, but the extension function argument was ok when we interacted with legacy java api's, this getting worn a little thin when we're talking about new api's that can/are being built from the ground up.
Another use case which I'll share which shows how this is much easier in Joda/Java but difficult in kotlinx-dt:
Now the datetime equivalent (which don't do lookahead, and we have own methods for leapyears, days in month which don't exist in kt-datetime):
I get what you are saying, but with ZonedDateTime, we can deduce and operate on the instance at a parsed level. 2020-01-01T10:10:10-0600 would have it's offset set so means we have rough knowledge of what time zone to operate in for time operations.
Most common case is start of day, some places go from 12 -> 1am when time shifts or 1 -> 12am, I think in most cases what Joda does is correct, I specify 12am (00:00) it would put me to 1am as they are the "same" time. Clocks moving back is a little more interesting, specifying a time at say 1am would still be correct, but you move a clock forward 24 hours from 6pm, that would be 5am the next day, but if you plus 1 day that would still be 6pm but if you diffed the hours that would be 25 not 24. Same in reverse, plus 24 hours would put you at 7pm, but if you diffed the hours for plus 1 day it would be 23. THis is actually calculations we do with scheduling:
Where we will dynamically test how many schedule slots will get created on a given day:
Multiple places will do things like:
I see your point, you "can" do everything that other API's do, I think where people get caught up is that you have to go back and forth through cumbersome API's and more importantly know how to do those conversions. The shared above time/date conversions are easy to do and operate on, but I have to do Instant -> LocalDate -> Set Time -> Instant -> plus/minus -> toLocaleDatetTime. It's just not intuitive.
See above, think where it gets heated/frustrated is that people are coming from these API's and are getting stuck when seeing no direct equivalent, then questions feel like being dismissed without example api's. Maybe the outcome here would be a Coming from Joda/Java guide which would have eased my transition dramatically and I touch just about every API (writing scheduling/calendar based stuff) so can only imagine how much easier it would be for those with less knowledge of both api's. |
@chrisjenx, tl;dr: your problem is that you're using
Having zero onboarding is not a goal for This is out of scope of this discussion, but the gist of the reasoning behind this decision was that we saw how java.time APIs were widely used incorrectly due to users not understanding what the operations that "intuitively make sense" actually do. We expose the naked truth, hopefully leading a developer to less buggy code.
Would be nice if you could provide some benchmarks to substantiate this claim. I don't imagine any significant performance wins would be gained from squashing In fact, I believe orthogonal operations make writing performant code easier. Imagine you want to obtain the start of the next day instead. If you know about
Sure, but it's not like questions don't get asked all the time about java.time or Joda as well.
Is it expected that I'll assume this is a bug (and another data point for not providing such error-prone low-level functions), but please do correct me if I'm wrong, and I'll try to think of a kotlinx-datetime-idiomatic solution that covers your exact case. If this is indeed a bug, I don't see any downsides to the following implementation: import kotlinx.datetime.*
/**
* Returns the date >= this, setting day-of-month to [dayOfMonth],
* unless the length of the month does not allow this,
* in which case the end of the month is returned.
*
* If [lookahead] is set to `true`, instead of returning a date >= this,
* a date > this is returned.
*/
fun LocalDate.nextDayOfMonth(dayOfMonth: Int, lookahead: Boolean = false): LocalDate {
if (lookahead) return plus(1, DateTimeUnit.DAY).nextDayOfMonth(dayOfMonth)
require(dayOfMonth in 1..31) { "Invalid day of month" }
val startOfNextMonth = LocalDate(year, month, 1).plus(1, DateTimeUnit.MONTH)
val endOfMonth = startOfNextMonth.minus(1, DateTimeUnit.DAY)
return when {
dayOfMonth >= endOfMonth.dayOfMonth -> endOfMonth
dayOfMonth >= this.dayOfMonth -> LocalDate(year, month, dayOfMonth)
else -> startOfNextMonth.nextDayOfMonth(dayOfMonth)
}
}
fun Instant.nextDayOfMonth(dayOfMonth: Int, zone: TimeZone, lookahead: Boolean = false): Instant {
val dateTime = toLocalDateTime(zone)
val newDate = dateTime.date.nextDayOfMonth(dayOfMonth, lookahead)
return newDate.atTime(dateTime.time).toInstant(zone)
} With the upcoming fun LocalDate.nextDayOfMonth(dayOfMonth: Int, lookahead: Boolean = false): LocalDate {
if (lookahead) return plus(1, DateTimeUnit.DAY).nextDayOfMonth(dayOfMonth)
require(dayOfMonth in 1..31) { "Invalid day of month" }
val endOfMonth = yearMonth.lastDay
return when {
dayOfMonth >= endOfMonth.dayOfMonth -> endOfMonth
dayOfMonth >= this.dayOfMonth -> yearMonth.onDay(dayOfMonth)
else -> yearMonth.plusMonth().firstDay.nextDayOfMonth(dayOfMonth)
}
} Other snippets you provided also don't look scary to me when translated to import kotlinx.datetime.*
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes
fun LocalDate.dayDurationIn(zone: TimeZone): Duration {
val start = atStartOfDayIn(zone)
val end = plus(1, DateTimeUnit.DAY).atStartOfDayIn(zone)
return end - start
}
fun LocalDate.hoursInDayIn(zone: TimeZone): Int =
dayDurationIn(zone).inWholeHours.toInt()
val defaultDuration: Duration = 150.minutes
fun LocalDate.numberOfSlotsIn(zone: TimeZone): Int =
(dayDurationIn(zone) / defaultDuration).toInt()
fun sevenAmYesterdayAtDenver(): Instant {
val denver = TimeZone.of("America/Denver")
return Clock.System.todayIn(denver).minus(1, DateTimeUnit.DAY).atTime(7, 0).toInstant(denver)
} Note: I haven't actually run any of the functions I've written, so please do test these functions if you want to use them.
Parsing this is covered by
We are making the conversions that are going to happen anyway explicit in an attempt to simplify the mental model one may have of datetimes. In our research, we found that the wrappers around this model make it easier for bugs to sneak by in innocuous-looking code. There is no way around understanding what the code you are writing actually does.
There are usage examples for every API provided. In any case, in Kotlin Slack, we are always ready to answer any usage questions, and when it indeed turns out that something is difficult to express in
This would be tough to write. The issue is that often, there really is no straightforward direct equivalent, and the answer "you probably don't need this, please rethink your logic" is likely to only be disappointing to the reader, even if it really is true. I can't quickly think of a way to summarize the problems I've seen people experience when migrating to
If you somehow find the format strings used by Java natural, you can use those: https://github.com/Kotlin/kotlinx-datetime?tab=readme-ov-file#using-unicode-format-strings-like-yyyy-mm-dd |
@chrisjenx, found a behavior in your original snippet that's certainly a bug: |
"I have a date or a time, and I want to round/adjust it."
Examples:
Duration
: Feature Request: truncateTo for LocalTime #225 (comment) (can also be solved by tweaking the formatter).The text was updated successfully, but these errors were encountered: