Repository: hashicorp/vault
File: ui/app/components/clients/date-range.ts (TypeScript)

Code before:
    super(owner, args);
    this.setTrackedFromArgs();
  }

  setTrackedFromArgs() {
    if (this.args.startTime) {
      this.startDate = parseAPITimestamp(this.args.startTime, 'yyyy-MM') as string;
    }
    if (this.args.endTime) {
      this.endDate = parseAPITimestamp(this.args.endTime, 'yyyy-MM') as string;
    }
  }

  formattedDate = (isoTimestamp: string) => {
    return parseAPITimestamp(isoTimestamp, 'MMMM yyyy');
  };

  get historicalBillingPeriods() {
    const count = this.args.retentionMonths / 12;
    const periods: string[] = [];

    // for each historical billing period, start_date will be billing_start_date - (12 * multiple)
    for (let i = 1; i <= count; i++) {
      const startDate = new Date(this.args.billingStartTime);
      startDate.setMonth(startDate.getMonth() - 12 * i);

      periods.push(startDate.toISOString());
    }

    return periods;
  }

  get useDefaultDates() {
    return !this.startDate && !this.endDate;
  }

  get validationError() {
    if (this.useDefaultDates && this.version.isEnterprise) {
      // this means we want to reset, which is fine for ent only
      return null;
    }
    if (!this.startDate || !this.endDate) {
      return 'You must supply both start and end dates.';
    }
    if (this.startDate > this.endDate) {
      return 'Start date must be before end date.';
    }
    return null;
  }

  @action onClose() {

Review comment (bug):
Just a heads up about a subtle issue here using `new Date()`. Even though we're passing a UTC timestamp, javascript Date objects are automatically converted to local time which can introduce inconsistencies when doing date math (which setMonth) operators in local time. Even though we (fortunately) don't have to worry about the exact day or time, we don't want to accidentally send the wrong month which could happen if `new Date()` is called in a browser where the timezone is UTC-12 for example. 

To keep everything in UTC and avoid local time zone drift, you could use Date.UTC(...) combined with getUTCFullYear(), getUTCMonth(), etc. to ensure billing periods are calculated consistently.

Let me know if you have more questions! It might make sense to make this a util.

Code after:
  constructor(owner: unknown, args: Args) {
    super(owner, args);
    this.setTrackedFromArgs();
  }

  setTrackedFromArgs() {
    if (this.args.startTime) {
      this.startDate = parseAPITimestamp(this.args.startTime, 'yyyy-MM') as string;
      this.selectedStart = this.formattedDate(this.args.startTime) as string;
    }
    if (this.args.endTime) {
      this.endDate = parseAPITimestamp(this.args.endTime, 'yyyy-MM') as string;
    }
  }

  formattedDate = (isoTimestamp: string) => {
    return parseAPITimestamp(isoTimestamp, 'MMMM yyyy');
  };

  get historicalBillingPeriods() {
    // we want whole billing periods
    const count = Math.floor(this.args.retentionMonths / 12);
    const periods: string[] = [];

    for (let i = 1; i <= count; i++) {
      const startDate = new Date(this.args.billingStartTime);
      const utcMonth = startDate.getUTCMonth();
      const utcYear = startDate.getUTCFullYear() - i;

      startDate.setUTCFullYear(utcYear);
      startDate.setUTCMonth(utcMonth);

      periods.push(startDate.toISOString());
    }

    return periods;
  }

  get useDefaultDates() {
    return !this.startDate && !this.endDate;
  }

  get validationError() {
    if (this.useDefaultDates && this.version.isEnterprise) {
      // this means we want to reset, which is fine for ent only
      return null;
    }
    if (!this.startDate || !this.endDate) {
      return 'You must supply both start and end dates.';
    }
    if (this.startDate > this.endDate) {