Commit da728ff0 by Demid Merzlyakov

Fix partial weather updates.

parent 8bd27528
...@@ -36,10 +36,10 @@ struct WdtDailySummariesArray: Codable { ...@@ -36,10 +36,10 @@ struct WdtDailySummariesArray: Codable {
self.items = rawItems self.items = rawItems
} }
func toAppModel(timeZone: TimeZone, updatedAt: Date) throws -> [DailyWeather] { func toAppModel(context originalLocation: Location, updatedAt: Date) throws -> [DailyWeather] {
var result = [DailyWeather]() var result = [DailyWeather]()
for item in items { for item in items {
if let appModel = item.toAppModel(timeZone: timeZone, updatedAt: updatedAt) { if let appModel = item.toAppModel(context: originalLocation, updatedAt: updatedAt) {
result.append(appModel) result.append(appModel)
} }
} }
......
...@@ -39,10 +39,10 @@ struct WdtHourlySummariesArray: Codable { ...@@ -39,10 +39,10 @@ struct WdtHourlySummariesArray: Codable {
self.items = rawItems self.items = rawItems
} }
func toAppModel(timeZone: TimeZone, updatedAt: Date) throws -> [HourlyWeather] { func toAppModel(context originalLocation: Location, updatedAt: Date) throws -> [HourlyWeather] {
var result = [HourlyWeather]() var result = [HourlyWeather]()
for item in items { for item in items {
if let appModel = item.toAppModel(timeZone: timeZone, updatedAt: updatedAt) { if let appModel = item.toAppModel(context: originalLocation, updatedAt: updatedAt) {
result.append(appModel) result.append(appModel)
} }
} }
......
...@@ -46,7 +46,8 @@ struct WdtDailySummary: Codable { ...@@ -46,7 +46,8 @@ struct WdtDailySummary: Codable {
private static let log = Logger(componentName: "WdtDailySummary") private static let log = Logger(componentName: "WdtDailySummary")
public func toAppModel(timeZone: TimeZone, updatedAt: Date) -> DailyWeather? { public func toAppModel(context originalLocation: Location, updatedAt: Date) -> DailyWeather? {
let timeZone = originalLocation.timeZone
let log = WdtDailySummary.log let log = WdtDailySummary.log
let dateFormatter = DateFormatter() let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy" dateFormatter.dateFormat = "MM/dd/yyyy"
......
...@@ -9,8 +9,8 @@ import Foundation ...@@ -9,8 +9,8 @@ import Foundation
struct WdtHourlySummary: Codable { struct WdtHourlySummary: Codable {
public var dateTimeLocal: String public var dateTimeLocal: String
public var weekDay: String public var weekDay: String?
public var dayNight: String public var dayNight: String?
public var tempF: String? public var tempF: String?
public var apparentTempF: String? public var apparentTempF: String?
...@@ -36,7 +36,8 @@ struct WdtHourlySummary: Codable { ...@@ -36,7 +36,8 @@ struct WdtHourlySummary: Codable {
private static let log = Logger(componentName: "WdtHourlySummary") private static let log = Logger(componentName: "WdtHourlySummary")
public func toAppModel(timeZone: TimeZone, updatedAt: Date) -> HourlyWeather? { public func toAppModel(context originalLocation: Location, updatedAt: Date) -> HourlyWeather? {
let timeZone = originalLocation.timeZone
let log = WdtHourlySummary.log let log = WdtHourlySummary.log
let dateFormatter = DateFormatter() let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
...@@ -46,29 +47,44 @@ struct WdtHourlySummary: Codable { ...@@ -46,29 +47,44 @@ struct WdtHourlySummary: Codable {
log.error("Failed to parse date from: \(dateTimeLocal)") log.error("Failed to parse date from: \(dateTimeLocal)")
return nil return nil
} }
var weekDay = WeekDay(rawValue: self.weekDay) var optionalResult: HourlyWeather? = nil
if weekDay == nil { // TODO: this is super slow...
var calendar = Calendar(identifier: .gregorian) if let existingHourly = originalLocation.hourly.first(where: {$0.date == date}) {
calendar.timeZone = timeZone optionalResult = existingHourly
weekDay = WeekDay(grigorianWeekDayNumber: calendar.component(.weekday, from: date)) }
else {
guard let weekDayFromXml = self.weekDay, let dayNightFromXml = self.dayNight else {
// this is a partial update, but there's no matching hourly data in the originalLocation, so we just skip.
return nil
}
var weekDay = WeekDay(rawValue: weekDayFromXml)
if weekDay == nil {
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = timeZone
weekDay = WeekDay(grigorianWeekDayNumber: calendar.component(.weekday, from: date))
}
guard let weekDayUnwrapped = weekDay else {
log.error("Couldn't parse week day: \(weekDayFromXml)")
return nil
}
let isDay = dayNightFromXml.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "day"
optionalResult = HourlyWeather(lastTimeUpdated: updatedAt, date: date, timeZone: timeZone, weekDay: weekDayUnwrapped, isDay: isDay)
} }
guard let weekDayUnwrapped = weekDay else { guard var result = optionalResult else {
log.error("Couldn't parse week day: \(self.weekDay)")
return nil return nil
} }
let isDay = dayNight.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "day"
var result = HourlyWeather(lastTimeUpdated: updatedAt, date: date, timeZone: timeZone, weekDay: weekDayUnwrapped, isDay: isDay)
if let weatherCode = self.weatherCode { if let weatherCode = self.weatherCode {
result.type = WdtWeatherCode(rawValue: weatherCode)?.toAppModel() ?? .unknown result.type = WdtWeatherCode(rawValue: weatherCode)?.toAppModel() ?? result.type
} }
result.temp = Temperature(valueString: tempF, unit: .fahrenheit)
result.apparentTemp = Temperature(valueString: apparentTempF, unit: .fahrenheit) result.temp = Temperature(valueString: tempF, unit: .fahrenheit) ?? result.temp
result.apparentTemp = Temperature(valueString: apparentTempF, unit: .fahrenheit) ?? result.apparentTemp
result.windSpeed = WindSpeed(valueString: self.windSpeedKph, unit: .kilometersPerHour) result.windSpeed = WindSpeed(valueString: self.windSpeedKph, unit: .kilometersPerHour) ?? result.windSpeed
result.windDirection = WindDirection(rawValue: windDirection ?? "") result.windDirection = WindDirection(rawValue: windDirection ?? "") ?? result.windDirection
if let precipitationProbability = Percent(self.precipitationProbability ?? ""), precipitationProbability >= 0, precipitationProbability <= 100 { if let precipitationProbability = Percent(self.precipitationProbability ?? ""), precipitationProbability >= 0, precipitationProbability <= 100 {
result.precipitationProbability = precipitationProbability result.precipitationProbability = precipitationProbability
......
...@@ -110,16 +110,17 @@ struct WdtLocation: Codable { ...@@ -110,16 +110,17 @@ struct WdtLocation: Codable {
return today return today
} }
public func toAppModel(timeZone: TimeZone, updatedAt: Date) throws -> Location { public func toAppModel(context originalLocation: Location, updatedAt: Date) throws -> Location {
let timeZone = originalLocation.timeZone
var coordinates: CLLocationCoordinate2D? = nil var coordinates: CLLocationCoordinate2D? = nil
if let lat = CLLocationDegrees(lat ?? ""), let lon = CLLocationDegrees(lon ?? "") { if let lat = CLLocationDegrees(lat ?? ""), let lon = CLLocationDegrees(lon ?? "") {
coordinates = CLLocationCoordinate2D(latitude: lat, longitude: lon) coordinates = CLLocationCoordinate2D(latitude: lat, longitude: lon)
} }
let dailyWeather = try dailySummaries?.toAppModel(timeZone: timeZone, updatedAt: updatedAt) ?? [DailyWeather]() let dailyWeather = try dailySummaries?.toAppModel(context: originalLocation, updatedAt: updatedAt) ?? [DailyWeather]()
let hourlyWeather = try hourlySummaries?.toAppModel(timeZone: timeZone, updatedAt: updatedAt) ?? [HourlyWeather]() let hourlyWeather = try hourlySummaries?.toAppModel(context: originalLocation, updatedAt: updatedAt) ?? [HourlyWeather]()
var todayOpt = surfaceObservation.toAppModel(timeZone: timeZone, updatedAt: updatedAt) var todayOpt = surfaceObservation.toAppModel(context: originalLocation, updatedAt: updatedAt)
if let today = todayOpt { if let today = todayOpt {
todayOpt = enhance(today: today, from: dailyWeather, using: timeZone) todayOpt = enhance(today: today, from: dailyWeather, using: timeZone)
} }
......
...@@ -8,9 +8,9 @@ ...@@ -8,9 +8,9 @@
import Foundation import Foundation
struct WdtSurfaceObservation: Codable { struct WdtSurfaceObservation: Codable {
public var dateTimeLocal: String public var dateTimeLocal: String? // can be nil for MICRO (partial) update
public var weekDay: String public var weekDay: String? // can be nil for MICRO (partial) update
public var dayNight: String public var dayNight: String? // can be nil for MICRO (partial) update
public var tempF: String? // Currently XMLCoder seems to struggle with decoding optional doubles with an empty value: <temp_F></temp_F> won't be decoded as nil, instead decoding will fail. So, using Strings for now. public var tempF: String? // Currently XMLCoder seems to struggle with decoding optional doubles with an empty value: <temp_F></temp_F> won't be decoded as nil, instead decoding will fail. So, using Strings for now.
public var apparentTempF: String? public var apparentTempF: String?
...@@ -42,30 +42,61 @@ struct WdtSurfaceObservation: Codable { ...@@ -42,30 +42,61 @@ struct WdtSurfaceObservation: Codable {
private static let log = Logger(componentName: "WdtSurfaceObservation") private static let log = Logger(componentName: "WdtSurfaceObservation")
public func toAppModel(timeZone: TimeZone, updatedAt: Date) -> CurrentWeather? { private func guessWeekDay(currentDate: Date, timeZone: TimeZone) -> WeekDay? {
var weekDay: WeekDay?
if let weekDayFromXml = self.weekDay {
weekDay = WeekDay(rawValue: weekDayFromXml)
}
if weekDay == nil {
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = timeZone
weekDay = WeekDay(grigorianWeekDayNumber: calendar.component(.weekday, from: currentDate))
}
return weekDay
}
private func guessIsDay(currentDate: Date, context originalLocation: Location) -> Bool {
if let dayNightFromXml = self.dayNight {
return dayNightFromXml.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "day"
}
if let currentDayInDaily = originalLocation.daily.first(where: { $0.date <= currentDate && currentDate < $0.date.addingTimeInterval(24 * 3600) }) {
if currentDayInDaily.sunState == CelestialState.alwaysUp {
return true
}
if currentDayInDaily.sunState == CelestialState.neverUp {
return false
}
if let sunrise = currentDayInDaily.sunrise, let sunset = currentDayInDaily.sunset {
return sunrise <= currentDate && currentDate <= sunset
}
}
// TODO: do we need a more sophisticated guessing here?
return originalLocation.today?.isDay ?? true
}
public func toAppModel(context originalLocation: Location, updatedAt: Date) -> CurrentWeather? {
let timeZone = originalLocation.timeZone
let log = WdtSurfaceObservation.log let log = WdtSurfaceObservation.log
let dateFormatter = DateFormatter() let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = timeZone dateFormatter.timeZone = timeZone
guard let date = dateFormatter.date(from: self.dateTimeLocal) else { var date: Date = Date() // dateTimeLocal will be nil in case of MICRO (partial) update.
log.error("failed to parse date from: \(self.dateTimeLocal)") if let dateStringFromXml = self.dateTimeLocal {
return nil guard let dateFromXml = dateFormatter.date(from: dateStringFromXml) else {
} log.error("failed to parse date from: \(dateStringFromXml)")
var weekDay = WeekDay(rawValue: self.weekDay) return nil
if weekDay == nil { }
var calendar = Calendar(identifier: .gregorian) date = dateFromXml
calendar.timeZone = timeZone
weekDay = WeekDay(grigorianWeekDayNumber: calendar.component(.weekday, from: date))
} }
guard let weekDayUnwrapped = weekDay else { guard let weekDay = guessWeekDay(currentDate: date, timeZone: timeZone) else {
log.error("Couldn't parse weekDay from: \(self.weekDay)") log.error("Couldn't parse weekDay from: \(self.weekDay ?? "<nil>")")
return nil return nil
} }
let isDay = dayNight.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "day" let isDay = guessIsDay(currentDate: date, context: originalLocation)
var result = CurrentWeather(lastTimeUpdated: updatedAt, date: date, timeZone: timeZone, weekDay: weekDayUnwrapped, isDay: isDay) var result = CurrentWeather(lastTimeUpdated: updatedAt, date: date, timeZone: timeZone, weekDay: weekDay, isDay: isDay)
if let weatherCode = self.weatherCode { if let weatherCode = self.weatherCode {
result.type = WdtWeatherCode(rawValue: weatherCode)?.toAppModel() ?? .unknown result.type = WdtWeatherCode(rawValue: weatherCode)?.toAppModel() ?? .unknown
} }
...@@ -81,7 +112,7 @@ struct WdtSurfaceObservation: Codable { ...@@ -81,7 +112,7 @@ struct WdtSurfaceObservation: Codable {
result.pressure = Pressure(valueString: pressureMb, unit: .millibars) result.pressure = Pressure(valueString: pressureMb, unit: .millibars)
// Doesn't it make life great when the server sends local time, but doesn't send a timezone? // Doesn't it make life great when the server sends local time, but doesn't send a timezone?
if let localDateWithoutTime = dateTimeLocal.split(separator: " ").first { if let dateTimeLocal = self.dateTimeLocal, let localDateWithoutTime = dateTimeLocal.split(separator: " ").first {
if let sunriseLocalTime = self.sunriseLocalTime, let sunsetLocalTime = self.sunsetLocalTime { if let sunriseLocalTime = self.sunriseLocalTime, let sunsetLocalTime = self.sunsetLocalTime {
let sunriseLocalDateTime = "\(localDateWithoutTime) \(sunriseLocalTime)" let sunriseLocalDateTime = "\(localDateWithoutTime) \(sunriseLocalTime)"
let sunsetLocalDateTime = "\(localDateWithoutTime) \(sunsetLocalTime)" let sunsetLocalDateTime = "\(localDateWithoutTime) \(sunsetLocalTime)"
......
...@@ -127,7 +127,7 @@ public class WdtWeatherSource: WeatherSource { ...@@ -127,7 +127,7 @@ public class WdtWeatherSource: WeatherSource {
return nil return nil
} }
let updateTime = Date() let updateTime = Date()
let incrementalChanges = try wdtLocation.toAppModel(timeZone: location.timeZone, updatedAt: updateTime) let incrementalChanges = try wdtLocation.toAppModel(context: location, updatedAt: updateTime)
let updatedLocation = location.mergedWith(incrementalChanges: incrementalChanges) let updatedLocation = location.mergedWith(incrementalChanges: incrementalChanges)
return updatedLocation return updatedLocation
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment