WeatherKit :: DailyForecast :: Sunrise :: ForEach :: Timezone Issue

My application engages the following:

  1. Generates and properly displays a selected location with MapKit.
  2. Generates SwiftUI WeatherKit information for a selected MapKit location.
  3. Generates correct Timezone WeatherKit [DayWeather] Sunrise and Sunset times within my timezone using the [ForEach] function.
  4. Generates incorrect Timezone WeatherKit [DayWeather] using the ForEach function, while retrieving Sunrise and Sunset times outside of my timezone.

I modified my MapKit application to locate and retrieve weather information at specified locations. The application correctly illustrates all the weather information views I want to display without issue for a MapKit selected location. One view exception presents itself specific to the [ForEach] function inside a WeatherKit view, which retrieves Sunrise and Sunset information. The retrieved Sunrise and Sunset times for a selected timezone location outside of my identified timezone are not correct. My application does not generate nor apply an incorrect timezone identifier, such as [Europe/Paris] anywhere within the application, but the following view code segment surely hiccups.

The selected timezone location view presents the Sunrise and Sunset time information as an equivalent time within my timezone, displayed in the images below.

The issue happens to be within the view's following ForEach function code:

Text(day.sun.sunrise?.formatted(.dateTime.hour().minute()) ?? "?")

For the moment, I am struggling to discover how to correct the above code to properly display the [Sunrise] and [Sunset] times within a [ForEach] function. I do not know whether this issue happens to be inherent to WeatherKit's ForEach function, but most likely I am not properly applying the code to reflect the selected location's timezone format within the [Text]. I have not found a solution to apply within the [ForEach] function to correct this issue, as the application iterates through the supplied WeatherKit DayWeather information.

I know I can retrieve the correct current TimeZone time for a selected location with the following code:

            func main() {
                let d = Date()
                //  Show the [VARIABLE f] as [HOUR / MINUTE] such as [12:23PM]
                var f = Date.FormatStyle.dateTime.hour().minute()
                print("The [LOCAL TIME ZONE TIME with MAIN] :: \(d.formatted(f))")
                f.timeZone = TimeZone(identifier: "Europe/Paris")!
                print("The [SELECTED TIME ZONE TIME with MAIN] :: \(d.formatted(f))\n")
            }
            //  Call the function
            main()

The above code does not solve my application's issue, because the code simply returns the selected location's current timezone time relative to my current timezone time. So, if my timezone time happens to be 1:40 PM, then the above code generates a timezone time for a selected location, such as Paris to be 9:40 PM. I know a [View] does not respond to the incremental function code, such as stated above.

As a side note :: My application displays the WeatherKit information through a Container and Hosting Controller, where the application's SwiftUI ContentView calls ::

if let dailyForecast {

TestForecastView(dailyForecast: dailyForecast, timezone: timezone)

}

If you have a moment, I appreciate your possible corrective suggestions, which would be very welcome ... :]

Best regards,

jim_k

My TestForecastView Code with the attached generated views follow:

import Foundation
import SwiftUI
import CoreLocation
import WeatherKit

struct TestForecastView: View {

