Daniel Eden, Designer
Deep Dive

Solstice

Two iPhones showing Solstice version 1 and version 2 side-by-side. Version 1 features a monochrome black-and-white design with limited use of color to denote interactive controls. Version 2 has a lot more color by comparison, showing a graphical chart that emulates the sky's appearance, and colorful bar charts indicating the daylight length changing over the year.
Solstice 1.0 and 2.0. Version 2 added support for multiple locations, and more advanced data visualisations. Keeping the simple design while adding more advanced features was an important part of evolving the app.

Solstice is a project that started with a simple idea: what if I could see how much more or less daylight there is today compared to yesterday? But simple ideas can be complex to build. I wanted to share this deep dive into the work that went into making Solstice a powerful tool that’s still simple to use.

Overview

Solstice is a project close to my heart. When I was working in my first job in Manchester, England, I spent most of my day in an office that had no external windows. During the winter, this meant I didn’t see sunlight during the work week—I’d go into the office before the sun rose, and I wouldn’t leave until after it had set.

This experience underscored the effect that sunlight has on my mood and mental well-being. When, during the winter of 2020/2021, COVID-19 made an already-dreary season even drearier, I decided to build an app that would help me quantify the change in daylight, and give me something to look forward to.

Version 1 of Solstice launched in January, 2021. I rebuilt the app more or less from scratch for version 2, which launched in March 2023. Evolving the simple design from version 1 to accommodate more powerful features while retaining its simplicity was a rewarding challenge.

The app is built in SwiftUI and works on iPhone, iPad, macOS, and Apple Watch. It’s privacy-sensitive by default, asking only for the permissions it needs (approximate location) and works without access to your location unless you explicitly grant permissions.

An iPhone and Apple Watch running the Solstice app, showing daylight information for London, England.
Solstice works across iPhone, iPad, macOS, and Apple Watch, and features widgets, daily notifications, and Shortcuts support.

Time Travel

One of Solstice’s core features is Time Travel; the ability to compare today’s daylight to any other date. In the first version of Solstice, a slider was used to allow quick time travel to dates in the past or future. This affordance is easy to use, but imprecise: on smaller iPhone screens, individual days might end up less than 1px apart on the slider, making it impossible to choose a specific date. When I rebuilt Solstice, I knew from the start I wanted to allow choosing a specific date.

A zoomed in screenshot of Solstice's time travel feature, showing a date picker and a slider with “Past” and “Future” as the start and end labels.
The rebuilt Solstice app lets people choose specific dates, as well as quickly scrub to dates in the past or future using the slider

This meant the “Time Machine” for controlling time travel needed three properties:

  1. A reference date (the date we want to travel from—usually the current date)
  2. A target date (the date we want to travel to)
  3. An offset representing the number of days between the reference and target dates

To keep the offset in sync with the reference and target dates, it needs to be its own computed value, and since we want to control it via a slider, it needs to be a Binding over a Double. Putting all this together, a simplified version of Solstice’s Time Machine looks like this:

@MainActor
class TimeMachine: ObservableObject {
    // The `isOn` property just indicates whether the Time Machine is active or not
    @Published var isOn = false
    @Published var referenceDate = Date()
    @Published var targetDate = Date()

    var offset: Binding<Double> {
        Binding<Double>(get: {
            Double(Calendar.current.dateComponents([.day], from: self.referenceDate, to: self.targetDate).day ?? 0)
        }, set: { newValue in
            self.targetDate = Calendar.current.date(byAdding: .day, value: Int(newValue), to: self.referenceDate) ?? self.referenceDate
        })
    }
}

The referenceDate is updated every minute in the app’s main view using a timer (I had explored using SwiftUI’s TimelineView for this update but found it degraded the app’s performance) and/or when the app (re)enters the foreground. The whole class is also run on the main thread thanks to the @MainActor attribute; this ensures the UI remains responsive when the Time Machine changes, which is important: almost everything on the screen is dependent on the Time Machine’s dates.

