Blazor Numbers Game

Oliver Brown
— This upcoming video may not be available to view yet.

Over the weekend I was playing around with Blazor and created a little number game based on part of a popular British TV show.

In the future this may expand in functionality as part of my exploration into Blazor.

You can try the number game full screen.

Mettle is probably not suitable for tech businesses

Oliver Brown
— This upcoming video may not be available to view yet.

Just to avoid any confusion, everything in this post is based on how things work in the UK and is for things available in the UK.

So far I have operated various business ventures off my personal bank account. Since I’ve been doing this as a sole trader, this is allowed. It does however make tax returns take more effort than necessary.

FreeAgent

I recently had reason to interact with FreeAgent, which is some pretty cool accounting software that makes a lot of things simpler. Unfortunately to really use it to its potential, you need to link it to a bank account used solely for your business.

Conveniently, they offer a free account to anyone who has a Mettle account. I had seen Mettle before, and it is described as “the free mobile business account with no hidden fees or charges”, and is operated by NatWest, one of the big UK high street banks.

E-money accounts

After a quick investigation, it seems one of the reasons it is free (which is the norm for personal accounts in the UK but not business accounts) is that it is not actually a bank account, but an “e-money account”. You get an account number as well as a debit card, but it is not subject to the Financial Services Compensation Scheme (a scheme in the UK in which the government guarantees bank balances up to a certain amount if a bank goes under).

That limitation seemed fine to me since I did not intend to keep a large balance, so I created an account. Sadly, I found out there are other limitations which basically rendered it useless to me.

Prepaid cards

Firstly, it does not come with an IBAN (international bank account number). This means I cannot receive payments from Apple. Secondly, the card is a prepaid card. Since it is tied to the e-money account they give you, it feels like a debit card to use, except some things just don’t accept payments using them. Crucially for me, Microsoft Azure and Google Ads.

Conclusion

In the end, I opened a business account with Royal Bank of Scotland. RBS and NatWest business accounts also get free FreeAgent accounts. The application was more involved (for Mettle, besides verifying your ID you just have to declare you aren’t in one of their list of prohibited business) but in the end was fine.

New Discword Audiobooks

Oliver Brown
— This upcoming video may not be available to view yet.

New audio versions of all* of the Discworld books are being produced. They aren’t being released in order though and as far as I can tell there is no convenient page listing all the release dates.

So here are the release dates currently listed on Audible. There are a few different ways Discworld books get grouped. “Series” below is how Audible have grouped them (and I assume matches the narrator).

Release Date

2021 December 21st

Title Series
Hogfather Death 20

2022 April 28th

Title Series
Equal Rites Witches 3
Wyrd Sisters Witches 6
Witches Abroad Witches 12
Lords and Ladies Witches 14
Maskerade Witches 18
Carpe Jugulum Witches 23
Small Gods Standalone 13

2022 July 7th

Title Series
The Colour of Magic Rincewind 1
The Light Fantastic Rincewind 2
Sourcery Rincewind 5
Eric Rincewind 9
Interesting Times Rincewind 17
The Last Continent Rincewind 22
Unseen Academicals Rincewind 37

2022 October 10th

Title Series
The Amazing Maurice and His Educated Rodents Standalone 28

2022 October 27th

Title Series
Mort Death 4
Reaper Man Death 11
Soul Music Death 16
Thief of Time Death 26

2023 February 23rd

Title Series
Moving Pictures Industrial Revolution 10
The Truth Industrial Revolution 25
Monstrous Regiment Industrial Revolution 31
Going Postal Industrial Revolution 33
Making Money Industrial Revolution 36
Raising Steam Industrial Revolution 40
Pyramids Standalone 7

2023 April 27th

Title Series
Guards! Gaurds! Ankh-Morpork City Watch 8
Men at Arms Ankh-Morpork City Watch 15
Feet of Clay Ankh-Morpork City Watch 19
Jingo Ankh-Morpork City Watch 21
The Fifth Elephant Ankh-Morpork City Watch 24
Night Watch Ankh-Morpork City Watch 29
Thud! Ankh-Morpork City Watch 34
Snuff Ankh-Morpork City Watch 39
The Wee Free Men Tiffany Aching 30
A Hat Full of Sky Tiffany Aching 32
Wintersmith Tiffany Aching 35
I Shall Wear Midnight Tiffany Aching 38
The Sheperds Crown Tiffany Aching 41

