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