Count half hours since midnight Europe/London

44 Views Asked by At

I have a seemingly simple task, I wish to solve it in pure Java 21 - although will use a library if I must.

I need to count the number of completed half hour intervals, from midnight on the same day - this is specially for the UK electricity market and there are specific rules for this counting. There must be

  • 48 periods numbered 1..48 on a normal day
  • 46 periods numbered 1..46 on a "short day" (clocks go forward)
  • 50 periods numbered 1..50 on a "long day" (clocks go back)

You can think of this as the count of half hours of "wall time" from the start of the day until now() (or a specific instant). Time zones are relevant only in that the change in zone causes there to be one more or less hour in the day in the UK.

So the method signature is something like

int settlementPeriod(final OffsetDateTime date)

i.e. given an arbitrary point on the timeline, determine the correct period in the UK.


My first attempt was very simple, essentially get the start of the day in the UK and that same point in the UK; take the duration between them and divide by the half hour

public static int settlementPeriod(final OffsetDateTime readingTime) {
    final var zone = ZoneId.of("Europe/London");
    final var startOfDay = readingTime.truncatedTo(ChronoUnit.DAYS).atZoneSameInstant(zone);
    final var duration = Duration.between(startOfDay, readingTime.atZoneSameInstant(zone));
    return (int) duration.dividedBy(Duration.ofMinutes(30)) + 1;
}

That works for normal days but for both "short" and "long" days fails miserably.

On a "short" day, once the transition occurs, the index jumps one hour.

On a "long" day, once the transition occurs, the index repeats one hour.


Right, so the time zone I get when I convert the readingTime to the local zone is the time zone after the transition - so then I'm comparing

  • for a "short" day midnight BST to >=2AM BST
  • for a "long" day midnight GMT to >= 1AM GMT

Where, the startOfDay should be in the pre-transition time zone.

I came up with the following which almost passes my tests

public static int settlementPeriod(final OffsetDateTime readingTime) {
    final var zone = ZoneId.of("Europe/London");
    final var transition = zone.getRules().previousTransition(readingTime.toInstant());
    final ZonedDateTime startOfDay;
    if (Objects.equals(transition.getDateTimeBefore().toLocalDate(), readingTime.toLocalDate())) {
        startOfDay = readingTime.toLocalDate().atStartOfDay(transition.getOffsetBefore());
    } else {
        startOfDay = readingTime.truncatedTo(ChronoUnit.DAYS).atZoneSameInstant(zone);
    }
    final var duration = Duration.between(startOfDay, readingTime.atZoneSameInstant(zone));
    return (int) duration.dividedBy(Duration.ofMinutes(30)) + 1;
}

But has a couple of problems

  1. it's rather ugly, having to determine if today is a transition day
  2. if the passed in readingTime is exaclty on the transition time, previousTransition returns the wrong transition (makes sense)

What is the "correct" way to do this with java.time?

1

There are 1 best solutions below

1
Sweeper On BEST ANSWER

You should first do atZoneSameInstant(zone), then truncatedTo(ChronoUnit.DAYS).

final var startOfDay = readingTime.atZoneSameInstant(zone).truncatedTo(ChronoUnit.DAYS);

Doing truncatedTo on an OffsetDateTime will keep the offset unchanged, but this is not what happens on a day with a time zone transition. The start of day might have a different offset than the offset of readingTime. By changing it to a ZonedDateTime first, you make sure that truncatedTo takes the time zone transitions into account.

Side note: I feel this method should take an Instant, or ZonedDateTime as parameter.