import java.time.DateTimeException;
import java.time.ZoneOffset;
import java.util.Random;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Warmup;

@Fork(value = 3, jvmArgsAppend = "-Djmh.stack.lines=3")
@Warmup(iterations = 5, time = 3)
@Measurement(iterations = 7, time = 3)
public class OffsetZoneCacheBenchmark {

    @State(Scope.Benchmark)
    public static class BenchmarkState {

        static final Random RANDOM = new Random();
        int seconds;

        @Setup(Level.Trial)
        public void setup() throws Exception {
            seconds = 15 * 60 * (-4 * 12 + RANDOM.nextInt(8 * 12));
        }

        @TearDown(Level.Trial)
        public void teardown() throws Exception {
        }
    }

    @Benchmark
    public Object testNative(BenchmarkState state) {
        return ZoneOffset.ofTotalSeconds(state.seconds);
    }

    @Benchmark
    public Object testAlternative(BenchmarkState state) {
        return ofTotalSeconds(state.seconds);
    }

    static final ZoneOffset[] SECONDS_CACHE;
    static {
        SECONDS_CACHE = new ZoneOffset[18 * 2 * 4 + 1];

        for (int i = 0; i < SECONDS_CACHE.length; i++) {
            SECONDS_CACHE[i] = ZoneOffset.ofTotalSeconds((i - 18 * 4) * 15 * 60);
        }
    }

    public static ZoneOffset ofTotalSeconds(int totalSeconds) {
        if (Math.abs(totalSeconds) > 18 * 3600)
            throw new DateTimeException("Zone offset not in valid range: -18:00 to +18:00");

        if (totalSeconds % (15 * 60) == 0)
            return SECONDS_CACHE[totalSeconds / (15 * 60) + (18 * 4)];
        else
            return ZoneOffset.ofTotalSeconds(totalSeconds);
    }

}

