I think Foundation.Date
should conform to Strideable
.
I think there is an unambiguous, preferable, implementation of Strideable
for Date
.
I have written out this implementation Here.
Since date and time is notoriously tricky to manipulate, what are the implications of such difficulties for Strideable
conformance?
The conformance I implemented - by design - only strides by (partial) seconds.
Any misbehavior can only happen in acquiring the desired number of seconds to stride by, which people do anyway.
I think it would be more useful to provide date iterators with an API surface that provides calendar accurate iteration for a variety time units. Iāve written them myself before but it seems like a logical Foundation extension in Swift to, say, iterate all Mondays between dates.
While that would be nice, from what I know about time and date manipulations, itād be very, very hard to do correctly in the general case and require significant domain-specific expertise, would it not?
Not really, considering Foundation already provides the abstractions and iteration logic. The main pain point is trying to map such broad functionality onto an Iterator
. My implementations have always used the Gregorian calendar, for example.
Yeah you could totally do that. Foundationās Calendar
has APIs for scanning dates which could easily encapsulated in an Iterator:
https://developer.apple.com/documentation/foundation/calendar/2293473-nextdate
This is an interesting request, but IMO striding through dates like youāre suggesting isnāt a common enough operation that would merit it inclusion in to the standard library.
This would make sense for a Date/Time-specific library though.
As a broader thought, striding through dates is calendrically difficult. The simple illustration for this is: āJan 31 + 1 month + 1 month = Mar 28ā, but āJan 31 + 2 months = Mar 31ā.
You could write an iterator to handle this sort of thing, but at that point you forego the nextDate
API on Calendarā¦
This sort of functionality falls under the broader topic of ārecurrence rulesā, which would be an ideal topic for a non-standard library.
I am not suggesting adding it to the stdlib, but to Foundation
The suggested addition only strides seconds, not getting into the complexities of calendars.
Do we have examples of what adding this functionality would enable in practice?
Not currently, by me or anyone else.
I just like conforming types to protocols, when the conformance isnāt ambiguous.
Hmm, I think that's problematic. I had assumed you had a concrete use case in mind. Conformances in Swift should always enable useful code to be written, not least because it tests the implementation in a real-world scenario.
Without that, I think we should hold off on adding this particular conformance, as it may block alternative ways of conforming Date
to Strideable
that would enable a greater number of useful things to be done with it.
You donāt want to use strides with TimeInterval, but date.stride(by: 1.weeks, through: date + 52.weeks)
has a certain appeal.
The types on that expression need to be very carefully thought about, because ā52 weeksā is not a TimeInterval and I donāt think we have a type that can describe it yet.
Itās also insufficient to write date.stride(by: 1.weeks, through: date + 52.weeks)
as the meaning (and result) of the operation depends on the calendar the date is being interpreted in.
Right. You canāt really make the API for advancing dates any simpler than Calendar
, or thatās what they would have done in the first place. This isnāt the first version of the Calendar API - its been revised a couple of times over the years. That said, you can wrap those APIs to give you a more convenient interface for your needs - thatās why I recommended encapsulating the Foundation API in an iterator.
So I had a little go at a quick version of an iterator:
import Foundation
extension Calendar {
func makeIterator(from date: Date, until: Date?, every components: DateComponents) -> Calendar.DateComponentsIterator {
return DateComponentsIterator(date: date, calendar: self, cutoff: until, components: components)
}
struct DateComponentsIterator: IteratorProtocol {
var date: Date?
let calendar: Calendar
let cutoff: Date?
let components: DateComponents
mutating func next() -> Date? {
guard let nextDate = self.date else { return nil } // Ended.
self.date = calendar.date(byAdding: components, to: nextDate)
if let advancedDate = self.date, let cutoff = self.cutoff, advancedDate > cutoff { self.date = nil }
return nextDate
}
}
}
And it seems to work well enough:
var components = NSDateComponents(); components.day = 1
var iter = Calendar.current.makeIterator(from: Date(), until: Date().addingTimeInterval(10_000_000),
every: components as DateComponents)
IteratorSequence(iter).forEach { print($0) }
// outputs this:
2018-01-22 20:36:42 +0000
2018-01-23 20:36:42 +0000
2018-01-24 20:36:42 +0000
2018-01-25 20:36:42 +0000
2018-01-26 20:36:42 +0000
2018-01-27 20:36:42 +0000
2018-01-28 20:36:42 +0000
2018-01-29 20:36:42 +0000
2018-01-30 20:36:42 +0000
... etc
nextDate(after:matching:matchingPolicy:repeatedTimePolicy:direction:)
attempts to find the next date which matches the components youāre setting, not by adding them ā what youāre asking for is "give me the next date whose minute is 01
", which indeed happens once an hour. Asking for the list of dates whose hours are 03
will similarly iterate once a day, etc.
What your iterator should use is date(byAdding:to:wrappingComponents:)
or date(byAdding:value:to:wrappingComponents:)
, which allows you to add 1 minute at a time, 1 hour at a time, etc.
š¤¦āāļø Thanks, fixed!
Iām going to defer to @davedelong on this, but it might be interesting to have a date instance that can be set with reference to a calendar, so that doing date offsetting its components by +1 week
would always be with respect to that calendar used at initialization. I can think of many many cases where a date would stick to a single calendar for the lifetime of the instance and its persistance/rehydration. Can you think of non-esoteric use-cases for when a date would move between calendars?