There’s one problem though; the hour, minute, and second components of the reference and target dates can drift. If you open the app at 9am and set the target date to some future date, when you reopen the app at 9pm, the app will still show you data matching 9am on the target date. To prevent this, we need one last computed property to synchronise the time components of the reference and target dates:

extension TimeMachine {
    var date: Date {
        guard isOn else { return referenceDate }
        let time = Calendar.current.dateComponents([.hour, .minute, .second], from: referenceDate)
        return Calendar.current.date(bySettingHour: time.hour ?? 0,
                            minute: time.minute ?? 0,
                            second: time.second ?? 0,
                            of: targetDate) ?? targetDate
    }
}

This date property is the source of truth for Solstice’s dates, returning the reference date when time travel is inactive, and the target date—with the reference date’s time components—when it’s active.

This code still leaves one quirk: when the referenceDate ticks over to the next day, the targetDate has its time components set to the beginning of the same day, but this “groundhog day” effect feels like it’s actually expected behaviour.

Time Zones

In addition to comparing daylight at different times of the year, Solstice lets you compare it in different locations around the world. It’s impossible to build an app that deals with time and location without running into the complexities of time zones, and Solstice is no exception.

Furthermore, Solstice had the unique challenge of accurately representing dates and times in a way that also let people jump to arbitrary dates in the past or future.

As a result, almost every reference to a Date value is passed through this function:

extension Date {
    func withTimeZoneAdjustment(for timeZone: TimeZone?) -> Date {
        guard let timeZone else { return self }
        let tzOffset = timeZone.secondsFromGMT(for: self) - TimeZone.autoupdatingCurrent.secondsFromGMT(for: self)
        return self.addingTimeInterval(TimeInterval(tzOffset))
    }
}

The function takes a time zone and returns the date adjusted for this time zone. This helps prevent weirdness like a 2pm sunrise when you’re looking at a location 8 hours behind your own.

A zoomed in screenshot of Solstice's time zone indicator at the top of its detail view, showing San Francisco's local time, which is 8 hours behind London time.
The time zone indicator in Solstice started as a helpful debugging tool and turned into one of my favourite little details

To help make sure my dates were working as expected, I added a time zone indicator at the top of views for locations outside of my current time zone. This debugging tool went on to become one of my favourite little details in the app.

Polar Days

Two iPhones showing a location close to the north pole in Solstice at different times of year. The charts indicate 24 hours and 0 seconds of daylight respectively.
In locations close to the north or south pole, there might not be a sunrise or sunset. Distinguishing between 0 seconds and 24 hours of daylight is surprisingly hard.

In locations close to the north or south pole, there are periods of the year during which the sun never rises or sets. In Solar, the library used in Solstice to calculate sunrise/sunset times, the time of the sunrise or sunset can sometimes be nil. In the original version of Solstice, I often force-unwrapped these values or fell back on an arbitrary date, leading to crashes or unexpected behaviour in the app. When I rebuilt the app, I implemented a “safe” sunrise/sunset value:

extension Solar {
    var fallbackSunrise: Date? {
        sunrise ?? civilSunrise ?? nauticalSunrise ?? astronomicalSunrise
    }

    var fallbackSunset: Date? {
        sunset ?? civilSunset ?? nauticalSunset ?? astronomicalSunset
    }

    var safeSunrise: Date { sunrise ?? startOfDay }

    var safeSunset: Date {
        if let sunset { return sunset }

        guard let fallbackSunset, let fallbackSunrise else {
            return endOfDay
        }

        // If the sunrise is over 7 hours from sunrise, it's safe to assume this is a 24 hour day of daylight
        if fallbackSunrise.distance(to: fallbackSunset) > (60 * 60 * 7) {
            return endOfDay
        } else {
            // Otherwise, it's safe to assume this is a day of no daylight
            return startOfDay.addingTimeInterval(0.1)
        }
    }
}