Unknown

Title Series
The Last Hero Rincewind 27

Reading Order

Title Series Release date
The Colour of Magic Rincewind 1 2022 July 7th
The Light Fantastic Rincewind 2 2022 July 7th
Equal Rites Witches 3 2022 April 28th
Mort Death 4 2022 October 27th
Sourcery Rincewind 5 2022 July 7th
Wyrd Sisters Witches 6 2022 April 28th
Pyramids Standalone 7 2023 February 23rd
Guards! Gaurds! Ankh-Morpork City Watch 8 2023 April 27th
Eric Rincewind 9 2022 July 7th
Moving Pictures Industrial Revolution 10 2023 February 23rd
Reaper Man Death 11 2022 October 27th
Witches Abroad Witches 12 2022 April 28th
Small Gods Standalone 13 2022 April 28th
Lords and Ladies Witches 14 2022 April 28th
Men at Arms Ankh-Morpork City Watch 15 2023 April 27th
Soul Music Death 16 2022 October 27th
Maskreade Witches 18 2022 April 28th
Interesting Times Rincewind 17 2022 July 7th
Feet of Clay Ankh-Morpork City Watch 19 2023 April 27th
Hogfather Death 20 2021 December 21st
Jingo Ankh-Morpork City Watch 21 2023 April 27th
The Last Continent Rincewind 22 2022 July 7th
Carpe Jugulum Witches 23 2022 April 28th
The Fifth Elephant Ankh-Morpork City Watch 24 2023 April 27th
The Truth Industrial Revolution 25 2023 February 23rd
Thief of Time Death 26 2022 October 27th
The Last Hero Rincewind 27 Unknown
The Amazing Maurice and His Educated Rodents Standalone 28 2022 October 10th
Night Watch Ankh-Morpork City Watch 29 2023 April 27th
The Wee Free Men Tiffany Aching 30 2023 April 27th
Monstrous Regiment Industrial Revolution 31 2023 February 23rd
A Hat Full of Sky Tiffany Aching 32 2023 April 27th
Going Postal Industrial Revolution 33 2023 February 23rd
Thud! Ankh-Morpork City Watch 34 2023 April 27th
Wintersmith Tiffany Aching 35 2023 April 27th
Making Money Industrial Revolution 36 2023 February 23rd
Unseen Academicals Rincewind 37 2022 July 7th
I Shall Wear Midnight Tiffany Aching 38 2023 April 27th
Snuff Ankh-Morpork City Watch 39 2023 April 27th
Raising Steam Industrial Revolution 40 2023 February 23rd
The Sheperds Crown Tiffany Aching 41 2023 April 27th

* There are 41 Discworld books. The website says “40 magnificent new recordings”. The one that seems to be missing is “The Last Hero”.

Interacting with this video is done so under the Terms of Service of YouTube
View this video directly on YouTube

IAB Transparency & Consent Framework does not meet GDRP requirements for transparency or consent

Oliver Brown
— This upcoming video may not be available to view yet.

I posted a while ago about a technical standard that was being used for consent gathering on websites and in apps known as the “Transparency & Consent Framework” by IAB Europe.

Well, the Belgian Data Protection Agency (the Belgian organization responsible for enforcing the GDPR) has fined IAB Europe €250,000 because the TCF “fails to comply with a number of provisions of the GDPR”.

I don’t know whether fining IAB Europe is the correct choice, as they themselves claim they aren’t a data controller and only provide guidance (and a spec) for other companies to use. But the fine itself is not really what is going to be turn out important. The judgement requires:

all recipients of the personal data processed in the TCF . . . to permanently delete all TC Strings and other personal data already processed in the TCF from all IT systems, files and data carriers.

One piece of irony - if you search for articles about this you will find they are nearly all protected by consent dialogs based on TCF.

Here is the full judgement in English.

Blazor custom elements

Oliver Brown
— This upcoming video may not be available to view yet.

There is now a preview package for Blazor that allows creating custom elements.

Custom elements are part of the Web Components standards and are intended to be a way of defining tags that can be consumed in HTML and interoperate with each other.

The docs mostly focus on using them with Angular or React, but that isn’t why they are interesting to me.

The site for Tic-tac-toe Collection includes pages that show some information about specific game mode (for example four-player Tic-tac-toe misère rumble).

