Commit da728ff0 by Demid Merzlyakov

Fix partial weather updates.

parent 8bd27528
......@@ -36,10 +36,10 @@ struct WdtDailySummariesArray: Codable {
self.items = rawItems
}
func toAppModel(timeZone: TimeZone, updatedAt: Date) throws -> [DailyWeather] {
func toAppModel(context originalLocation: Location, updatedAt: Date) throws -> [DailyWeather] {
var result = [DailyWeather]()
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)
}
}
......
......@@ -39,10 +39,10 @@ struct WdtHourlySummariesArray: Codable {
self.items = rawItems
}
func toAppModel(timeZone: TimeZone, updatedAt: Date) throws -> [HourlyWeather] {
func toAppModel(context originalLocation: Location, updatedAt: Date) throws -> [HourlyWeather] {
var result = [HourlyWeather]()
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)
}
}
......
......@@ -46,7 +46,8 @@ struct WdtDailySummary: Codable {
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 dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy"
......
......@@ -9,8 +9,8 @@ import Foundation
struct WdtHourlySummary: Codable {
public var dateTimeLocal: String
public var weekDay: String
public var dayNight: String
public var weekDay: String?
public var dayNight: String?
public var tempF: String?
public var apparentTempF: String?
......@@ -36,7 +36,8 @@ struct WdtHourlySummary: Codable {
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 dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
......@@ -46,29 +47,44 @@ struct WdtHourlySummary: Codable {
log.error("Failed to parse date from: \(dateTimeLocal)")
return nil
}
var weekDay = WeekDay(rawValue: self.weekDay)
if weekDay == nil {
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = timeZone
weekDay = WeekDay(grigorianWeekDayNumber: calendar.component(.weekday, from: date))
var optionalResult: HourlyWeather? = nil
// TODO: this is super slow...
if let existingHourly = originalLocation.hourly.first(where: {$0.date == date}) {
optionalResult = existingHourly
}
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 {
log.error("Couldn't parse week day: \(self.weekDay)")
guard var result = optionalResult else {
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 {
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.windDirection = WindDirection(rawValue: windDirection ?? "")
result.windSpeed = WindSpeed(valueString: self.windSpeedKph, unit: .kilometersPerHour) ?? result.windSpeed
result.windDirection = WindDirection(rawValue: windDirection ?? "") ?? result.windDirection
if let precipitationProbability = Percent(self.precipitationProbability ?? ""), precipitationProbability >= 0, precipitationProbability <= 100 {
result.precipitationProbability = precipitationProbability
......
......@@ -110,16 +110,17 @@ struct WdtLocation: Codable {
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
if let lat = CLLocationDegrees(lat ?? ""), let lon = CLLocationDegrees(lon ?? "") {
coordinates = CLLocationCoordinate2D(latitude: lat, longitude: lon)
}
let dailyWeather = try dailySummaries?.toAppModel(timeZone: timeZone, updatedAt: updatedAt) ?? [DailyWeather]()
let hourlyWeather = try hourlySummaries?.toAppModel(timeZone: timeZone, updatedAt: updatedAt) ?? [HourlyWeather]()
let dailyWeather = try dailySummaries?.toAppModel(context: originalLocation, updatedAt: updatedAt) ?? [DailyWeather]()
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 {
todayOpt = enhance(today: today, from: dailyWeather, using: timeZone)
}
......
......@@ -8,9 +8,9 @@
import Foundation
struct WdtSurfaceObservation: Codable {
public var dateTimeLocal: String
public var weekDay: String
public var dayNight: String
public var dateTimeLocal: String? // can be nil for MICRO (partial) update
public var weekDay: String? // can be nil for MICRO (partial) update
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 apparentTempF: String?
......@@ -42,30 +42,61 @@ struct WdtSurfaceObservation: Codable {
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 dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = timeZone
guard let date = dateFormatter.date(from: self.dateTimeLocal) else {
log.error("failed to parse date from: \(self.dateTimeLocal)")
return nil
}
var weekDay = WeekDay(rawValue: self.weekDay)
if weekDay == nil {
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = timeZone
weekDay = WeekDay(grigorianWeekDayNumber: calendar.component(.weekday, from: date))
var date: Date = Date() // dateTimeLocal will be nil in case of MICRO (partial) update.
if let dateStringFromXml = self.dateTimeLocal {
guard let dateFromXml = dateFormatter.date(from: dateStringFromXml) else {
log.error("failed to parse date from: \(dateStringFromXml)")
return nil
}
date = dateFromXml
}
guard let weekDayUnwrapped = weekDay else {
log.error("Couldn't parse weekDay from: \(self.weekDay)")
guard let weekDay = guessWeekDay(currentDate: date, timeZone: timeZone) else {
log.error("Couldn't parse weekDay from: \(self.weekDay ?? "<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 {
result.type = WdtWeatherCode(rawValue: weatherCode)?.toAppModel() ?? .unknown
}
......@@ -81,7 +112,7 @@ struct WdtSurfaceObservation: Codable {
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?
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 {
let sunriseLocalDateTime = "\(localDateWithoutTime) \(sunriseLocalTime)"
let sunsetLocalDateTime = "\(localDateWithoutTime) \(sunsetLocalTime)"
......
......@@ -127,7 +127,7 @@ public class WdtWeatherSource: WeatherSource {
return nil
}
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)
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