All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.squarespace.cldrengine.calendars.CalendarManager Maven / Gradle / Ivy

The newest version!
package com.squarespace.cldrengine.calendars;

import static com.squarespace.cldrengine.utils.StringUtils.isEmpty;

import java.util.HashSet;
import java.util.Set;

import com.squarespace.cldrengine.api.Bundle;
import com.squarespace.cldrengine.api.CalendarDate;
import com.squarespace.cldrengine.api.CalendarType;
import com.squarespace.cldrengine.api.DateFormatOptions;
import com.squarespace.cldrengine.api.DateIntervalFormatOptions;
import com.squarespace.cldrengine.api.FormatWidthType;
import com.squarespace.cldrengine.api.NumberSymbolType;
import com.squarespace.cldrengine.calendars.SkeletonData.Field;
import com.squarespace.cldrengine.internal.DateTimePatternFieldType;
import com.squarespace.cldrengine.internal.Internals;
import com.squarespace.cldrengine.internal.Schema;
import com.squarespace.cldrengine.numbers.NumberParams;
import com.squarespace.cldrengine.parsing.DateTimePattern;
import com.squarespace.cldrengine.utils.Cache;

/**
 * Manages calendar-specific patterns and construction of formatting requests.
 */
class CalendarManager {

  private final Bundle bundle;
  private final Internals internals;
  private final Set availableCalendars;
  private final Cache patternCache;

  public CalendarManager(Bundle bundle, Internals internals) {
    this.bundle = bundle;
    this.internals = internals;
    Schema schema = internals.schema;
    this.availableCalendars = new HashSet<>(internals.config.get("calendars"));
    this.patternCache = new Cache<>(calendar -> {
      if (this.availableCalendars.contains(calendar)) {
        switch (calendar) {
          case "buddhist":
            return new CalendarPatterns(bundle, internals, schema.Buddhist);
          case "japanese":
            return new CalendarPatterns(bundle, internals, schema.Japanese);
          case "persian":
            return new CalendarPatterns(bundle, internals, schema.Persian);
        }
      }
      return new GregorianPatterns(bundle, internals, schema.Gregorian);
    }, 20);
  }

  public CalendarPatterns getCalendarPatterns(String calendar) {
    return this.patternCache.get(calendar);
  }

  public DateFormatRequest getDateFormatRequest(CalendarDate date, DateFormatOptions options, NumberParams params) {
    CalendarType calendar = this.internals.calendars.selectCalendar(this.bundle, options.calendar.get());
    CalendarPatterns patterns = this.getCalendarPatterns(calendar.value);

    FormatWidthType dateKey = options.datetime.or(options.date.get());
    FormatWidthType timeKey = options.datetime.or(options.time.get());
    FormatWidthType wrapKey = options.wrap.get();
    String skelKey = options.skeleton.or("");

    if (dateKey == null && timeKey == null && isEmpty(skelKey)) {
      dateKey = FormatWidthType.LONG;
    }

    boolean atTime = options.atTime.or(true);
    String wrapper = "";
    if (wrapKey != null) {
      wrapper = patterns.getWrapperPattern(wrapKey, atTime);
    } else if (dateKey != null && timeKey != null) {
      wrapper = patterns.getWrapperPattern(dateKey, atTime);
    }

    DateFormatRequest req = new DateFormatRequest();
    req.wrapper = wrapper;
    req.params = params;
    if (dateKey != null) {
      req.date = patterns.getDatePattern(dateKey);
    }
    if (timeKey != null) {
      req.time = patterns.getTimePattern(timeKey);
    }

    DateSkeleton query = null;

    // We have both standard formats, we're done
    if (req.date != null && req.time != null) {
      return req;
    }

    // We have at least a date/time standard format.
    if (req.date != null || req.time != null) {

      // If no skeleton specified, we're done
      if (isEmpty(skelKey)) {
        return req;
      }

      // We have a standard date or time pattern along with a skeleton.
      // We split the skeleton into date/time parts, then use the one
      // that doesn't conflict with the specified standard format
      query = patterns.parseSkeleton(skelKey);

      // Use the part of the skeleton that does not conflict
      DateSkeleton time = query.split();
      if (req.date != null) {
        query = time;
      }

      // Update skeleton key with only the used fields
      skelKey = query.canonical();
    } else {
      // No standard format specified, so just parse the skeleton
      query = patterns.parseSkeleton(skelKey);
    }

    // Perform a best-fit match on the skeleton

    // TODO: skeleton caching disabled for now due to mixed formats
    // Check if we've cached the patterns for this skeleton before
//    CachedSkeletonRequest entry = patterns.getCachedSkeletonRequest(skelKey);
//    if (entry != null) {
//      req.date = entry.date;
//      req.time = entry.time;
//      if (wrapKey == null && entry.dateSkel != null && req.date != null && req.time != null) {
//        // If wrapper not explicitly requested, select based on skeleton date fields
//        req.wrapper = this.selectWrapper(patterns, entry.dateSkel, req.date);
//      }
//      return req;
//    }

    DateSkeleton timeQuery = null;
    DateSkeleton dateSkel = null;
    DateSkeleton timeSkel = null;

    // Check if skeleton specifies date or time fields, or both.
    if (query.compound()) {
      // Separate into a date and a time skeleton.
      timeQuery = query.split();
      dateSkel = patterns.matchAvailable(query);
      timeSkel = patterns.matchAvailable(timeQuery);
    } else if (query.isDate) {
      dateSkel = patterns.matchAvailable(query);
    } else {
      timeQuery = query;
      timeSkel = patterns.matchAvailable(query);
    }

    if (dateSkel != null) {
      req.date = this.getAvailablePattern(patterns, date, query, dateSkel, params);
    }
    if (timeQuery != null && timeSkel != null) {
      req.time = this.getAvailablePattern(patterns, date, timeQuery, timeSkel, params);
    }

    if (wrapKey == null) {
      if (dateSkel != null && req.date != null && req.time != null) {
        req.wrapper = this.selectWrapper(patterns, dateSkel, req.date, atTime);
      } else {
        req.wrapper = patterns.getWrapperPattern(dateKey == null ? FormatWidthType.SHORT : dateKey, atTime);
      }
    }

    // TODO: skeleton caching disabled for now due to mixed formats
//    entry = new CachedSkeletonRequest(dateSkel, req.date, req.time);
//    patterns.setCachedSkeletonRequest(skelKey, entry);
    return req;
  }