That content is rendered as a custom element (in this case <ttt-settings short-code="T_D4_S6x6_W3_T0_P0_M1_O1_F0"></ttt-settings>). Since the site is statically generated with no frontend framework, I wrote the element in vanilla JavaScript. Blazor support for custom elements means I could rewrite that component in C#.

[SOLVED] Firebase Performance Monitoring in Xamarin Android

Oliver Brown
— This upcoming video may not be available to view yet.

I decided to add Firebase Performance Monitoring to the latest version of Tic-tac-toe Collection.

What is it

This allows you to collect timed traces of things that happen in your app. You can set up custom traces for anything you like, but it also tracks how long HTTP requests take by default. At least it’s supposed to. On iOS it worked well, but Android had nothing.

There is an open issue on the repo with the Xamarin binding that was created in December 2018. It includes suggestions to make sure your app is set to using the native HTTP handlers (which makes sense since whatever Firebase is doing it won’t be expecting a random third party HTTP stack to be used), however it still does not work.

How does it work

So I decided to investigate how the HTTP tracing is actually implemented. How found this Stack Overflow question which lead me to this video. It turns out the implementation works by doing some byte code rewriting that changes any references in your app to the HTTP classes to instead reference Firebase provided wrappers.

Why doesn’t it work for Xamarin Android?

I kind of assumed it was doing something like that, but didn’t really think through the implications. Specifically - it does the byte code rewriting as part of a Gradle plugin that is not executed as part of a Xamarin build.

Solution

It may be possible to get the Gradle plugin to execute it’s transformer, but that seems complicated. Luckily, Firebase does provide support for adding HTTP tracing to other libraries, so I decided to work with that.

I decided to implement my own HttpMessageHandler that sets up the HttpMetric and then delegates to a normal HttpClientHandler. This can be passed into the constructor of HttpClient.

public class FirebaseHttpMessageHandler : DelegatingHandler
{
    public FirebaseHttpMessageHandler() : base(new HttpClientHandler())
    {
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var metric = StartTracking(request);
        var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
        StopTracking(metric, response);
        return response;
    }

    private HttpMetric? StartTracking(HttpRequestMessage request)
    {
        try
        {
            var url = new Java.Net.URL(request.RequestUri.AbsoluteUri);
            var metric = Firebase.Perf.FirebasePerformance.Instance.NewHttpMetric(url, request.Method.Method);
            metric.Start();
            if (request.Content?.Headers?.ContentLength is long length)
            {
                metric.SetRequestPayloadSize(length);
            }

            return metric;
        }
        catch (Exception ex)
        {
            Debug.Fail($"URL performance tracking failed: {request.RequestUri.AbsoluteUri} - {ex.Message}");
            return null;
        }
    }

    private void StopTracking(HttpMetric? metric, HttpResponseMessage response)
    {
        if (metric is null)
        {
            return;
        }
        try
        {
            metric.SetHttpResponseCode((int)response.StatusCode);
            if (response.Content?.Headers?.ContentLength is long length)
            {
                metric.SetResponsePayloadSize(length);
            }
            if (response.Content?.Headers?.ContentType?.MediaType is string mediaType)
            {
                metric.SetResponseContentType(mediaType);
            }
        }
        catch (Exception ex)
        {
            Debug.Fail($"URL performance tracking failed: {response.RequestMessage.RequestUri.AbsoluteUri} - {ex.Message}");
        }
        finally
        {
            metric.Stop();
        }
    }
}

Why does it work for Xamarin iOS?

I don’t know the exact method used on iOS, but I do know that iOS has a feature called NSURLProtocol that lets you intercept all HTTP requests by the app, as well as method swizzling which lets you replace basically any method implementation at runtime.

Either of those could be used, and they would both just work in Xamarin.

Accessing the UI in Xamarin.iOS

Oliver Brown
— This upcoming video may not be available to view yet.

An interesting discussion happened recently, triggered by a code review of (something like) the following C#:

public class ProductViewController
{
    private UIView _detailsView { get; set; }
    private ProductViewModel _viewModel { get; set; }

    private void Process()
    {
        if (_viewModel.ProcessDetails)
        {
            InvokeOnMainThread(() =>
            {
                if (_detailsView != null)
                {
                    UpdateDetailsView();
                }
            });
        }
    }
}

One of the reviewers suggested moving the second if outside the call to InvokeOnMainThread (and then combining it with the other if).

The theory was that it would more efficient to check if we need to be on the main thread before we do it, instead of doing the check on the main thread and finding out we have nothing to do.

