using System.Collections.Generic;
using System.Threading.Tasks;
public static async Task Main()
var cityWeather = new CityWeather();
await cityWeather.ExportCityWeatherAsync();
private const string OPEN_WEATHER_BASE_URL = "https://api.openweathermap.org";
private const string FORECAST_ROUTE = "/data/2.5/forecast";
private const string API_KEY = "00f529dd202ee16b980ec0ead48f78d5";
private readonly HttpClient _openWeatherClient;
_openWeatherClient = new HttpClient
BaseAddress = new Uri(OPEN_WEATHER_BASE_URL),
public async Task ExportCityWeatherAsync()
var cities = GetInputCities();
foreach (var city in cities)
var zipCode = GetZipCode(city.Key, city.Value);
var fiveDayForecast = await GetFiveDayForcastForCity(city.Key, city.Value);
PrintOutput(city.Key, city.Value, zipCode, fiveDayForecast);
private static void PrintOutput(string city, string state, string zipCode, List<DailyCityForcast> forecastData)
Console.WriteLine("______________________________");
Console.WriteLine($"{city}, {state} ({zipCode})");
Console.WriteLine($"{"Date".PadRight(15)} Avg Temp (F)");
Console.WriteLine("------------------------------");
foreach(var dailyForecast in forecastData)
var date = dailyForecast.Date;
if(dailyForecast.HasPrecipitation)
Console.WriteLine($"{date.PadRight(15)} {dailyForecast.AvgTemp} F");
private static string GetZipCode(string city, string state)
if (city == "Marlboro" && state == "MA") return "01752";
if (city == "San Diego" && state == "CA") return "92112";
var zipCodeObj = Zip2City.AllCityStateZips.Where(x => x[0] == city.ToUpper() && x[1] == state).FirstOrDefault();
throw new Exception($"No zip code was found for {city}, {state}.");
private async Task<List<DailyCityForcast>> GetFiveDayForcastForCity(string city, string state)
var fiveDayForcastExtensive = await GetFiveDayForcastFromWebAsync(city, state);
var foreCastGroups = fiveDayForcastExtensive.Forecasts!.GroupBy(x => Convert.ToDateTime(x.Date).Day);
var fiveDayForecast = new List<DailyCityForcast>();
foreach(var forecastGrp in foreCastGroups)
var date = forecastGrp.FirstOrDefault()!.Date;
var avgTemp = forecastGrp.Select(x => x.TemperatureData!.Temperature).Average();
var timesWithPrecip = forecastGrp.Count(
x => x.WeatherData!.FirstOrDefault()!.WeatherType == "Rain" || x.WeatherData!.FirstOrDefault()!.WeatherType == "Snow");
var isPrecipitation = timesWithPrecip > 0;
var dailyCityForecast = new DailyCityForcast(date, avgTemp, isPrecipitation);
fiveDayForecast.Add(dailyCityForecast);
if (fiveDayForecast.Count < 5)
throw new Exception($"Could not retrieve all 5 days. Only {fiveDayForecast.Count} forecast days were available.");
if (fiveDayForecast.Count > 5)
fiveDayForecast.RemoveAt(0);
private async Task<FiveDayForecast> GetFiveDayForcastFromWebAsync(string city, string state, string country = "us", string units = "imperial")
var requestUri = $"{FORECAST_ROUTE}" +
$"?q={city},{state},{country}&units={units}&appid={API_KEY}";
var response = await _openWeatherClient.GetAsync(requestUri);
if(!response.IsSuccessStatusCode)
throw new Exception($"There was an error processing the http request. Status Code: {response.StatusCode}");
var responseContent = await response.Content.ReadAsStringAsync();
var fiveDayForecast = JsonConvert.DeserializeObject<FiveDayForecast>(responseContent);
if(fiveDayForecast == null || fiveDayForecast.Forecasts == null || !fiveDayForecast.Forecasts.Any())
throw new Exception($"No forecast data was found for {city}, {state}.");
private static Dictionary<string, string> GetInputCities()
return new Dictionary<string, string>()
public class DailyCityForcast
public DailyCityForcast(DateTime? date, double avgTemp, bool precipitation)
Date = Convert.ToDateTime(date).Date.ToShortDateString();
AvgTemp = Math.Round(avgTemp, 2);
HasPrecipitation = precipitation;
public string Date { get; private set; }
public double AvgTemp { get; private set; }
public bool HasPrecipitation { get; private set; }
public class FiveDayForecast
public string Cod { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
public CityInfo CityInfo { get; set; }
public List<ThreeHourForcast> Forecasts { get; set; }
public class ThreeHourForcast
public DateTime Date { get; set; }
public TemperatureData TemperatureData { get; set; }
[JsonProperty("weather")]
public List<WeatherData> WeatherData { get; set; }
public class TemperatureData
public double Temperature { get; set; }
public string WeatherType { get; set; }
public string Name { get; set; }
[JsonProperty("country")]
public string Country { get; set; }