Best way to deal with logic that uses "now" i.e. Date()

I'm writing an app that shows the launches from SpaceX. Just a pet project for now to help me learn TCA.

One of my reqs is to put the "Days from/since now" in the view. I've done that but it uses Date() in the view logic. (and Calendar but it's all part of the same problem).

So, I moved the calculation into a LaunchViewState struct which takes a Launch in the init.

So in my AppState I have...

struct AppState {
    var launches: IdentifiedArrayOf<Launch> = []
    var sortedLaunches: IdentifiedArrayOf<LaunchViewState> {
        let sortedArray = self.launches
            .sorted(by: \Launch.launchDate, using: >)
            .map(LaunchViewState(launch:))
        
        return IdentifiedArray(sortedArray)
    }

    // other stuff...
}

The LaunchViewState init method then uses Date() and Calendar.current to calculate the number of days until/since the launch.

My problem is that I don't know how or when to inject the date for "now" in order to make this testable and consistent.

I have a feeling I should be adding a date property to my Environment. But even then, I'm not sure when I would inject it?

The sortedLaunches are not created from an action so I can't inject it from the reducer.

Please could someone provide some guidance on the possible ways of doing this?

Thanks

1 Like

The sortedLaunches are not created from an action so I can't inject it from the reducer.

Hmm...

OK, thinking about this further...

Why is the sortedLaunches a computed var?

It's computed because it depends on several things. The list of launches. Some other state that goes into it. And the sort method (which is also in state). There are also some filters that can be selected and so this sortedLaunches will actually be a filtered list also.

So... if I know everything that can affect the sortedLaunches I could also create an action like sortAndFilterLaunches that will calculate what the sorted filtered launches are.

Then when any of the state changes that my sort and filter depends on I can return the new action.

If I do that then the action will run and send me into the reducer. At which point I could run a function like sortAndFilterLaunches(state.launches, state.filterStuff, environment.calendar, environment.now)

This means that I can finally get hold of the now and inject it into the LaunchViewState initialiser and I've solved the problem.

In the tests I can set a fixed "now".

:boom: problem solved :smiley:

1 Like

Another option is to think of generating relative date strings as any other date formatting task. The NSRelativeDateTimeFormatter seems tailor made for your use case.

However, it appears that we cannot pass "now" into the formatter. Then again, this might not be an issue as we usually do not test foundation date formatters, in general.

2 Likes