The original author pushed back saying you can’t access _detailsView off the main thread since you would get an exception.

On the surface this sounds reasonable - everyone knows you can only access UIKit objects on the main thread. But it naturally leads to the question: what does “accessing a UIKit object” actually mean?

So I wrote a quick sample that intentionally tries to do lots of UIKit manipulation on background threads in various ways:

The tests

Quick refresher. The View property of a newly constructed UIViewController is not populated until it is first accessed. Some of the tests below refer to accessing this property either before or after it is created. By default properties declared in C# will not be visible to any native iOS code. They can be made visible by adding the [Export] attribute.

  • Create a UIViewController.
  • Create a UIView.
  • Create a UIColor.
  • Check if a view controller’s View property is equal to null.
  • Check if a view controller’s View property is null using pattern matching.
  • Check if a view controller’s IsViewLoaded property is equal to true.
  • Check if a view controller’s View property is equal to null after previously creating View on the main thread.
  • Check if a view controller’s View property is null using pattern matching after previously creating View on the main thread.
  • Check if a view controller’s IsViewLoaded property is equal to true after previously creating View on the main thread.
  • Check if a new UIView property, that is not exported is equal to null.
  • Check if a new UIView property, that is not exported is null using pattern matching.
  • Check if a new UIView property, that is exported is equal to null.
  • Check if a new UIView property, that is exported is null using pattern matching.
  • Check if a view controller’s NavigationController property is equal to null.
  • Check if a view controller’s NavigationController property is null using pattern matching.
  • Set a new UIView property, that is not exported, with a view created on the main thread.
  • Set a new UIView property, that is exported, with a view created on the main thread.

Results

Test Result
Create UIViewController Exception
Create UIView Exception
Create UIColor OK
Check if View is equal to null Exception
Check if View is null pattern Exception
Check if View is loaded Exception
Check if View is equal to null after creating View Exception
Check if View is null pattern after creating View Exception
Check if View is loaded after creating View Exception
Check if non-exported view is equal to null OK
Check if non-exported view is null pattern OK
Check if exported view is equal to null OK
Check if exported view is null pattern OK
Check if NavigationController is equal to null Exception
Check if NavigationController is null pattern Exception
Set non-exported view OK
Set exported view OK

Summary

This is by no means exhaustive, but it seems in general, acessing properties declared natively in iOS will throw an exception whereas accessing properties you have declared yourself will be fine. I had a suspicion that an exported view might behave the same as a native property, but apparently not.

The code for this test is available on GitHub.

Charles Dunstone in the news

Oliver Brown
— This upcoming video may not be available to view yet.

Charles Dunstone, a well known entrepreneur and founder of Carphone Warehouse has quit as chair of the Royal Museums Greenwich.

Apparently, the government refused to reappoint a trustee because they advocated for “decolonising” the curriculum. More information is available on the Financial Times.

Normally I wouldn’t be interested in posting about such things, but my blog has a bit of history with Charles Dunstone. He was in charge of UK ISP TalkTalk when it came out. Some people had problems and people complaining about it resulted in my most popular blog post.

Some of the complaints led to some interesting solutions, and I briefly included Charles Dunstone’s email address.

The future of Microsoft MAUI (and Xamarin Forms)

Oliver Brown
— This upcoming video may not be available to view yet.

Since Google seems to like my post about the future of Xamarin Forms so much (and I have a slight history of such posts), I’d figure I’d post an update about interesting things happening in the Xamarin Forms repo specifically related to MAUI.

Renaming

The change that actually made me write this post - a large PR with 5000+ changed files that changes the Xamarin Forms name to MAUI.

Not much of a thing for actual functionality, but a significant symbolic milestone.

Handlers and the great big architecture shift

.NET MAUI will completely change the way renderers are handled in Xamarin Forms. There are many advantages of doing it the new way, but the mechanics of how it is done are fairly complex. This video by Javier Suárez covers it well.

Interacting with this video is done so under the Terms of Service of YouTube
View this video directly on YouTube

This is all happening right now in the main-handler branch.

Update

While I was writing this, work officially moved to the dotnet/maui repo and it is accepting pull requests directly.

AppHost and Microsoft.Extensions

Originally an ASP.NET concept, that then migrated its way to Windows client development, this provides a common way to provide dependency injection, logging, and lots of other infrastructure stuff. In isolation, the pattern and implementation is good and will make it easier to override certain things in MAUI (such as handlers). It’s also useful in a wider sense since it will make configuring different styles of .NET apps more similar.

