public DateTimeGranularitySpec(String granularity) {
    Preconditions.checkArgument(StringUtils.isNotEmpty(granularity), "Must provide granularity");

    char separator;
    int sizePosition;
    int timeUnitPosition;
    int minTokens;
    int maxTokens;

    if (Character.isDigit(granularity.charAt(0))) {
      // Colon format: 'size:timeUnit'
      separator = COLON_SEPARATOR;
      sizePosition = COLON_FORMAT_SIZE_POSITION;
      timeUnitPosition = COLON_FORMAT_TIME_UNIT_POSITION;

      minTokens = COLON_FORMAT_NUM_TOKENS;
      maxTokens = COLON_FORMAT_NUM_TOKENS;
    } else {
      // Pipe format: 'timeUnit(|size)'
      separator = PIPE_SEPARATOR;
      sizePosition = PIPE_FORMAT_SIZE_POSITION;
      timeUnitPosition = PIPE_FORMAT_TIME_UNIT_POSITION;

      minTokens = PIPE_FORMAT_MIN_TOKENS;
      maxTokens = PIPE_FORMAT_MAX_TOKENS;
    }

    String[] granularityTokens = StringUtil.split(granularity, separator, 2);
    Preconditions.checkArgument(granularityTokens.length >= minTokens && granularityTokens.length <= maxTokens,
            "Invalid granularity: %s, must be of format 'timeUnit(|size)' or 'size:timeUnit'", granularity);

    try {
      _timeUnit = TimeUnit.valueOf(granularityTokens[timeUnitPosition]);
    } catch (Exception e) {
      throw new IllegalArgumentException(
          String.format("Invalid time unit: %s in granularity: %s", granularityTokens[timeUnitPosition],
              granularity));
    }

    // New format without explicitly setting size - use default size = 1
    if (granularityTokens.length == PIPE_FORMAT_MIN_TOKENS) {
      _size = 1;
      return;
    }

    try {
      _size = Integer.parseInt(granularityTokens[sizePosition]);
    } catch (Exception e) {
      throw new IllegalArgumentException(
          String.format("Invalid size: %s in granularity: %s", granularityTokens[sizePosition], granularity));
    }
    Preconditions.checkArgument(_size > 0, "Invalid size: %s in granularity: %s, must be positive", _size, granularity);
  }
@Test
  public void testDateTimeGranularitySpec() {
    // Old format
    DateTimeGranularitySpec dateTimeGranularitySpec = new DateTimeGranularitySpec("1:HOURS");
    assertEquals(dateTimeGranularitySpec.getSize(), 1);
    assertEquals(dateTimeGranularitySpec.getTimeUnit(), TimeUnit.HOURS);
    assertEquals(dateTimeGranularitySpec.granularityToMillis(), 3600000);

    dateTimeGranularitySpec = new DateTimeGranularitySpec("15:MINUTES");
    assertEquals(dateTimeGranularitySpec.getSize(), 15);
    assertEquals(dateTimeGranularitySpec.getTimeUnit(), TimeUnit.MINUTES);
    assertEquals(dateTimeGranularitySpec.granularityToMillis(), 900000);

    dateTimeGranularitySpec = new DateTimeGranularitySpec("1:MILLISECONDS");
    assertEquals(dateTimeGranularitySpec.getSize(), 1);
    assertEquals(dateTimeGranularitySpec.getTimeUnit(), TimeUnit.MILLISECONDS);
    assertEquals(dateTimeGranularitySpec.granularityToMillis(), 1);

    // New format
    dateTimeGranularitySpec = new DateTimeGranularitySpec("HOURS|1");
    assertEquals(dateTimeGranularitySpec.getSize(), 1);
    assertEquals(dateTimeGranularitySpec.getTimeUnit(), TimeUnit.HOURS);
    assertEquals(dateTimeGranularitySpec.granularityToMillis(), 3600000);

    dateTimeGranularitySpec = new DateTimeGranularitySpec("MINUTES|15");
    assertEquals(dateTimeGranularitySpec.getSize(), 15);
    assertEquals(dateTimeGranularitySpec.getTimeUnit(), TimeUnit.MINUTES);
    assertEquals(dateTimeGranularitySpec.granularityToMillis(), 900000);

    dateTimeGranularitySpec = new DateTimeGranularitySpec("MILLISECONDS|1");
    assertEquals(dateTimeGranularitySpec.getSize(), 1);
    assertEquals(dateTimeGranularitySpec.getTimeUnit(), TimeUnit.MILLISECONDS);
    assertEquals(dateTimeGranularitySpec.granularityToMillis(), 1);

    // New format without explicitly setting size
    dateTimeGranularitySpec = new DateTimeGranularitySpec("HOURS");
    assertEquals(dateTimeGranularitySpec.getSize(), 1);
    assertEquals(dateTimeGranularitySpec.getTimeUnit(), TimeUnit.HOURS);
    assertEquals(dateTimeGranularitySpec.granularityToMillis(), 3600000);

    dateTimeGranularitySpec = new DateTimeGranularitySpec("MINUTES");
    assertEquals(dateTimeGranularitySpec.getSize(), 1);
    assertEquals(dateTimeGranularitySpec.getTimeUnit(), TimeUnit.MINUTES);
    assertEquals(dateTimeGranularitySpec.granularityToMillis(), 60000);

    dateTimeGranularitySpec = new DateTimeGranularitySpec("MILLISECONDS");
    assertEquals(dateTimeGranularitySpec.getSize(), 1);
    assertEquals(dateTimeGranularitySpec.getTimeUnit(), TimeUnit.MILLISECONDS);
    assertEquals(dateTimeGranularitySpec.granularityToMillis(), 1);

    // IllegalArguments
    assertThrows(IllegalArgumentException.class, () -> new DateTimeGranularitySpec(""));
    assertThrows(IllegalArgumentException.class, () -> new DateTimeGranularitySpec("1"));
    assertThrows(IllegalArgumentException.class, () -> new DateTimeGranularitySpec("1:1"));
    assertThrows(IllegalArgumentException.class, () -> new DateTimeGranularitySpec("1|1"));
    assertThrows(IllegalArgumentException.class, () -> new DateTimeGranularitySpec("DAY:DAY"));
    assertThrows(IllegalArgumentException.class, () -> new DateTimeGranularitySpec("DAY|DAY"));
    assertThrows(IllegalArgumentException.class, () -> new DateTimeGranularitySpec("DAY:1"));
    assertThrows(IllegalArgumentException.class, () -> new DateTimeGranularitySpec("1|DAY"));
    assertThrows(IllegalArgumentException.class, () -> new DateTimeGranularitySpec("EPOCH|DAY|1"));
    assertThrows(IllegalArgumentException.class, () -> new DateTimeGranularitySpec("1:DAY:EPOCH"));
  }