Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zoned date time #3

Open
wants to merge 11 commits into
base: option-0-no-api
Choose a base branch
from

Conversation

jejking-tw
Copy link

What is being fixed - and why?

Excess use of global configuration (including time zones). Started with introducing Clock and ZonedDateTime.

Followed up with some further simplification and refactorings which I hope make sense.

What has changed?

Some other notable points:

  • moves to ZonedDateTime so we actually know which day it is (in that zone) rather than relying on hidden system default time zone
  • simplifes ElecriticityReadingsGenerator with a stream. Supplies tests for this stream.
  • factors out PeakTimeMultiplier into a simple record
  • provides ElectricityReading with a secondary constructor to avoid the fixture factory
  • PricePLan is now constructed with simple Map<DayOfWeek, BigDecimal> for simpler mapping
  • Tidies up imports and formatting using the code conventions defined in the build

Largely simplifies things down a little more and reduces reliance on global settings such as timezones or clocks.

Some other notable points:
- simplifes ElecriticityReadingsGenerator with a stream. Supplies tests for this stream.
- factors out PeakTimeMultiplier into a simple record
- provides ElectricityReading with a secondary constructor to avoid the fixture factory
- PricePLan is now constructed with simple Map<DayOfWeek, BigDecimal> rather than a list for more determinism
- Tidies up imports and formatting using the code conventions defined in the build
readings.add(electricityReading);
previousReading = currentReading;
}
public static Stream<ElectricityReading> generateElectricityReadingStream(int days) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I buy returning a stream here. The implementation doesn't look much simpler, and every single caller wants a List instead of a Stream.

The name also adds a lot of stutter (unless every caller switches to a static import).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend the static import 😄 which was sort of my assumption anyway.

Lets think a bit more about streams - they're trivially convertible to lists anyway. To be honest, the functions also need some more possible parameters - eg the interval between the readings to generate and the function to generate the values. I hadn't quite finished removing all dependencies on hidden state such as Random...

src/test/java/tw/joi/energy/domain/PricePlanTest.java Outdated Show resolved Hide resolved
src/test/java/tw/joi/energy/domain/PricePlanTest.java Outdated Show resolved Hide resolved
@DisplayName("Get price should return price given non-peak date and time")
public void get_price_should_return_the_base_price_given_an_ordinary_date_time() {
ZonedDateTime nonPeakDateTime = ZonedDateTime.of(LocalDateTime.of(2017, Month.AUGUST, 31, 12, 0, 0),
ZoneId.of("GMT"));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't thought this through completely, but what's the value in using a time zone? I presume:

  • the local date time is in the time zone of the meter that produced the reading
  • only the local date/time matters when determining the day and therefore the zone isn't necessary
  • we'll never compare dates from two different meters in meaningful ways

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider the following. In the model, there is no time zone associated with a meter, rather the readings are captured as Instant - as in 'absolute' moments in time and not associated with a time zone.

So, such a reading could be in different days depending on where we are in the world. As the Javadoc says, there's no way to convert to an instant with an offset or timezone. Likewise, there's no way to map an Instant to a day of the week without an offset or timezone.

To make that conversion, you'd need to supply a Zone whichever way, and in the worst case you'd arbitrarily choose whatever the system property gave you, making yourself dependent on hidden global variables.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, huh, yeah. Then it seems most correct to associate a zone with the measurements (or with the smart meter). But that'd be a bigger change.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still being used by CostComparisonTest, which no longer compiles.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works on my machine ... That's possibly because it seems to have disappeared. I'll double check what's happening there.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't actually see that test in the branch option-0-no-api, so I'm a bit puzzled. Could you take another look?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, somehow that one was sitting around from another branch 🤷


private static final ZoneId GMT = ZoneId.of("GMT");
private static final String SMART_METER_ID = "10101010";
private static final Clock FIXED_CLOCK = Clock.fixed(Instant.ofEpochSecond(1721124813L), GMT);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious what the value of using a fixed clock is here. I get the general idea of fixing a clock to test time-based code, but it seems like we're not actually leveraging it here, and the resulting code is harder to understand (to my eyes anyway).

And the hardcoded epoch seconds are a bit distracting. Makes me curious what the significance is, and I have to use a converter just to find out that it's when you made this change 😅

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, that was maybe a bit cute. I'll make that easier to understand.

What I generally wanted to avoid was the dependence on the system clock or other hidden global variables.

You're right that it's not used in these specific tests, but it's sort of a matter of principle too 😄

src/main/java/tw/joi/energy/domain/PricePlan.java Outdated Show resolved Hide resolved
this.planName = planName;
this.energySupplier = energySupplier;
this.unitRate = unitRate;
this.peakTimeMultipliers = peakTimeMultipliers;
this.peakTimeMultipliers = Collections.unmodifiableMap(new HashMap<>(peakTimeMultipliers));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.peakTimeMultipliers = Collections.unmodifiableMap(new HashMap<>(peakTimeMultipliers));
this.peakTimeMultipliers = Map.copyOf(peakTimeMultipliers);

Credit goes to IntelliJ for that suggestion...

public record ElectricityReading(Instant time, BigDecimal reading) {}
public record ElectricityReading(Instant time, BigDecimal readingInKwH) {

public ElectricityReading(Clock clock, double readingInKwH) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this constructor meant to be for testing purposes (faking the current time, lighter syntax for the reading) or also for production usage?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants