Skip to content

Commit bb613fa

Browse files
committed
Create TomorrowRepository
1 parent 1547bba commit bb613fa

2 files changed

Lines changed: 194 additions & 0 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package io.github.tobyhs.weatherweight.data
2+
3+
import io.github.tobyhs.weatherweight.api.tomorrow.TomorrowService
4+
import io.github.tobyhs.weatherweight.data.model.DailyForecast
5+
import io.github.tobyhs.weatherweight.data.model.ForecastResultSet
6+
import io.github.tobyhs.weatherweight.data.model.HourlyForecast
7+
8+
import kotlinx.coroutines.CoroutineDispatcher
9+
import kotlinx.coroutines.withContext
10+
11+
import java.time.Clock
12+
import java.time.ZonedDateTime
13+
14+
import kotlin.math.roundToInt
15+
16+
/**
17+
* Implementation of [WeatherCoroutinesRepository] that uses tomorrow.io' APIs
18+
*/
19+
class TomorrowRepository(
20+
private val tomorrowService: TomorrowService,
21+
private val clock: Clock,
22+
private val ioDispatcher: CoroutineDispatcher,
23+
) : WeatherCoroutinesRepository {
24+
override suspend fun getForecast(location: String): ForecastResultSet = withContext(ioDispatcher) {
25+
val forecast = tomorrowService.getForecast(location)
26+
ForecastResultSet(
27+
location = forecast.location.name,
28+
publicationTime = ZonedDateTime.now(clock),
29+
dailyForecasts = convertDailyForecasts(forecast.timelines.daily),
30+
hourlyForecasts = convertHourlyForecasts(forecast.timelines.hourly),
31+
)
32+
}
33+
34+
private fun convertDailyForecasts(
35+
dailyForecasts: List<io.github.tobyhs.weatherweight.api.tomorrow.forecast.DailyForecast>
36+
): List<DailyForecast> = dailyForecasts.map { dailyForecast ->
37+
val descriptionMin = convertWeatherCode(dailyForecast.values.weatherCodeMin)
38+
val descriptionMax = convertWeatherCode(dailyForecast.values.weatherCodeMax)
39+
val description = if (descriptionMin == descriptionMax) {
40+
descriptionMin
41+
} else {
42+
"$descriptionMin / $descriptionMax"
43+
}
44+
DailyForecast(
45+
date = dailyForecast.time.toLocalDate(),
46+
low = dailyForecast.values.temperatureMin.roundToInt(),
47+
high = dailyForecast.values.temperatureMax.roundToInt(),
48+
text = description,
49+
precipitationProbability = dailyForecast.values.precipitationProbabilityMax,
50+
)
51+
}
52+
53+
private fun convertHourlyForecasts(
54+
hourlyForecasts: List<io.github.tobyhs.weatherweight.api.tomorrow.forecast.HourlyForecast>
55+
): List<HourlyForecast> = hourlyForecasts.map { hourlyForecast ->
56+
HourlyForecast(
57+
dateTime = hourlyForecast.time,
58+
text = convertWeatherCode(hourlyForecast.values.weatherCode),
59+
temperature = hourlyForecast.values.temperature.roundToInt(),
60+
precipitationProbability = hourlyForecast.values.precipitationProbability,
61+
)
62+
}
63+
64+
private fun convertWeatherCode(weatherCode: Int): String {
65+
return TOMORROW_WEATHER_CODES.getOrDefault(weatherCode, "Unknown")
66+
}
67+
}
68+
69+
// From https://docs.tomorrow.io/reference/data-layers-weather-codes
70+
private val TOMORROW_WEATHER_CODES = mapOf(
71+
0 to "Unknown",
72+
1000 to "Clear, Sunny",
73+
1100 to "Mostly Clear",
74+
1101 to "Partly Cloudy",
75+
1102 to "Mostly Cloudy",
76+
1001 to "Cloudy",
77+
2000 to "Fog",
78+
2100 to "Light Fog",
79+
4000 to "Drizzle",
80+
4001 to "Rain",
81+
4200 to "Light Rain",
82+
4201 to "Heavy Rain",
83+
5000 to "Snow",
84+
5001 to "Flurries",
85+
5100 to "Light Snow",
86+
5101 to "Heavy Snow",
87+
6000 to "Freezing Drizzle",
88+
6001 to "Freezing Rain",
89+
6200 to "Light Freezing Rain",
90+
6201 to "Heavy Freezing Rain",
91+
7000 to "Ice Pellets",
92+
7101 to "Heavy Ice Pellets",
93+
7102 to "Light Ice Pellets",
94+
8000 to "Thunderstorm",
95+
)
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package io.github.tobyhs.weatherweight.data
2+
3+
import io.github.tobyhs.weatherweight.api.tomorrow.TomorrowService
4+
import io.github.tobyhs.weatherweight.api.tomorrow.forecast.DailyForecast
5+
import io.github.tobyhs.weatherweight.api.tomorrow.forecast.DailyForecastValues
6+
import io.github.tobyhs.weatherweight.api.tomorrow.forecast.ForecastResponse
7+
import io.github.tobyhs.weatherweight.api.tomorrow.forecast.ForecastTimelines
8+
import io.github.tobyhs.weatherweight.api.tomorrow.forecast.HourlyForecast
9+
import io.github.tobyhs.weatherweight.api.tomorrow.forecast.HourlyForecastValues
10+
import io.github.tobyhs.weatherweight.api.tomorrow.forecast.Location
11+
12+
import io.mockk.coEvery
13+
import io.mockk.every
14+
import io.mockk.mockk
15+
16+
import kotlinx.coroutines.test.StandardTestDispatcher
17+
import kotlinx.coroutines.test.runTest
18+
19+
import org.hamcrest.CoreMatchers.equalTo
20+
import org.hamcrest.MatcherAssert.assertThat
21+
22+
import org.junit.Test
23+
24+
import java.time.Clock
25+
import java.time.ZoneOffset
26+
import java.time.ZonedDateTime
27+
28+
class TomorrowRepositoryTest {
29+
private val tomorrowService = mockk<TomorrowService>()
30+
private val clock = mockk<Clock>()
31+
private val testDispatcher = StandardTestDispatcher()
32+
private val repository = TomorrowRepository(tomorrowService, clock, testDispatcher)
33+
34+
@Test
35+
fun getForecast() = runTest(testDispatcher) {
36+
val time = ZonedDateTime.parse("2025-09-08T12:00:00Z")
37+
every { clock.instant() } returns time.toInstant()
38+
every { clock.zone } returns ZoneOffset.UTC
39+
val forecastResponse = ForecastResponse(
40+
timelines = ForecastTimelines(
41+
daily = listOf(
42+
DailyForecast(
43+
time = time,
44+
values = DailyForecastValues(
45+
temperatureMin = 65.8,
46+
temperatureMax = 70.2,
47+
precipitationProbabilityMax = 5,
48+
weatherCodeMin = 1100,
49+
weatherCodeMax = 1101,
50+
)
51+
),
52+
DailyForecast(
53+
time = time.plusDays(1),
54+
values = DailyForecastValues(
55+
temperatureMin = 66.0,
56+
temperatureMax = 72.0,
57+
precipitationProbabilityMax = 2,
58+
weatherCodeMin = 2000,
59+
weatherCodeMax = 2000,
60+
)
61+
)
62+
),
63+
hourly = listOf(
64+
HourlyForecast(
65+
time = time,
66+
values = HourlyForecastValues(
67+
temperature = 67.0,
68+
precipitationProbability = 2,
69+
weatherCode = 2000,
70+
)
71+
)
72+
)
73+
),
74+
location = Location(name = "San Francisco, California, United States")
75+
)
76+
val location = "San Francisco"
77+
coEvery { tomorrowService.getForecast(location) } returns forecastResponse
78+
79+
val result = repository.getForecast(location)
80+
assertThat(result.location, equalTo("San Francisco, California, United States"))
81+
assertThat(result.publicationTime, equalTo(time))
82+
83+
var dailyForecast = result.dailyForecasts[0]
84+
assertThat(dailyForecast.date.toString(), equalTo("2025-09-08"))
85+
assertThat(dailyForecast.low, equalTo(66))
86+
assertThat(dailyForecast.high, equalTo(70))
87+
assertThat(dailyForecast.precipitationProbability, equalTo(5))
88+
assertThat(dailyForecast.text, equalTo("Mostly Clear / Partly Cloudy"))
89+
dailyForecast = result.dailyForecasts[1]
90+
assertThat(dailyForecast.text, equalTo("Fog"))
91+
92+
assertThat(result.hourlyForecasts[0], equalTo(io.github.tobyhs.weatherweight.data.model.HourlyForecast(
93+
dateTime = time,
94+
text = "Fog",
95+
temperature = 67,
96+
precipitationProbability = 2,
97+
)))
98+
}
99+
}

0 commit comments

Comments
 (0)