0

for example:I want to change LocalDateTime.now() return result in non-production enviroment, I am trying to invoke the following code,but error occors:


 @Test
 public void test3() {
 System.out.println(LocalDateTime.now());
 Instrumentation instrumentation = ByteBuddyAgent.install();
 Class<?>[] cls = instrumentation.getAllLoadedClasses();
 for (Class<?> cl : cls) {
 if ("java.time.LocalDateTime".equals(cl.getName())) {
 try {
 LocalDateTime mockData = LocalDateTime.of(2020, 1, 1, 0, 0, 0, 0);
 byte[] bytes = new ByteBuddy()
 .redefine(LocalDateTime.class)
 .method(ElementMatchers.named("now").and(ElementMatchers.takesNoArguments()))
 .intercept(FixedValue.value(mockData))
 .make()
 .getBytes();
 ClassDefinition classDefinition = new ClassDefinition(cl, bytes);
 instrumentation.redefineClasses(classDefinition);
 } catch (Throwable e) {
 e.printStackTrace();
 }
 }
 }
 System.out.println(LocalDateTime.now());
 }

2024年03月03日T15:33:59.291699 java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields) at java.instrument/sun.instrument.InstrumentationImpl.redefineClasses0(Native Method) at java.instrument/sun.instrument.InstrumentationImpl.redefineClasses(InstrumentationImpl.java:195) at com.yi.component.test.agent.bytecode.ByteBuddyTest.test3(ByteBuddyTest.java:76) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.junit.runners.model.FrameworkMethod1ドル.runReflectiveCall(FrameworkMethod.java:59) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner3ドル.evaluate(ParentRunner.java:306) at org.junit.runners.BlockJUnit4ClassRunner1ドル.evaluate(BlockJUnit4ClassRunner.java:100) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) at org.junit.runners.ParentRunner4ドル.run(ParentRunner.java:331) at org.junit.runners.ParentRunner1ドル.schedule(ParentRunner.java:79) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) at org.junit.runners.ParentRunner.access100ドル(ParentRunner.java:66) at org.junit.runners.ParentRunner2ドル.evaluate(ParentRunner.java:293) at org.junit.runners.ParentRunner3ドル.evaluate(ParentRunner.java:306) at org.junit.runners.ParentRunner.run(ParentRunner.java:413) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) at com.intellij.rt.junit.IdeaTestRunner$Repeater1ドル.execute(IdeaTestRunner.java:38) at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)

I want to change LocalDateTime.now() return result in non-production enviroment

Peter Cordes
378k50 gold badges747 silver badges1k bronze badges
asked Mar 3, 2024 at 7:37
11
  • 1
    Why do you want to change the method? What is it you're actually trying to do for which you think you need to do this? Commented Mar 3, 2024 at 7:41
  • 6
    The right way to have a different "current time" in non-production environments (for example, for testing what will happen at some future date) is never to use LocalDateTime.now(), but always LocalDateTime.now(Clock clock) instead, and inject a suitable Clock implementation into any class that needs the current time. Are you sure that "How can I modify the core api in Java" is the question you actually want to ask? Commented Mar 3, 2024 at 7:46
  • @DawoodibnKareem: In a non-production environment, another option is to set the system clock to any time you want. (e.g. inside a VM, but outside the JVM.) Or perhaps with LD_PRELOAD with a library that intercepts libc function calls by the JVM, so clock_gettime will return different times. That would let LocalDateTime stuff be different from the mod times on files your program writes. Commented Mar 3, 2024 at 8:04
  • purpose : change the implementation of get system time in non-prd-env,and the developer do nothing,and can not change the system clock. Commented Mar 3, 2024 at 8:15
  • 1
    @PeterCordes - Well yea ... but that potentially effects the entire system. It will break some things (e.g. those that depend on clocks being in sync with other machines), and make other things behave in undesirable ways; e.g. timestamps in log messages for the entire environment. Commented Mar 3, 2024 at 8:16

2 Answers 2

2

It sounds like what you actually want to do is be able to control the time in your tests, which is very sensible.

The simpler way to go about this is using dependency injection: instead of having the class you want to test know about the system clock, pass in the clock so that you can vary the clock you use for testing and for production.

