Handling times for an EV charger

This morning (October 30th 2022), the clocks went back in the UK – the time that would have been 2am fell back to 1am. This is just the regular “fall back” transition – there’s nothing special about it.

As it happens, I’d driven my electric car for quite a long journey yesterday, so I had it plugged in to charge overnight… and that’s where things get interesting.

My electricity tariff is called Octopus Go, which is designed for electric vehicle owners. Any electricity I use between 12:30am and 4:30am is significantly cheaper than at other times. I use a PodPoint charger, which allows me to control when the car will charge via an app. For each day of the week, there’s a start time and an end time – the charger turns on at the start time, and off at the end time. (If the car isn’t plugged in while it’s “on”, that’s fine. Likewise if the car finishes charging, it will stop drawing power.) Unsurprisingly, I have my schedule set for 12:30am to 4:30am every day. (If I know I need more charge than 4 hours provides, I tweak the schedule and then set it back.) The app looks like this:

PodPoint scheduling

Normally, that schedule will get me 4 hours of charging. But this morning was special due to the clocks going back… and I didn’t know what would happen. If the charger handled the schedule as “if the local time is between 12:30am and 4:30am, then the charger should be on” then it should charge for 5 hours:

  • It would charge for 1 1/2 hours between 12:30am and 2am
  • Local time would fall back to 1am
  • It would charge for 3 1/2 hours between 1am (the second occurrence of 1am!) and 4:30am

Assuming that happened, what rate would Octopus charge me for these 5 hours? The same logic should mean the whole charging period is on the cheap tariff… but would something go wrong?

I was geekily excited by all this and tweeted as much:

Exciting experiment tonight! I have my electric car charger set to charge between 00:30 and 04:30, as that’s when I get cheap electricity. The clocks go back (2:00 to 1:00) tonight. So: a) will I get 5 hours of charging? b) will all of it be at the cheap rate? Enquiring minds etc

What actually happened?

The car definitely charged for 5 hours. The PodPoint app shows each charging session, as shown in the screenshot below. (The session only ends when I remove the cable from the car, but the charging duration is measured separately.)

PodPoint charging session

The price there is only what PodPoint thinks I’ll be charged. Octopus makes data available the day after, but I’ll be checking three things when they do:

  • How today is represented in the CSV file you can download from them
  • How today is represented in the web graphs of usage
  • How much the electricity actually cost me

(I’m fairly convinced it will all be cheap, but it’ll be good to check.)

What should the code for an EV charger look like?

I had various responses to my tweet, including at least a few people informing me that the industry standard approach to time zone handling is to convert everything to UTC internally and only convert to local time for display purposes. Those responses are the reason for this blog post… because in my view, that’s absolutely the wrong way to treat this situation.

If you haven’t previously read my post on why storing UTC is not a silver bullet you may wish to do so, and my objections this time aren’t entirely unrelated, but it’s not quite the same thing. In particular, the problems with using a conversion to UTC have nothing to do with time zone rules changing in the future.

Let’s consider the information we have here:

  • The charging schedule is expressed in local start/end times on a per-day-of-week basis, e.g. “Monday: 00:30 to 04:30”. Note that there are no dates here; just days of the week and local times.
  • The charger needs to know the current local date and time. Typically (but not necessarily) that will mean:
    • The charger knows the current instant in time (i.e. it has a system clock)
    • The charger knows the “target” time zone for which the schedule should be applied (e.g. Europe/London)
    • The charger knows the rules for that time zone

My immediate question to the proposal of “the charger should convert everything to UTC” is to ask what that even means, given the information above. Knowing that the time zone is Europe/London, how does one convert a schedule entry of “Monday: 00:30 to 04:30” to UTC? A conversion to UTC is normally for a local date and time in a particular time zone to an instant in time. Here we don’t have a local date and time; we have a day of the week and a time. In Europe/London, “Monday” will sometimes have a UTC offset of +00, and sometimes have an offset of +01. (And “Sunday” can vary over the course of the day – as it would today, starting off with a UTC offset of +01 and ending with a UTC offset of +00.)

