{"record":{"id":"e434b046cabf7ed2","repo":"apache/druid","slug":"period-cannot-be-converted-to-milliseconds-as-some","errorCode":null,"errorMessage":"Period cannot be converted to milliseconds as some fields mays vary in length with chronology ","messagePattern":"Period cannot be converted to milliseconds as some fields mays vary in length with chronology ","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/java/util/common/granularity/PeriodGranularity.java","lineNumber":604,"sourceCode":"        current = chronology.add(period, current, -1);\n      } while (t < current);\n    }\n    return current;\n  }\n\n  private long truncateMillisPeriod(final long t)\n  {\n    // toStandardDuration assumes days are always 24h, and hours are always 60 minutes,\n    // which may not always be the case, e.g if there are daylight saving changes.\n    if (chronology.days().isPrecise() && chronology.hours().isPrecise()) {\n      final long millis = period.toStandardDuration().getMillis();\n      long offset = t % millis - origin % millis;\n      if (offset < 0) {\n        offset += millis;\n      }\n      return t - offset;\n    } else {\n      throw new UnsupportedOperationException(\n          \"Period cannot be converted to milliseconds as some fields mays vary in length with chronology \" + chronology\n      );\n    }\n  }\n\n  @Override\n  public void serialize(JsonGenerator jsonGenerator, SerializerProvider serializerProvider)\n      throws IOException\n  {\n    // Retain the same behavior as before #3850.\n    // i.e. when Granularity class was an enum.\n    if (GranularityType.isStandard(this)) {\n      jsonGenerator.writeString(GranularityType.fromPeriod(getPeriod()).toString());\n    } else {\n      jsonGenerator.writeStartObject();\n      jsonGenerator.writeStringField(\"type\", \"period\");\n      jsonGenerator.writeObjectField(\"period\", getPeriod());\n      jsonGenerator.writeObjectField(\"timeZone\", getTimeZone());","sourceCodeStart":586,"sourceCodeEnd":622,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/java/util/common/granularity/PeriodGranularity.java#L586-L622","documentation":"PeriodGranularity.truncate() can only bucket timestamps arithmetically when the period's total duration is expressible as a fixed number of milliseconds. Periods containing calendar-varying fields (months, years, and with some chronologies weeks/days) have no fixed millisecond length, so truncateMillisPeriod throws UnsupportedOperationException naming the chronology.","triggerScenarios":"Truncating a timestamp against a PeriodGranularity whose period contains months or years (e.g. Period.parse(\"P1M\") or \"P1Y\") — called via truncate/bucketStart during query time-bucketing or segment allocation.","commonSituations":"Month or year query granularity on data where the truncation path expects fixed-length periods; chronology (e.g. non-ISO or UTC-less) configurations that make week/day fields variable length.","solutions":["Use ISO chronology with UTC and periods made of fixed-length fields (weeks, days, hours, minutes, seconds, millis) for truncation-dependent code paths","For month/year bucketing, keep the standard Granularity enum (MONTH/YEAR) implementations that handle calendar bucketing via Chronology-based truncation instead of the millis path","Verify the granularity's chronology is set to DateTimeZones.UTC / ISOChronology so fixed-period truncation applies"],"exampleFix":"// before\nGranularity g = new PeriodGranularity(Period.parse(\"P1M\"), origin, chronology); // thrown in truncate\n// after\nGranularity g = GranularityType.MONTH.getDefaultGranularity(); // calendar-aware month bucketing","handlingStrategy":"validation","validationCode":"Period p = granularity.getPeriod();\nboolean fixedLength = p.getMonths() == 0 && p.getYears() == 0;\n// if (!fixedLength) avoid truncate-millis path or use Granularity.MONTH/YEAR","typeGuard":"boolean truncatable = granularity.getPeriod().getMonths() == 0 && granularity.getPeriod().getYears() == 0;","tryCatchPattern":"try { long b = granularity.truncate(ts); } catch (UnsupportedOperationException e) { /* use calendar-aware Granularity.MONTH/YEAR */ }","preventionTips":["Avoid PeriodGranularity with months/years for code paths that truncate timestamps arithmetically","Use built-in MONTH/YEAR Granularity constants for calendar bucketing","Keep chronology ISO/UTC so week/day periods remain fixed-length"],"tags":["granularity","period","unsupported-operation","chronology","druid"],"backgroundTag":"unsupported-operation","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}