fallbackSunrise and fallbackSunset return the “edges” of a day’s daylight; the moments when the sun begins or ends its journey above the horizon. Each of the common, civil, nautical, and astronomical sunrise/sunsets represent different degrees above or below the horizon and generally are calculable values, but during polar days—days with either 24 hours or 0 seconds of daylight—these times may not be calculable. To overcome this, the safeSunrise and safeSunset values find the best approximate fallback values, based on the distance between sunrise and sunset.

Little Details

Solstice is a real labour of love; it’s a tool I still use almost daily to enjoy the long summer days and get me through the long winter nights. Every time I open the app, I tend to notice something new that feels like it could use polishing, or think of another “wouldn’t it be nice if…”. The app boasts daily notifications with scheduling options to match the sunrise or sunset; Lock Screen widgets which show tomorrow’s sunlight information after the sun sets; little details like the ability to share the solar chart or configure its appearance; and SAD preferences that let you turn off notifications when daylight starts to reduce.

I’ve also loved people’s kind reviews for the app. I’ll let them speak for themselves:

4.5

50 ratings

Great design with an issue
This app is well designed but the morning sunlight notification doesn’t work. I set it to 9am but don’t get a notification about it. Please fix it for 5* rating.
Great utility for daylight aficionados
Love it. Simple, adds value to my phone, and has continually improved since release without losing its vision. Thank you for making this. Please add an optional subscription or tip jar so I can support it!
Does what it says on the tin
Great app. The latest iteration on widgets is even better than the last. Excellent information density!
Pourrais être utile mais…
La date annoncée du Solstice d’hiver 2021 est erronée. A 14h TL il est annoncé dans 9h, donc à 23h TL, or il a lieu à 17h TL le 21 décembre 2021. Une différence de 6h Ceci implique que le programme pense que je suis à New York, où il est 6h plus tôt.
Understand sunlight at a glance
Quickly see sunlight differences around the world, today or in the future. This app is simple and easy to understand. 🌻
Wonderful design
Writing this passing through roads above the arctic circle where the sun hasn’t rose for 5 days. Started appreciating effects of sunlight on my health. Thanks!
Very nice
Does what it says and has a nice widget
Lovely app!
Easy to understand and use!
Cute utility!
Finally downloaded this after winter started and it’s such a neat little app. I was really excited to use the widget and it’s a fun way to pass the dreary winter
An amazing app!
Thank you for making this app, it’s very motivating with the notifications just to see that there’s more sunlight today :)
poetic
few apps touch the twin spirit of poetry and technology. this is a rare example. thank you, daniel.
Great app
Well done, mate
Super Underrated!!! Nice Interface
Really great tool for Sunlight lovers! Nice user interface that makes me think it’s made by a huge developer. Love the app so much!
One of my favourite apps.
I love this app, it is simple to understand and informative which is ideal, the way all apps should be.
Flawless open-source app
A fantastic example of open-source apps on iOS!
Learned something new
Increase in darkness and light during change of season is obvious. I didn’t know that the rate of reduction /increase of sunlight varies during the year. I still don’t understand why the rate of change is not constant. Please explain
Great app!
It works super well and is exactly what it says on the tin :) also love the SAD settings for notifications!
Feature request
Can you have a countdown of sunlight left in a day….?
Great app + a small suggestion
Great app, love the design. It would be phenomenal if the app would provide an option to enable automatic push notifications for sunset. I’d love to receive a notification 5 min before sunset each day, without me having to manually set the notification time 🙏🏻
The perfect app
Simple, functional, reliable. Does what it claims to do quietly and with no fuss. Perfection.
役に立つ
アプリは分かりやすい!
Awesome idea
Great idea but the “more daylight” function doesn’t work on AW
Love it
Daniel, I find your app immensely comforting! I’m very happy to have discovered it as it helps me through winter and makes me more conscious of our passage through the year which I love. Many thanks for developing it and making it freely available. As someone who lives in Italy but has friends and family in UK, I’d love it if the app remembered another location so I didn’t have to type it in each time (honestly, such a tiny gripe!). I’d also love it if I could compare the data for two places on the same screen.
Needs Equinox
Great app. Adding measurement and widgets for the equinoxes would be a feature I’d pay for.
Nice
Nice app and well designed.
Love it
It’s a simple idea extremely well executed, beautifully designed, love the watch complications, useful app which brings joy. Thanks!
Perfect lightweight utility
I’m a big fan of Solstice for its clean, simple design and helpful at a glance data. The widgets are perfect for being able to glance at sunrise and sunset times, and I enjoy being able to look into the future at different dates to see the amount of sunlight for that day.
Simply great
Really well designed. I love it!
Missing diagram after update
Please bring back sunrise/sunset diagram for Apple Watch
The best apple watch complication is gone
Why? It looked so cool with the minimal graph, but now it’s gone
Location
Hey, could you make it so I can set a location as "Favorite" and use that for notifications? (Instead of sharing my location...) Thank you! PS. Great app, nice job 😊
Well designed, beautiful, and useful
Solid app for helping understand variations of daylight hours across different locations. Would highly recommend everyone to try it. I mean, it’s even free!! Had to dock a star because the lock screen widget is broken. The progress ring around the sunset/sunrise time shows incorrect progress. Occasional it shows the correct progress when the screen is woken but then it immediately changes to something else. Needs to be fixed.
Great app!
Kudos on this freemium app! As a fan of sunsets, your app has been my go-to for modular complications. I do have one request though - could you please bring back the old design of the solar chart complication which showed the exact time of sunrise and sunset? Regardless, thank you for such a fantastic app!
Great App
I love your app but I don't why I can't see the overview complication on my apple watch ultra.
Finalmente
Finalmente un app che dice orari tramonto ben fatta. Sarebbe stupendo averla in lingua italiana
Info about next solstice and equinox are not correct
today is july 26, “Solstice” says next solstice will be in 4 months and next equinox next month
Bug
Dopo l’ultimo aggiornamento tutte le volte che spengo e accendo l’iPhone l’app non carica più. È necessario disistallarla e reistallarla. Non so se è un problema dell’app o di iOS17
Just THE thing
This is a great app!
Okay
It’s does a job but the widget often doesn’t update time to sunrise / sunset until I restart my phone.
ty daniel!
just wanted to say thank u for making this info so conveniently available for free. i use solstice every day before deciding when to bring our sheep in on our farm <3
Widget options not showing
Really nice app and widgets look great on my friends’ phones (primary reason I downloaded!), however I am not getting options to add widgets to either home or lock screens (I have tried deleting and redownloading the app, turning phone off and on, and granting all permissions but no success). How can I get the widgets working?!
Inutile
Non ci sono molte info utili e tante cose non funzionano
Fantastic App
Even allows you to turn sad notifications off. :) 'leave tip' function not working. Sad!
One of the best apps in the store
Straightforward, does a delightful thing well, and helps me understand and show others how the days are changing. Just wonderful. Great Apple Watch complications as a bonus (among the most watch appropriate apps and complications around). Thank you for making this!
Great experience
Beautifully designed app, super handy for tracking sunlight. I was looking for an app like this and it does exactly what I need perfectly. Kudos to the developers!!!
Kleine informative App :)
Funktioniert wunderbar und macht Spass.
Solstice
Great app, what more can I say! A simple clean straightforward app that is easy to use and understand. Great job developers 👏🏻👏🏻👍🏻😁
Great Little App
Simple in design, full but concise data. App works as clean as an Apple app should do! There is a slight difference between notification and opening the app but only by a second or two...
Stunning 🌞
A straightforward and easy to use app, there’s no reason to use anything else for sunrise/set tracking. Beautiful.
Zickt etwas aber sonst gut
Beim starten unter iPhone iOS 17.4.1 friert der Bildschirm (grau) 📺gelegentlich ein, und es tut sich nichts. Erst bei erneuten Start läuft es reibungslos. Vielleicht 🤔 hilft ein Update?

It’s heartwarming to see that an app I took such pleasure in building has been loved by so many people. The app is free to download on the App Store.