The next question would involve dealing with ambiguity and skipped times. Suppose my schedule for Sunday was Start=01:15, End=01:45. Assuming the conversion code was pinned to a specific date, how would those be converted into UTC today, when each of those times occurs twice? What about on March 27th 2022, when those times didn’t occur at all due to a “spring forward” from 1am to 2am?

Finally, I would ask where the requirement to convert to UTC came from. Is this thinking through the requirements, or just applying received wisdom of “always convert everything to UTC”?

Slightly generalizing my earlier statement, I would probably write the requirement as:

The charger status (on or off) is determined by the charging schedule, applied to the current local date and time. The charger should be “on” if the current local time is between the start and end time in the schedule for the current local day-of-week.

That doesn’t require any conversion to UTC. It doesn’t even require that the system is aware of the current instant in time at all – it only needs to know the current local date and time, because that’s the context in which the requirements are expressed.

So how do we know when to turn the charger on or off? If we cared about turning on and off at exactly the right time, we’d probably want to work out the duration between now and the next change – and that probably would involve conversions to UTC. But that’s unecessary. The way I’d write this would be to just have an infinite loop, checking whether the charger should be on or not, then sleeping for a bit. (That could be sleeping for 1 second, 10 seconds, a minute or even 5 minutes.)

I’ve created “somewhat pseudo-code” (it’s valid C#, it compiles, and would work – but there’s no application hooked up to use the library) for this in my GitHub demo repo, but the most important aspects are discussed below. I should note that there are no tests, and it isn’t designed to handle:

  • Changes to the time zone database
  • Changes to the target time zone
  • Changes to the schedule
  • Shutdown requests
  • Handling schedules where Start is later than End (e.g. to have a schedule of “11pm to 2am”)
  • Handling an end time of “midnight” in a schedule

None of these would be hard to handle (and the first three would be much harder to handle in any system that started from a position of “convert everything to UTC) but would be distractions from the main business of “how should the conversions work”.

The main loop is in EvChargerController, which is reproduced in its entirety below (other than comments; see the full code for the comments):

using Microsoft.Extensions.Logging;
using NodaTime;
using NodaTime.Text;

namespace EvChargerTiming;

public class EvChargerController
    private readonly DateTimeZone zone;
    private readonly IClock clock;
    private readonly ChargingSchedule schedule;
    private readonly EvCharger charger;
    private readonly ILogger logger;

    public EvChargerController(EvCharger charger, ChargingSchedule schedule, DateTimeZone zone, IClock clock, ILogger logger)
        this.charger = charger;
        this.schedule = schedule;
        this.zone = zone;
        this.clock = clock;
        this.logger = logger;

    public void MainLoop(TimeSpan pollingInterval)
        while (true)
            Instant now = clock.GetCurrentInstant();
            ZonedDateTime nowInTimeZone = now.InZone(zone);

            bool shouldBeOn = schedule.IsChargingEnabled(nowInTimeZone.LocalDateTime);
            if (charger.On != shouldBeOn)
                logger.LogInformation("At {now} ({local} local), changing state to {state}",



The only conversion involved is from the current instant in time to the local time in the target time zone. That’s much easier than converting from a local time into an instant, because there’s no scope for ambiguity or skipped values. The result of the conversion is used immediately rather than stored, which means we don’t need to worry about what data going stale if the time zone rules change.

I do use the instant when logging – in reality, I’d expect the logging infrastructure to log the instant at which the log entry is created, but I thought I’d demonstrate that it’s potentially useful to specify the instant and the result of the conversion. (As it happens, ZonedDateTimePattern.GeneralFormatOnlyIso includes the the UTC offset anyway, so the instant could be inferred from that, but hey.)

The ChargingSchedule type used by EvChargerController is even simpler. Again, I’ve cut the comments out – the full code has comments.

using NodaTime;

namespace EvChargerTiming;

public record ChargingScheduleDay(IsoDayOfWeek DayOfWeek, LocalTime Start, LocalTime End)
    public bool Contains(LocalTime now) =>
        Start <= now && now < End;

public class ChargingSchedule
    private readonly List<ChargingScheduleDay> days;

    public ChargingSchedule(List<ChargingScheduleDay> days)
        this.days = days;

    public bool IsChargingEnabled(LocalDateTime dateTime)
        var day = days.Single(candidate => candidate.DayOfWeek == dateTime.DayOfWeek);
        return day.Contains(dateTime.TimeOfDay);

The key part here is the signature of the sole method within ChargingSchedule:

public bool IsChargingEnabled(LocalDateTime dateTime)

From the perspective of turning the charger on and off, all we need to know is whether or not it should be on at a particular local date and time – which maps precisely onto the requirements.

Everything else derives from that requirement – and as you can see, the implementation is really trivial. There are basically three lines of “real code”, and they’re very easily testable.


When working with a date/time challenge, the first response should be “I need specific and clear requirements” rather than “we should use UTC”. Let the requirements drive the code. In this particular case, all the data is inherently “local”, and we never want to store any instants in time, so the conventional wisdom of converting to UTC really doesn’t help.

I’d also note that it’s a lot easier to spot that only the local date/time is relevant when using Noda Time than it would have been with the .NET built-in types – a signature of IsChargingEnabled(DateTime dateTime) would have needed more careful documentation to explain its intention.

Finally, remember that conversions from an instant in time to a local date/time are generally simpler than the other way round, as they’re always unambiguous. The solution above never needs to convert in the other direction, so we never need to make any decisions of how to handle ambiguous or skipped values.

None of this is intended to imply that you should never use UTC. When storing current/past timestamps (rather than user data) I’d almost always use UTC. But user data itself is rarely expressed in UTC, and sometimes (as here) we never need to do a conversion to UTC in order to process the data – if you don’t need to convert it, why would you do so?

Introduction to DigiMixer

This is the first of what I expect to become a series of maybe a dozen blog posts about a hobby project I’ve started, called DigiMixer.

Back in January 2021 I posted about controlling an XR-16 using Open Sound Control, and then later using an X-Touch Mini to control the XR-16 using the same underlying code.

Since then, this has become part of my church A/V project, At Your Service, which I’ve also mentioned in blog posts about VISCA cameras and MAUI. At Your Service (AYS) has been used “in production” (i.e. for real Sunday services) for about a year and a half now, and the code to control the XR-18 (which is an XR-16 plus USB audio interface, effectively) is absolutely crucial to this. Fortunately, it’s proved pretty stable.

I don’t currently expect AYS to be used in any church other than my local one (Tilehurst Methodist Church), but I’d like to at least work on making it a little more feasible for that to happen – particularly if I can have fun with more coding experience at the same time. To that end, I’ve started looking at other digital mixers that are similar to the XR-16. These are audio mixers which all look pretty similar: they have XLR sockets for inputs and outputs, possibly some headphone sockets and volume control for those, usually a USB connection so it can act as an audio interface, and a network connection to control it. Some have additional network connections for network-based audio expansions and the like, but I’m not (currently) interested in that aspect.

The part that makes these mixers different to “regular” audio mixers is what they lack: faders, EQ adjusters, mute buttons etc. That’s all done via network control. There are some mixers that can be controlled over the network as well as physically, but I haven’t investigated those.

Each of these mixers from different manufacturers is controlled in a different way, and they all have different features and limitations. However, they have some core functionality in common, and that’s probably enough commonality for use in a church service. The aim of the DigiMixer project is to create a lowest-common-denominator abstraction allowing an application such as AYS (and potentially multiple sample standalone DigiMixer applications) to control any of these mixers without having to “know” about anything other than the abstraction.

There’s nothing particularly new about the abstraction concept here, but this use case happens to tie into something I really want to do anyway, and I believe it will provide plenty of material for blog posts on applying abstraction in C#, in the real world. Most articles on abstraction are theoretical, for perfectly valid reasons – but that means they gloss over the kind of issue you face when trying to apply the ideas for real. I suspect most developers have encountered this sort of thing, but I don’t have any deadlines for DigiMixer, and I can share everything without worrying about confidential material etc.

This first post is nothing but background material, partly as I’m waiting for one mixer to arrive, and some more information about others. The rest of this post is just a list of the mixers I either have access to, have on order, or which I’d like to get to work if possible. If you know of any others (particularly budget-friendly ones with good documentation!), please leave a comment.

Behringer X-Air series (XR-12, XR-16, XR-18, X-32)

XR-18 photo

This is where I started, and the mixer series I know best. We use an XR-18 at church and I have one in my shed as my “main mixer”. It’s controlled via Open Sound Control – with a few customizations. There’s a reasonable amount of documentation, albeit scattered across the web and mostly aimed at the (higher end) X-32. The Unofficial X32/M32 OSC Protocol document by Patrickā€Gilles Maillot is probably the most helpful.

SoundCraft Ui series


The SoundCraft Ui series (Ui12, Ui16, Ui24R) is a set of mixers I initially considered back in 2020/2021 when doing research. Big hat tip to Tom Der from SoundCraft, who sent me documentation for the protocol that Ui mixers use for control. (With an explicit “this isn’t supported” note, which is entirely reasonable.) I recently found a more recent version of that documentation on a Crestron Programmers Group (which I joined purely to get access to the doc). In other words, the documentation does exist and is somewhat public, but it’s not as easily accessible as the OSC documentation.

I now have a Ui24R which I’m enjoying playing with. I’ll be blogging more about the protocol later.

Allen and Heath Qu series


The Qu series is a range of digital mixers, most of which have physical control surfaces. I have a Qu-Sb on order, but I’m not expecting it to arrive for a while. (They’re back-ordered everywhere, basically.)

These mixers can be controlled by RTP-MIDI – effectively, a MIDI connection over the network. Allen and Heath provide what looks to be pretty comprehensive documentation – although as I haven’t started implementing it yet, it’s hard to say that it’s truly accurate and comprehensive just now. (I’m pretty hopeful though.) I’ve already used MIDI quite a bit for other projects, and I’m hoping I’ll be able to use that abstraction (either with an existing RTP-MIDI driver or cobbling together just the bits I need myself).

RCF M-18


The M-18 is unique in this set, as all the sockets are on the back rather than the front. That makes it less attractive for rack-mounting, unless you can rack mount it backwards (which would then be fine in terms of audio cables, but annoying for power). One thing it very much has in its favour is price though – it’s the cheapest of any of the mixers in this post.

It isn’t well-documented in terms of control protocol, but there’s a project on GitHub which reliably informs me that it implements OSC (like the X-Air series does). That could be very interesting in terms of seeing how much I’d need to change my OSC code; implementing a protocol with only one peer to test against is always a risky business.

PreSonus StudioLive Series III

StudioLive 16R

There are three options in the StudioLive Series III “R” range: 16R, 24R and 32R. (It looks like Series III mixers without the “R” suffix, i.e. the non-rack-mounted ones, have been discontinued, but that the R range is still going.) The 16R is a mere 1U for racking, which is very appealing – with the downside that inputs are at the front and outputs are at the back. It also trades height for depth – at 305mm deep, it’s deeper than the studio rack cabinet I have, and I suspect I’m not alone in that. As it’s so short though, I’m sure I could find another space for it…

It uses the “ucnet” protocol, which is proprietary to PreSonus and not documented… but there’s a project on GitHub where the author has performed quite a lot of reverse engineering already and documented his findings. This would certainly be an interesting mixer to include, although it’s pricy.

Mackie DL Series

Mackie DL16S

The Mackie DL16S and its 32-input cousins the DL32S and DL32R are all rack-mountable mixers, and the DL32R also features Dante audio networking which I’d love to dabble with some time.

Unfortunately, as far as I can tell there’s no documentation for the Master Fader Control app which is used to control the mixer… which means I’d be stuck reverse-engineering from scratch. While that can be fun, it’s something I really don’t have the time for. I’m not saying I’d object if I found one going for a song on ebay, but I really can’t justify buying one with only a small likelihood of getting anywhere with it. So for the moment at least, the Mackie DL series is unlikely to make it into my shed. (That’s probably a good thing really; arguably one really can have too many mixers.)