Single project

Over the past couple of years there has been a move towards producing Xamarin libraries (and .NET libraries in general) using a single multi-targeted project. The most significant is probably Xamarin Essentials. This PR adds support for creating applications following the same pattern.

Merging in Xamarin Essentials

There is a lot of functionality in Xamarin Essentials that Xamarin Forms would like to use. Likewise there is some functionality in Forms that is useful when not using Forms. This lead to some overlap in functionality (and occasionally overlap in APIs but not a perfect match in functionality).

There was an attempt to add Essentials as a dependency of Forms but it faced some problems, and there was a “change of plans”.

Now the solution is to have Forms and Essentials in the same repo. I hope Essentials remains available as its own Nuget package (and it looks like that will be the case).

Resizetizer.NT

Resizertizer.NT, like its predecessor Resizetizer, is a package for generating platform specific images in all the right sizes at build time.

Managing image assets across iOS and Android (and using Visual Studio) has always been an unpleasant process. This tool makes it much easier and will be included in MAUI by default.

When can I reuse my calendar?

Oliver Brown
— This upcoming video may not be available to view yet.

This was a question that came up recently, and the answer is interesting enough I decided to step through the process of working it out.

A good year

There are seven days in the week. If the year had a number of days in it that was divisible by seven (say 364), then the answer would be easy: every year and every date would fall on the same day every year.

This would have some nice properties. Some of the more complicated holidays that need to happen on certain days would get simpler, and the poem about what kind of person you are based on the day of your birth would be more poignant since your birthday would be the same each year.

A normal year

But alas, the number of days in the year is not divisible by seven, and we have to suffer with a different calendar each year. But there is good news, there are only seven possible calendars - one starting on each day of the week.

With 365 days in the year, we would have the following possible calendars:

  • Monday 1st January, Tuesday 2nd January … Monday 31st December
  • Tuesday 1st January, Wednesday 2nd January … Tuesday 31st December
  • Wednesday 1st January, Thursday 2nd January … Wednesday 31st December
  • Thursday 1st January, Friday 2nd January … Thursday 31st December
  • Friday 1st January, Saturday 2nd January … Friday 31st December
  • Saturday 1st January, Sunday 2nd January … Saturday 31st December
  • Sunday 1st January, Monday 2nd January … Sunday 31st December

After we have cycled through those calendars, the pattern starts again. Hooray.

Leap years

Of course it is not that simple. Every four years we have a year with an extra day in it, throwing everything off. And to fix it we will need Maths.

We have a cycle of seven starting days, overlaid with a four year cycle of leap years. To determine when the large cycle repeats we need to calculate the least common multiple of four and seven. Since four and seven are coprime (they have no common divisors), their least common multiple is their product.

4 × 7 = 28

So you will be able to reuse your calendar every 28 years.

You can check this is correct by looking at the calendar for 2020 and the calendar for 2048.

Going one better

Knowing when the whole cycle repeats give you an answer to when you can reuse your calendar, but not necessarily the best answer. Applying a bit of logical reasoning, you can work out that there are only 14 possible calendars: one for each day of the week in a leap year, and one for each day of the week in a common year.

A non-leap year is also called a common year.

Sadly, at this point clever maths is probably less illustrative than a brute force approach. Since we know the cycle is only 28 items long, the force is not that brutish…

I shall name the possible calendars with a number indicating the day of the week they start on (1 for Monday), and a letter L or C, indicating a leap year or common year respectively.

Lets start with a common year:

C1

As you may have noticed from earlier, the year after a common year begins with the next day.

C1 C2

We can continue the pattern up to the first leap year, since leapyness does not affect how a year starts.

C1 C2 C3 L4

But what next? A leap year has an extra day, so instead of going up by one, we go up by two.

C1 C2 C3 L4 C6

We are now ready to complete the pattern (remembering after 7, we go back to 1)

C1 C2 C3 L4 C6 C7 C1 L2 C4 C5 C6 L7 C2 C3 C4 L5 C7 C1 C2 L3 C5 C6 C7 L1 C3 C4 C5 L6

The cycle is 28 years long, and there are 14 possible calendars so you might naïvely think each one appears twice, but that isn’t the case. The leap years appear once each, and the common years appears three times each.

