swiftlang / swift-foundation

The Foundation project
Apache License 2.0
2.4k stars 159 forks source link

Date.RelativeFormatStyle is rounding up #774

Open BrentMifsud opened 3 months ago

BrentMifsud commented 3 months ago

We recently switched over to using FormatStyles instead of Dateformatters.

I noticed some odd behavior with the relative date format style.

import Foundation

let date = Calendar.autoupdatingCurrent.date(byAdding: .second, value: 90, to: .now)!
let date2 = Calendar.autoupdatingCurrent.date(byAdding: .second, value: 89, to: .now)!
date.formatted(.relative(presentation: .numeric, unitsStyle: .wide)) // in 2 minutes
date2.formatted(.relative(presentation: .numeric, unitsStyle: .wide)) // in 1 minute

Is this the intended behaviour?

Using legacy RelativeDateFormatters, it says "in 1 minute" until you actually reach the 2 min mark

itingliu commented 3 months ago

It's probably more correct to say it's "rounding to the nearest minute" instead of "rounding up". 89 seconds is 1.48 minutes, so it's displayed as "in 1 minute", whereas "90 seconds" is 1.5 minutes, so it becomes "in 2 minutes" (see here for implementation

And yes it is intentional. We had some feedback about the current behaviour of RelativeDateFormatters being undesired, so we made this choice intentionally. I think it would be useful to expose an API to customise this though.

BrentMifsud commented 3 months ago

It's probably more correct to say it's "rounding to the nearest minute" instead of "rounding up". 89 seconds is 1.48 minutes, so it's displayed as "in 1 minute", whereas "90 seconds" is 1.5 minutes, so it becomes "in 2 minutes" (see here for implementation

And yes it is intentional. We had some feedback about the current behaviour of RelativeDateFormatters being undesired, so we made this choice intentionally. I think it would be useful to expose an API to customise this though.

Thanks for the context on this.

I did notice that this rounding also occurs with larger units of time as well (weeks, years, etc...). Which definitely starts to feel more strange.

Also going backwards in time seems to be inconsistent compared to when comparing future dates.

let calendar = Calendar.autoupdatingCurrent

let formatStyle = Date.RelativeFormatStyle(presentation: .numeric, unitsStyle: .wide, locale: .autoupdatingCurrent, calendar: calendar, capitalizationContext: .middleOfSentence)

let today = Date()
let oneWeekFromNow = calendar.date(byAdding: .weekOfYear, value: 1, to: today)
let nineDaysFromNow = calendar.date(byAdding: .day, value: 9, to: today)
let oneYearFromNow = calendar.date(byAdding: .year, value: 1, to: today)
let oneYearSixMonthsFromNow = calendar.date(byAdding: .month, value: 18, to: today)
oneWeekFromNow!.formatted(formatStyle) // in 1 week
nineDaysFromNow!.formatted(formatStyle) // in 2 weeks
oneYearFromNow!.formatted(formatStyle) // in 1 year
oneYearSixMonthsFromNow!.formatted(formatStyle) // in 2 years
oneWeekAgo!.formatted(formatStyle) // 1 week ago
nineDaysAgo!.formatted(formatStyle) // 1 week ago
oneMinuteAgo!.formatted(formatStyle) // 1 minute ago
oneAndAHalfMinAgo!.formatted(formatStyle) // 2 minutes ago

Would definitely be nice to be able to configure the rounding options, as well as the date components we want to show.

itingliu commented 3 months ago

I did notice that this rounding also occurs with larger units of time as well (weeks, years, etc...).

You're right. When I said "it's rounding to the nearest minute" previously, I should have said that it's "rounding to the nearest component", and is not particular to "minute".

Would definitely be nice to be able to configure the rounding options, as well as the date components we want to show.

We recently added the allowFields property to let you choose which components to show. There are some examples in this test. Let us know if that's a good start!

BrentMifsud commented 2 months ago

I did notice that this rounding also occurs with larger units of time as well (weeks, years, etc...).

You're right. When I said "it's rounding to the nearest minute" previously, I should have said that it's "rounding to the nearest component", and is not particular to "minute".

Would definitely be nice to be able to configure the rounding options, as well as the date components we want to show.

We recently added the allowFields property to let you choose which components to show. There are some examples in this test. Let us know if that's a good start!

Unfortunately the new AllowedFields aren't backported to earlier iOS versions. So I can't use them. But will test it out in a playground to see if it does what I need.