Scheduling Windows Notifications from Rust

randomengy

David Rickard

Posted on March 19, 2023

Scheduling Windows Notifications from Rust

The windows crate exposes the whole WinRT API surface in Rust and makes it quite easy to send native Windows notifications. One particular handy feature is scheduling notifications for the future, that Windows will deliver even when your app is closed: ScheduledToastNotification::CreateScheduledToastNotification .

One little sticking point is that it requires a Foundation::DateTime to specify the start time, and there don't seem to be any helpers to create one from a chrono or any native Rust date struct. I was able to find in the documentation:

A 64-bit signed integer that represents a point in time as the number of 100-nanosecond intervals prior to or after midnight on January 1, 1601 (according to the Gregorian Calendar).

So the job is to figure out how many 100ns intervals are between your desired time and the WinRT epoch. Then create a DateTime from scratch, passing in this tick count to the UniversalTime property. The full demonstration code is below:

use chrono::{Local, NaiveDateTime, Utc, Duration, TimeZone};
use windows::Foundation::DateTime;
use windows::UI::Notifications::ScheduledToastNotification;
use windows::{Data::Xml::Dom::XmlDocument, UI::Notifications::ToastNotificationManager};

use windows::core::{Result, HSTRING};

fn main() {
    let app_id = "my.app"; // AUMID
    let toast_notifier = ToastNotificationManager::CreateToastNotifierWithId(&HSTRING::from(app_id)).unwrap();

    let toast_xml = create_toast_xml().unwrap();

    let local_time_string = "2023-03-19T16:01:27";
    let winrt_start_time = local_time_to_win_datetime(local_time_string);

    let scheduled_notification = ScheduledToastNotification::CreateScheduledToastNotification(&toast_xml, winrt_start_time).unwrap();
    toast_notifier.AddToSchedule(&scheduled_notification).unwrap();
}

fn create_toast_xml() -> Result<XmlDocument> {
    let toast_xml = XmlDocument::new()?;
    toast_xml.LoadXml(&HSTRING::from("<toast>
        <visual>
            <binding template=\"ToastText01\">
                <text>Testing</text>
            </binding>
        </visual>
    </toast>"))?;

    Ok(toast_xml)
}

fn local_time_to_win_datetime(local_date_time: &str) -> DateTime {
    // First parse the time into a timezone-agnostic local time.
    let naive_time: NaiveDateTime = local_date_time.parse().unwrap();

    // Convert the naive local time to the system time zone, preferring earlier if ambiguous.
    // If there was no local time there, go back an hour and try again.
    let date_time = Local.from_local_datetime(&naive_time)
        .earliest()
        .unwrap_or_else(|| Local.from_local_datetime(&naive_time.checked_sub_signed(Duration::hours(1)).unwrap()).earliest().unwrap());

    let winrt_epoch = Utc.with_ymd_and_hms(1601, 1, 1, 0, 0, 0).unwrap();

    let microsecond_timestamp = date_time.timestamp_micros() - winrt_epoch.timestamp_micros();

    DateTime {
        UniversalTime: microsecond_timestamp * 10
    }
}
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
randomengy
David Rickard

Posted on March 19, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related