As an example, let's say you need to compute fines for loaned items that are overdue (e.g. for a library or something). Your business rules may be:

  • The fine is 1 for every day the item is overdue
  • The fine is 0 if the item is not overdue

Note that I'm using recent tools: JUnit 5 and records were introduced in Java 14 (I'm on 21).

Here is a definition of a loaned item:

import java.time.LocalDate;
public record LoanedItem(String description, LocalDate dueOn) {}

and test cases expressing the behaviour:

import org.junit.jupiter.api.Test;
import java.time.Clock;
import java.time.LocalDate;
import java.time.ZoneOffset;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class FineCalculatorTest {
 @Test
 public void the_fine_is_1_for_every_day_the_item_is_overdue() {
 LocalDate dueOn = LocalDate.of(2024, 2, 1);
 LoanedItem loanedItem = new LoanedItem("Lawnmower", dueOn);
 int daysOverdue = 2;
 LocalDate today = dueOn.plusDays(daysOverdue);
 Clock clock = Clock.fixed(today.atStartOfDay().toInstant(ZoneOffset.UTC), ZoneOffset.UTC);
 FineCalculator fineCalculator = new FineCalculator(clock);
 long fine = fineCalculator.fineFor(loanedItem);
 assertEquals(2, fine);
 }
 @Test
 public void the_fine_is_0_if_the_item_is_not_overdue() {
 LocalDate dueOn = LocalDate.of(2024, 2, 1);
 LoanedItem loanedItem = new LoanedItem("Lawnmower", dueOn);
 LocalDate today = dueOn.minusDays(7);
 Clock clock = Clock.fixed(today.atStartOfDay().toInstant(ZoneOffset.UTC), ZoneOffset.UTC);
 FineCalculator fineCalculator = new FineCalculator(clock);
 long fine = fineCalculator.fineFor(loanedItem);
 assertEquals(0, fine);
 }
}

The key thing to note is that FineCalculator takes, as a constructor parameter, an instance of java.time.Clock.

Clock.fixed creates a Clock whose time is fixed at what you give it. In the first test, then, I create a clock that's fixed at two days after the item's due date:

int daysOverdue = 2;
LocalDate today = dueOn.plusDays(daysOverdue);
Clock clock = Clock.fixed(today.atStartOfDay().toInstant(ZoneOffset.UTC), ZoneOffset.UTC);

so we expect a fine of 2.

The second case is similar: I pass a clock that's fixed at 7 days before the due date, so we expect a fine of 0.

The implementation of FineCalculator then uses the clock to work out what today is and calculate the fine:

import java.time.Clock;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
public class FineCalculator {
 private Clock clock;
 public FineCalculator(Clock clock) {
 this.clock = clock;
 }
 public long fineFor(LoanedItem loanedItem) {
 LocalDate today = clock.instant().atZone(ZoneOffset.UTC).toLocalDate();
 long daysOverdue = ChronoUnit.DAYS.between(loanedItem.dueOn(), today);
 if (daysOverdue < 0) {
 daysOverdue = 0;
 }
 long fine = 1 * daysOverdue;
 return fine;
 }
}

When you create the FineCalculator in the production code, you'd pass the system clock. Here's a sample main:

import java.time.Clock;
import java.time.LocalDate;
import java.time.ZoneOffset;
public class Main {
 public static void main(String[] args) {
 LoanedItem item = new LoanedItem("Screwdriver", LocalDate.of(2024, 9, 1));
 Clock clock = Clock.system(ZoneOffset.UTC);
 FineCalculator fineCalculator = new FineCalculator(clock);
 System.out.println("Loaned item: " + item);
 System.out.println("Fine is: " + fineCalculator.fineFor(item));
 }
}
answered Mar 3, 2024 at 10:06
Sign up to request clarification or add additional context in comments.

Comments

-1

Solution found :

solution 1:

git clone https://github.com/wolfcw/libfaketime.git
make
sudo make install
faketime '@2007年01月01日 00:00:00' java -jar xxx.jar

solution 2:

Use linux 5.6+ kernel feature - time namespace 
https://github.com/moby/moby/issues/39163
https://securitylabs.datadoghq.com/articles/container-security-fundamentals-part-2/#time-namespace
answered Mar 6, 2024 at 8:59

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.