    let dailyForecast: Forecast<DayWeather>
    let timezone: TimeZone
    var body: some View {
        Spacer()
            .frame(height: 10)
        Text("Sunrise Sunset")
            .font(.system(size: 24, weight: .bold))
            .foregroundStyle(.white)
        Text("Ten Day Forecast")
            .font(.system(size: 11, weight: .medium))
            .foregroundStyle(.white)
        Spacer()
            .frame(height: 4)
        VStack {
            ForEach(dailyForecast, id: \.date) { day in
                LabeledContent {
                    HStack(spacing: 20) {
                        RoundedRectangle(cornerRadius: 10)
                            .fill(Color.orange.opacity(0.5))
                            .frame(width: 120, height: 5)
                            .padding(.leading, 2)
                            .padding(.trailing, 0)
                        VStack {
                            Image(systemName: "sunrise")
                                .font(.system(size: 24.0, weight: .bold))
                                .foregroundColor(.yellow)
                            Text(day.sun.sunrise?.formatted(.dateTime.hour().minute()) ?? "?")
                                .font(.system(size: 10.0))
                        }
                        .frame(width: 50, height: 20)
                        VStack {
                            Image(systemName: "sunset")
                                .font(.system(size: 24.0, weight: .bold))
                                .foregroundColor(.yellow)
                            Text(day.sun.sunset?.formatted(.dateTime.hour().minute()) ?? "?")
                                .font(.system(size: 10.0))
                        }
                        .frame(width: 50, height: 20)
                        Spacer()
                            .frame(width: 2)
                    }
                } label: {
                    Text(day.date.localDate(for: timezone))
                        .frame(width: 80, alignment: .leading)
                        .padding(.leading, 30)
                        .padding(.trailing, 0)
                }
                .frame(width: 380, height: 52)
                .background(RoundedRectangle(cornerRadius: 4).fill(LinearGradient(gradient: Gradient(colors: [Color(.systemBlue), Color(.black)]), startPoint: .topLeading, endPoint: .bottomTrailing)).stroke(.black, lineWidth: 6).multilineTextAlignment(.center))
                .shadow(color: Color.white.opacity(0.1), radius: 4, x: -2, y: -2)
                .shadow(color: Color.white.opacity(0.1), radius: 4, x: 2, y: 2)
            }
        }
        .padding(.top, 20)
        .padding(.bottom, 20)
        .padding(.leading, 20)
        .padding(.trailing, 20)
        .contentMargins(.all, 4, for: .scrollContent)
        .background(RoundedRectangle(cornerRadius: 10).fill(Color.black.opacity(0.0)).stroke(.gray, lineWidth: 4))
    }   //  End of [var body: some View]
}   //  End of [struct TestForecastView: View]

My resultant code views ::

Calgary

Paris

Answered by Engineer in 797955022

Hi Jim,

To get the time in the local time zone, you must set the time zone you want on the Date.FormatStyle you pass to formatted.

Quinn has a nice example of that here: https://forums.developer.apple.com/forums/thread/700213.

If you want to do this inline, assuming your timezone variable is the time zone of the location you are displaying, you can make a helper function like this:

var dateFormat -> Date.FormatStyle {
    let formatStyle = Date.FormatStyle.dateTime.hour().minute() 
    formatStyle.timeZone = self.timezone
    return formatStyle
}

Then replace lines like this:

Text(day.sun.sunrise?.formatted(.dateTime.hour().minute()) ?? "?")

With something like this:

Text(day.sun.sunrise?.formatted(dateFormat) ?? "?")

Hope that helps!

My error calling ForEach a function ... :]

ForEach is really a Struct implementing a View protocol ... :]

Best regards,

jim_k

Hi Jim,

To get the time in the local time zone, you must set the time zone you want on the Date.FormatStyle you pass to formatted.

Quinn has a nice example of that here: https://forums.developer.apple.com/forums/thread/700213.

If you want to do this inline, assuming your timezone variable is the time zone of the location you are displaying, you can make a helper function like this:

var dateFormat -> Date.FormatStyle {
    let formatStyle = Date.FormatStyle.dateTime.hour().minute() 
    formatStyle.timeZone = self.timezone
    return formatStyle
}

Then replace lines like this:

Text(day.sun.sunrise?.formatted(.dateTime.hour().minute()) ?? "?")

With something like this:

Text(day.sun.sunrise?.formatted(dateFormat) ?? "?")

Hope that helps!

Hello ... :]

Thank you for your quick reply ...

For the moment, and forgive my ignorance, but I do not understand how to engage your suggested code,

var dateFormat -> Date.FormatStyle {
    let formatStyle = Date.FormatStyle.dateTime.hour().minute() 
    formatStyle.timeZone = self.timezone
    return formatStyle
}

nor as to where I should place the code in the application to allow

Text(day.sun.sunrise?.formatted(dateFormat) ?? "?")

to be exercised ... ?

Placing the raw code anywhere in my application simply generates several warnings, and or error messages. My subsequent question happens to be, should I encase this code in an enum, struct, or class, and then again where ... ?

I do recognize Quinn's code example, where Quinn's function code illustrates a timezone time in the user's location, and then produces the equivalent correct time in the selected remote location, using the remote location's timezone identifier, such as "Europe/Paris".