  /**
   * Best-fit match an input skeleton. The skeleton can contain both date and
   * time fields.
   *
   * The field of greatest difference between the start and end dates can be
   * either a date or time field.
   *
   * Given this we need to cover the following cases:
   *
   * 1. Input skeleton requests both date and time fields.
   *  a. "yMd" same: split skeleton, format date standalone, followed by time range.
   *  b. "yMd" differ: format full start / end with fallback format.
   *
   * 2. Input skeleton requests date fields only:
   *  a. "yMd" same: format date standalone
   *  b. "yMd" differ: select and format date range
   *
   * 3. Input skeleton requests time fields only:
   *  a. "yMd" same, "ahms" same: format time standalone
   *  b. "yMd" same, "ahms" differ: select and format time range.
   *  c. "yMd" differ: prepend "yMd" to skeleton and go to (1a).
   */
  public DateIntervalFormatRequest getDateIntervalFormatRequest(CalendarType calendar,
      CalendarDate start, DateTimePatternFieldType fieldDiff, DateIntervalFormatOptions options,
      NumberParams params) {

    CalendarPatterns patterns = this.getCalendarPatterns(calendar.value);
    boolean dateDiffers = "yMd".indexOf(fieldDiff.value()) != -1;
    String wrapper = patterns.getIntervalFallback();
    DateIntervalFormatRequest req = new DateIntervalFormatRequest();
    req.params = params;
    req.wrapper = wrapper;

    String origSkeleton = options.skeleton.get();
    if (origSkeleton == null) {
      if (dateDiffers && options.date.ok()) {
        origSkeleton = options.date.get();
      } else {
        origSkeleton = options.time.get();
      }
    }

    // If the skeleton is still undefined, select a reasonable default
    if (origSkeleton == null) {
      origSkeleton = dateDiffers ? "yMMMd" : "hmsa";
    }
    String skeleton = origSkeleton;

    // Cache key consists of the input skeleton and the field of greatest difference between
    // the start and end dates.
    String cacheKey = skeleton + "\t" + fieldDiff;
    CachedIntervalRequest entry = patterns.getCachedIntervalRequest(cacheKey);
    if (entry != null) {
      req.date = entry.date;
      req.range = entry.range;
      req.skeleton = entry.skeleton;
      return req;
    }

    entry = new CachedIntervalRequest(null, null, null);
    DateSkeleton query = patterns.parseSkeleton(skeleton);

    // TODO: Augment skeleton to ensure context. day without month, minute without hour, etc.

    boolean standalone = fieldDiff == DateTimePatternFieldType.SECOND ||
        (query.isDate && !dateDiffers) || (query.isTime && dateDiffers);
    if (!standalone) {
      if (query.has(SkeletonData.Field.DAY.ordinal()) && !query.has(SkeletonData.Field.MONTH.ordinal())) {
        skeleton = "M" + skeleton;
      }
      if (query.has(SkeletonData.Field.MINUTE.ordinal()) && !query.has(SkeletonData.Field.HOUR.ordinal())) {
        skeleton = "j" + skeleton;
      }
    }

    if (!query.isDate && dateDiffers) {
      // 3c. prepend "yMd" and proceed
      if (fieldDiff == DateTimePatternFieldType.YEAR) {
        skeleton = "yMd" + skeleton;
      } else if (fieldDiff == DateTimePatternFieldType.MONTH) {
        skeleton = "Md" + skeleton;
      } else {
        skeleton = "d" + skeleton;
      }
    }

    if (!origSkeleton.equals(skeleton)) {
      query = patterns.parseSkeleton(skeleton);
    }

    DateSkeleton timeQuery = null;

    // If both date and time fields are requested, we have two choices:
    // a. date fields are the same:  ", 




© 2015 - 2025 Weber Informatics LLC | Privacy Policy