code: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   */
  
  package org.apache.skywalking.oap.server.core.query;
  
  import org.apache.skywalking.oap.server.core.Const;
  import org.apache.skywalking.oap.server.core.UnexpectedException;
  import org.apache.skywalking.oap.server.core.query.enumeration.Step;
  import org.joda.time.DateTime;
  import org.joda.time.format.DateTimeFormat;
  import org.joda.time.format.DateTimeFormatter;
  
  import java.util.LinkedList;
  import java.util.List;
  
  public enum DurationUtils {
      INSTANCE;
  
      private static final int MAX_TIME_RANGE = 500;
  
      public static final DateTimeFormatter YYYY_MM_DD = DateTimeFormat.forPattern("yyyy-MM-dd");
      public static final DateTimeFormatter YYYY_MM_DD_HH = DateTimeFormat.forPattern("yyyy-MM-dd HH");
      public static final DateTimeFormatter YYYY_MM_DD_HHMM = DateTimeFormat.forPattern("yyyy-MM-dd HHmm");
      public static final DateTimeFormatter YYYY_MM_DD_HHMMSS = DateTimeFormat.forPattern("yyyy-MM-dd HHmmss");
  
      private static final DateTimeFormatter YYYYMMDD = DateTimeFormat.forPattern("yyyyMMdd");
      private static final DateTimeFormatter YYYYMMDDHH = DateTimeFormat.forPattern("yyyyMMddHH");
      private static final DateTimeFormatter YYYYMMDDHHMM = DateTimeFormat.forPattern("yyyyMMddHHmm");
      private static final DateTimeFormatter YYYYMMDDHHMMSS = DateTimeFormat.forPattern("yyyyMMddHHmmss");
  
      /**
       * Convert date in `yyyy-MM-dd HHmmss` style to `yyyyMMddHHmmss` no matter the precision. Such as, in day precision,
       * this covert `yyyy-MM-dd` style to `yyyyMMdd`.
       */
      public long convertToTimeBucket(Step step, String dateStr) {
          verifyDateTimeString(step, dateStr);
          dateStr = dateStr.replaceAll(Const.LINE, Const.EMPTY_STRING);
          dateStr = dateStr.replaceAll(Const.SPACE, Const.EMPTY_STRING);
          return Long.parseLong(dateStr);
      }
  
      public long startTimeDurationToSecondTimeBucket(Step step, String dateStr) {
          long secondTimeBucket = convertToTimeBucket(step, dateStr);
          switch (step) {
              case DAY:
                  return secondTimeBucket * 100 * 100 * 100;
              case HOUR:
                  return secondTimeBucket * 100 * 100;
              case MINUTE:
                  return secondTimeBucket * 100;
              case SECOND:
                  return secondTimeBucket;
          }
          throw new UnexpectedException("Unsupported step " + step.name());
      }
  
      public long endTimeDurationToSecondTimeBucket(Step step, String dateStr) {
          long secondTimeBucket = convertToTimeBucket(step, dateStr);
          switch (step) {
              case DAY:
                  return ((secondTimeBucket * 100 + 23) * 100 + 59) * 100 + 59;
              case HOUR:
                  return (secondTimeBucket * 100 + 59) * 100 + 59;
              case MINUTE:
                  return secondTimeBucket * 100 + 59;
              case SECOND:
                  return secondTimeBucket;
          }
          throw new UnexpectedException("Unsupported step " + step.name());
      }
  
      public List<PointOfTime> getDurationPoints(Step step, long startTimeBucket, long endTimeBucket) {
          DateTime dateTime = parseToDateTime(step, startTimeBucket);
  
          List<PointOfTime> durations = new LinkedList<>();
          durations.add(new PointOfTime(startTimeBucket));
          if (startTimeBucket == endTimeBucket) {
              return durations;
          }
  
          int i = 0;
          do {
              switch (step) {
                  case DAY:
                      dateTime = dateTime.plusDays(1);
                      String timeBucket = YYYYMMDD.print(dateTime);
                      durations.add(new PointOfTime(Long.parseLong(timeBucket)));
                      break;
                  case HOUR:
                      dateTime = dateTime.plusHours(1);
                      timeBucket = YYYYMMDDHH.print(dateTime);
                      durations.add(new PointOfTime(Long.parseLong(timeBucket)));
                      break;
                  case MINUTE:
                      dateTime = dateTime.plusMinutes(1);
                      timeBucket = YYYYMMDDHHMM.print(dateTime);
                      durations.add(new PointOfTime(Long.parseLong(timeBucket)));
                      break;
                  case SECOND:
                      dateTime = dateTime.plusSeconds(1);
                      timeBucket = YYYYMMDDHHMMSS.print(dateTime);
                      durations.add(new PointOfTime(Long.parseLong(timeBucket)));
                      break;
              }
              i++;
              if (i > MAX_TIME_RANGE) {
                  // days, hours, minutes or seconds
                  String stepStr = step.name().toLowerCase() + "s";
                  String errorMsg = String.format(
                          "Duration data error, the range between the start time and the end time can't exceed %d %s",
                          MAX_TIME_RANGE, stepStr);
                  throw new UnexpectedException(errorMsg);
              }
          }
          while (endTimeBucket != durations.get(durations.size() - 1).getPoint());
  
          return durations;
      }
  
      public long startTimeToTimestamp(Step step, String dateStr) {
          switch (step) {
              case DAY:
                  return YYYY_MM_DD.parseMillis(dateStr);
              case HOUR:
                  return YYYY_MM_DD_HH.parseMillis(dateStr);
              case MINUTE:
                  return YYYY_MM_DD_HHMM.parseMillis(dateStr);
              case SECOND:
                  return YYYY_MM_DD_HHMMSS.parseMillis(dateStr);
          }
          throw new UnexpectedException("Unsupported step " + step.name());
      }
  
      public long endTimeToTimestamp(Step step, String dateStr) {
          switch (step) {
              case DAY:
                  return YYYY_MM_DD.parseDateTime(dateStr).plusDays(1).getMillis();
              case HOUR:
                  return YYYY_MM_DD_HH.parseDateTime(dateStr).plusHours(1).getMillis();
              case MINUTE:
                  return YYYY_MM_DD_HHMM.parseDateTime(dateStr).plusMinutes(1).getMillis();
              case SECOND:
                  return YYYY_MM_DD_HHMMSS.parseDateTime(dateStr).plusSeconds(1).getMillis();
          }
          throw new UnexpectedException("Unsupported step " + step.name());
      }
  
      public DateTime parseToDateTime(Step step, long time) {
          switch (step) {
              case DAY:
                  return YYYYMMDD.parseDateTime(String.valueOf(time));
              case HOUR:
                  return YYYYMMDDHH.parseDateTime(String.valueOf(time));
              case MINUTE:
                  return YYYYMMDDHHMM.parseDateTime(String.valueOf(time));
              case SECOND:
                  return YYYYMMDDHHMMSS.parseDateTime(String.valueOf(time));
          }
          throw new UnexpectedException("Unexpected downsampling: " + step.name());
      }
  
      public void verifyDateTimeString(Step step, String dateStr) {
          switch (step) {
              case DAY:
                  YYYY_MM_DD.parseDateTime(dateStr);
                  return;
              case HOUR:
                  YYYY_MM_DD_HH.parseDateTime(dateStr);
                  return;
              case MINUTE:
                  YYYY_MM_DD_HHMM.parseDateTime(dateStr);
                  return;
              case SECOND:
                  YYYY_MM_DD_HHMMSS.parseDateTime(dateStr);
                  return;
          }
          throw new UnexpectedException("Unsupported step " + step.name());
      }
  }
  