My application understands the desired requested remote location timezone, where the application wants to update the selected location's time, relative to the selected location's timezone, but for the moment my application does not correctly illustrate the selected location's sunrise or sunset time, as retrieved from <DayWeather>.

Addressing my newly discovered and very limited SwiftUI knowledge, I know I cannot introduce a function into a View to generate the desired result.

I do appreciate your initial quick reply and suggestion, but I still struggle to understand the solution, and again thank you.

Best regards,

jim_k

Accepted Answer

I resolved my Timezone issue with the following code corrections, marked below the [Commented Code] ::

Best regards,

jim_k

import Foundation
import SwiftUI
import CoreLocation
import WeatherKit

struct TestForecastView: View {

    //let dailyForecast: Forecast<DayWeather>
    let dayWeatherList: [DayWeather]
    let timezone: TimeZone
    var body: some View {
        Spacer()
            .frame(height: 10)
        Text("Sunrise Sunset")
            .font(.system(size: 24, weight: .bold))
            .foregroundStyle(.white)
        Text("Ten Day Forecast")
            .font(.system(size: 11, weight: .medium))
            .foregroundStyle(.white)
        Spacer()
            .frame(height: 4)
        VStack {
            //ForEach(dailyForecast, id: \.date) { day in
            ForEach(dayWeatherList, id: \.date) { dayWeatherItem in
                LabeledContent {
                    HStack(spacing: 20) {
                        RoundedRectangle(cornerRadius: 10)
                            .fill(Color.orange.opacity(0.5))
                            .frame(width: 120, height: 5)
                            .padding(.leading, 2)
                            .padding(.trailing, 0)
                        VStack {
                            Image(systemName: "sunrise")
                                .font(.system(size: 24.0, weight: .bold))
                                .foregroundColor(.yellow)
                            //Text(day.sun.sunrise?.formatted(.dateTime.hour().minute()) ?? "?")
Text(dayWeatherItem.sun.sunrise?.localTime(for: timezone) ?? "?")
                                .font(.system(size: 10.0))
                        }
                        .frame(width: 50, height: 20)
                        VStack {
                            Image(systemName: "sunset")
                                .font(.system(size: 24.0, weight: .bold))
                                .foregroundColor(.yellow)
                            //Text(day.sun.sunset?.formatted(.dateTime.hour().minute()) ?? "?")
Text(dayWeatherItem.sun.sunset?.localTime(for: timezone) ?? "?"
                                .font(.system(size: 10.0))
                        }
                        .frame(width: 50, height: 20)
                        Spacer()
                            .frame(width: 2)
                    }
                } label: {
                    //Text(day.date.localWeekDayShort(for: timezone))
                    Text(dayWeatherItem.date.localDate(for: timezone))
                        .frame(width: 80, alignment: .leading)
                        .padding(.leading, 30)
                        .padding(.trailing, 0)
                }
                .frame(width: 380, height: 52)
                .background(RoundedRectangle(cornerRadius: 4).fill(LinearGradient(gradient: Gradient(colors: [Color(.systemBlue), Color(.black)]), startPoint: .topLeading, endPoint: .bottomTrailing)).stroke(.black, lineWidth: 6).multilineTextAlignment(.center))
                .shadow(color: Color.white.opacity(0.1), radius: 4, x: -2, y: -2)
                .shadow(color: Color.white.opacity(0.1), radius: 4, x: 2, y: 2)
            }
        }
        .padding(.top, 20)
        .padding(.bottom, 20)
        .padding(.leading, 20)
        .padding(.trailing, 20)
        .contentMargins(.all, 4, for: .scrollContent)
        .background(RoundedRectangle(cornerRadius: 10).fill(Color.black.opacity(0.0)).stroke(.gray, lineWidth: 4))
    }   //  End of [var body: some View]
}   //  End of [struct TestForecastView: View]

The revised Paris Sunrise and Sunset displays the correct times :

As a side note, changing the [sun.sunrise?] to [moon.moonrise?] and [sun.sunset?] to [moon.moonset?] affects the Moon Phase too ... :]

Paris Sunrise Sunset ::

Paris Moonrise Moonset ::

WeatherKit :: DailyForecast :: Sunrise :: ForEach :: Timezone Issue
 
 
Q