This means for leap years, our guess at waiting 28 years to reuse a calendar is correct. For common years, you can reuse your calendar, on average every 9⅓ years, but in a seemingly irregular pattern.

Making it useful

For any given year, you only need to know when you are relative to a leap year to work out when a calendar can be reused:

Year Years until next reuse
Leap year 28
Year after leap year 6
Two years after leap year 11
Year before leap year 11

So if it is two years after a leap year, for example 2018, you can reuse your calendar 11 years later, in 2029.

If you want to calculate the next reuse after that, then the method is straightforward, but hard to explain clearly. First, I’ll provide a variation of the above table with just common years.

Year Years until next reuse
Year after leap year 6
Two years after leap year 11
Year before leap year 11
Year after leap year 6
Two years after leap year 11
Year before leap year 11
Year after leap year 6
Two years after leap year 11
Year before leap year 11
Year after leap year 6
Two years after leap year 11
Year before leap year 11

To work out the reuse after the next reuse, step backwards through the table (wrapping round when you get to the beginning).

So, if it is 2019 (a year before a leap year), the next reuse is 11 years later in 2030. This is two years after a leap year, so the next reuse is found by reading the row above - 11 years later again in 2041. Now we are a year after a leap - repeating the process and reading the row above we find the next reuse is 6 years later in 2047.

This can be summarised in the following decision tree:

Taking it further

What we have so far will work for the vast majority of people, at least for working out calendars within your own lifetime. But if you want to work out years close to 2100, it will fail.

The reason is that the idea that a leap year is every four years is not the whole story. If the year is also divisible by 100, then it is not a leap year. So 2100 is not a leap year. And nether was 1900.

But… what about 2000? If you check a 2000 calendar you’ll find February 29th. Turns out there is another rule to leap years - if the year is also divisible by 400, it is a leap year. So 2000 was, and 2400 will be.

The reason these are added are to make up for the fact that the length of the year is not nicely divisible by the length of the day. There are currently no further rules but the day will drift again at some point.

Matt Parker made an excellent video explaining this.

Interacting with this video is done so under the Terms of Service of YouTube
View this video directly on YouTube

There is a proposal to make every year divisible by 4000 into a common year but based on historical precedent we will probably completely change the calendar before that happens.

Current year Next reuse Difference
2980 Leap year L6 3020 40
2981 Year after leap year C1 2987 6
2982 Two years after leap year C2 2993 11
2983 Year before a leap year C3 2994 11
2984 Leap year L4 3024 40
2985 Year after leap year C6 2991 6
2986 Two years after leap year C7 2997 11
2987 Year before a leap year C1 2998 11
2988 Leap year L2 3028 40
2989 Year after leap year C4 2995 6
2990 Two years after leap year C5 3002 12
2991 Year before a leap year C6 3003 12
2992 Leap year L7 3004 12
2993 Year after leap year C2 2999 6
2994 Two years after leap year C3 3000 6
2995 Year before a leap year C4 3007 12
2996 Leap year L5 3008 12
2997 Year after leap year C7 3009 12
2998 Two years after leap year C1 3010 12
2999 Three years after a leap year C2 3005 6
3000 Four years after a leap year C3 3006 6
3001 Five years after a leap year C4 3007 6
3002 Six years after a leap year C5 3013 11
3003 Year before a leap year C6 3014 11
3004 Leap year L7 3032 28

At first glance that table looks unhelpful - the numbers in the last column are all over the place. But, with a bit of careful thinking it’s only a small extension to the previous method.

The final algorithm

Any more issues

I have assumed two calendars are the same if they start on the same day and have the same number of days. But there are other differences, like when certain holidays occur.

Some of the holidays will change in the same way and so won’t matter. For example in the UK, Christmas Day is a bank holiday. If it happens to fall on a weekend, then the next weekday is a bank holiday in lieu.

Many holidays however do change, and it is mostly to do with the moon. For instance the definition of when Easter occurs in western Christianity is:

the first Sunday after the first full moon that falls on or after the vernal equinox

To make things a bit weirder, it’s not really the vernal equinox, but March 21 (the vernal equinox is often on March 21, but in reality happens some time between March 19 and March 22 and can vary with timezone). It’s also not the full moon in the astronomical sense, but the Paschal full moon, which is an approximation of the astronomical full moon based on the 19-year Metonic cycle.

Wikipedia has a table of dates comparing Gregorian Easter and Astronomical Easter.

I decided not to try and calculate calendar reuse with these details considered.