com.squarespace.cldrengine.calendars.CalendarsImpl Maven / Gradle / Ivy
The newest version!
package com.squarespace.cldrengine.calendars;
import static com.squarespace.cldrengine.utils.StringUtils.isEmpty;
import static com.squarespace.cldrengine.utils.TypeUtils.defaulter;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import com.squarespace.cldrengine.api.BuddhistDate;
import com.squarespace.cldrengine.api.Bundle;
import com.squarespace.cldrengine.api.CalendarDate;
import com.squarespace.cldrengine.api.CalendarType;
import com.squarespace.cldrengine.api.Calendars;
import com.squarespace.cldrengine.api.ContextTransformFieldType;
import com.squarespace.cldrengine.api.ContextType;
import com.squarespace.cldrengine.api.DateFieldWidthType;
import com.squarespace.cldrengine.api.DateFormatAltOptions;
import com.squarespace.cldrengine.api.DateFormatOptions;
import com.squarespace.cldrengine.api.DateIntervalFormatOptions;
import com.squarespace.cldrengine.api.DateRawFormatOptions;
import com.squarespace.cldrengine.api.Decimal;
import com.squarespace.cldrengine.api.ExemplarCity;
import com.squarespace.cldrengine.api.FormatWidthType;
import com.squarespace.cldrengine.api.GregorianDate;
import com.squarespace.cldrengine.api.ISO8601Date;
import com.squarespace.cldrengine.api.JapaneseDate;
import com.squarespace.cldrengine.api.MetaZoneType;
import com.squarespace.cldrengine.api.MetazoneName;
import com.squarespace.cldrengine.api.MetazoneNames;
import com.squarespace.cldrengine.api.Option;
import com.squarespace.cldrengine.api.Pair;
import com.squarespace.cldrengine.api.Part;
import com.squarespace.cldrengine.api.PersianDate;
import com.squarespace.cldrengine.api.RelativeTimeFieldFormatOptions;
import com.squarespace.cldrengine.api.RelativeTimeFieldType;
import com.squarespace.cldrengine.api.RelativeTimeFormatOptions;
import com.squarespace.cldrengine.api.TimeData;
import com.squarespace.cldrengine.api.TimePeriodField;
import com.squarespace.cldrengine.api.TimeZoneInfo;
import com.squarespace.cldrengine.api.TimeZoneNameType;
import com.squarespace.cldrengine.internal.AbstractValue;
import com.squarespace.cldrengine.internal.DateTimePatternFieldType;
import com.squarespace.cldrengine.internal.Internals;
import com.squarespace.cldrengine.internal.PartsValue;
import com.squarespace.cldrengine.internal.PrivateApi;
import com.squarespace.cldrengine.internal.StringValue;
import com.squarespace.cldrengine.internal.TimeZoneSchema;
import com.squarespace.cldrengine.numbers.NumberParams;
import com.squarespace.cldrengine.parsing.DateTimePattern;
import com.squarespace.cldrengine.parsing.WrapperPattern;
import com.squarespace.cldrengine.utils.StringUtils;
public class CalendarsImpl implements Calendars {
private static final DateFormatOptions DATE_FORMAT_OPTIONS_DEFAULT =
DateFormatOptions
.build()
.date(FormatWidthType.FULL);
private static final RelativeTimeFieldFormatOptions RELATIVE_FIELD_OPTIONS_DEFAULT =
RelativeTimeFieldFormatOptions.build()
.width(DateFieldWidthType.WIDE);
private static final RelativeTimeFormatOptions RELATIVE_OPTIONS_DEFAULT =
RelativeTimeFormatOptions.build()
.width(DateFieldWidthType.WIDE)
.maximumFractionDigits(0)
.group(true);
private static final DateFormatAltOptions DEFAULT_ALT_OPTIONS = DateFormatAltOptions.build();
private final Bundle bundle;
private final Internals internals;
private final PrivateApi privateApi;
private final CalendarManager manager;
private final TimeZoneSchema tz;
private final int firstDay;
private final int minDays;
public CalendarsImpl(Bundle bundle, Internals internals, PrivateApi privateApi) {
this.bundle = bundle;
this.internals = internals;
this.privateApi = privateApi;
this.manager = new CalendarManager(bundle, internals);
this.tz = internals.schema.TimeZones;
String region = bundle.region();
this.firstDay = internals.calendars.weekFirstDay(region);
this.minDays = internals.calendars.weekMinDays(region);
}
@Override
public BuddhistDate toBuddhistDate(long epoch, String zoneId) {
return BuddhistDate.fromUnixEpoch(epoch, zoneId, firstDay, minDays);
}
@Override
public BuddhistDate toBuddhistDate(Date date, String zoneId) {
return toBuddhistDate(date.getTime(), zoneId);
}
@Override
public BuddhistDate toBuddhistDate(CalendarDate date) {
return toBuddhistDate(date.unixEpoch(), date.timeZoneId());
}
@Override
public GregorianDate toGregorianDate(long epoch, String zoneId) {
return GregorianDate.fromUnixEpoch(epoch, zoneId, firstDay, minDays);
}
@Override
public GregorianDate toGregorianDate(Date date, String zoneId) {
return toGregorianDate(date.getTime(), zoneId);
}
@Override
public GregorianDate toGregorianDate(CalendarDate date) {
return toGregorianDate(date.unixEpoch(), date.timeZoneId());
}
@Override
public ISO8601Date toISO8601Date(long epoch, String zoneId) {
return ISO8601Date.fromUnixEpoch(epoch, zoneId, firstDay, minDays);
}
@Override
public ISO8601Date toISO8601Date(Date date, String zoneId) {
return toISO8601Date(date.getTime(), zoneId);
}
@Override
public ISO8601Date toISO8601Date(CalendarDate date) {
return toISO8601Date(date.unixEpoch(), date.timeZoneId());
}
@Override
public JapaneseDate toJapaneseDate(long epoch, String zoneId) {
return JapaneseDate.fromUnixEpoch(epoch, zoneId, firstDay, minDays);
}
@Override
public JapaneseDate toJapaneseDate(Date date, String zoneId) {
return toJapaneseDate(date.getTime(), zoneId);
}
@Override
public JapaneseDate toJapaneseDate(CalendarDate date) {
return toJapaneseDate(date.unixEpoch(), date.timeZoneId());
}
@Override
public PersianDate toPersianDate(long epoch, String zoneId) {
return PersianDate.fromUnixEpoch(epoch, zoneId, firstDay, minDays);
}
@Override
public PersianDate toPersianDate(Date date, String zoneId) {
return toPersianDate(date.getTime(), zoneId);
}
@Override
public PersianDate toPersianDate(CalendarDate date) {
return toPersianDate(date.unixEpoch(), date.timeZoneId());
}
/**
* Find the field of visual difference between two dates. For example, the dates "2019-03-31" and "2019-04-01" differ
* visually in the month field, even though the dates are only 1 day apart.
*
* This can be used by applications to select an appropriate skeleton for date interval formatting, e.g. to format
* "March 31 - April 01, 2019"
*/
public DateTimePatternFieldType fieldOfVisualDifference(CalendarDate a, CalendarDate b) {
// Determine calendar type to use for comparison. We use the type for
// the left argument.
CalendarType type = a.type();
// Convert a and b to the same type
if (b.type() != type) {
b = convertDateTo(type, b);
}
return a.fieldOfVisualDifference(b);
}
@Override
public String formatDate(CalendarDate date, DateFormatOptions options) {
return this._formatDate(new StringValue(), date, options);
}
@Override
public List formatDateToParts(CalendarDate date, DateFormatOptions options) {
return this._formatDate(new PartsValue(), date, options);
}
@Override
public String formatDateRaw(CalendarDate date, DateRawFormatOptions options) {
return _formatDateRaw(new StringValue(), date, options);
}
@Override
public String formatDateInterval(CalendarDate start, CalendarDate end, DateIntervalFormatOptions options) {
return this._formatInterval(new StringValue(), start, end, options);
}
@Override
public List formatDateIntervalToParts(CalendarDate start, CalendarDate end, DateIntervalFormatOptions options) {
return this._formatInterval(new PartsValue(), start, end, options);
}
@Override
public String formatRelativeTimeField(Decimal value, RelativeTimeFieldType field,
RelativeTimeFieldFormatOptions options) {
options = defaulter(options, RelativeTimeFieldFormatOptions::build)
.mergeIf(RELATIVE_FIELD_OPTIONS_DEFAULT);
Map transform = this.privateApi.getContextTransformInfo();
NumberParams params = this.privateApi.getNumberParams(options.numberSystem.get(), null);
return this.internals.dateFields.formatRelativeTimeField(bundle, value, field, options, params, transform);
}
@Override
public String formatRelativeTime(CalendarDate start, CalendarDate end, RelativeTimeFormatOptions options) {
options = defaulter(options, RelativeTimeFormatOptions::build)
.mergeIf(RELATIVE_OPTIONS_DEFAULT);
Pair res = start.relativeTime(end, options.field.get());
TimePeriodField field = res._1;
double amount = res._2;
if (start.compare(end) == 1) {
amount *= -1;
}
if (field == TimePeriodField.MILLIS) {
amount /= 1000.0;
field = TimePeriodField.SECOND;
}
RelativeTimeFieldType relField = translateRelativeFieldType(field);
// See if we can use day of the week formatting
boolean dayOfWeek = options.dayOfWeek.or(false);
if (dayOfWeek && relField == RelativeTimeFieldType.WEEK && start.dayOfWeek() == end.dayOfWeek()) {
long dow = end.dayOfWeek() - 1;
relField = DOW_FIELDS[(int)dow];
}
return formatRelativeTimeField(new Decimal(amount), relField, options);
}
@Override
public TimeData timeData() {
CalendarPatterns patterns = this.manager.getCalendarPatterns(CalendarType.GREGORY.value);
CalendarPatterns.TimeData raw = patterns.getTimeData();
return new TimeData(raw._2, Arrays.asList(raw._1.split(" ")));
}
@Override
public List timeZoneIds() {
return TimeZoneData.zoneIds();
}
@Override
public String resolveTimeZoneId(String zoneId) {
return TimeZoneData.resolveId(zoneId);
}
@Override
public TimeZoneInfo timeZoneInfo(String zoneId) {
String id = this.resolveTimeZoneId(zoneId);
if (id == null) {
id = "Factory";
}
boolean isStable = TimeZoneData.zoneIsStable(id);
String stableId = isStable ? id : TimeZoneData.getStableId(id);
String city = this.tz.exemplarCity.get(this.bundle, stableId);
if (StringUtils.isEmpty(city)) {
city = this.tz.exemplarCity.get(this.bundle, "Etc/Unknown");
}
String metazoneId = TimeZoneData.getMetazone(id, Long.MAX_VALUE);
if (metazoneId == null) {
metazoneId = "";
}
ZoneMeta zoneMeta = TimeZoneData.zoneMeta(id);
MetaZoneType metazone = MetaZoneType.fromString(metazoneId);
String long_generic = tz.metaZones.long_.get(bundle, TimeZoneNameType.GENERIC, metazone);
String long_standard = tz.metaZones.long_.get(bundle, TimeZoneNameType.STANDARD, metazone);
String long_daylight = tz.metaZones.long_.get(bundle, TimeZoneNameType.DAYLIGHT, metazone);
String short_generic = tz.metaZones.short_.get(bundle, TimeZoneNameType.GENERIC, metazone);
String short_standard = tz.metaZones.short_.get(bundle, TimeZoneNameType.STANDARD, metazone);
String short_daylight = tz.metaZones.short_.get(bundle, TimeZoneNameType.DAYLIGHT, metazone);
return new TimeZoneInfo(
id,
new ExemplarCity(city),
Arrays.asList(zoneMeta.countries),
zoneMeta.latitude,
zoneMeta.longitude,
zoneMeta.stdoffset,
metazoneId,
new MetazoneNames(
new MetazoneName(long_generic, long_standard, long_daylight),
new MetazoneName(short_generic, short_standard, short_daylight))
);
}
protected static final RelativeTimeFieldType[] DOW_FIELDS = new RelativeTimeFieldType[] {
RelativeTimeFieldType.SUN,
RelativeTimeFieldType.MON,
RelativeTimeFieldType.TUE,
RelativeTimeFieldType.WED,
RelativeTimeFieldType.THU,
RelativeTimeFieldType.FRI,
RelativeTimeFieldType.SAT,
};
protected RelativeTimeFieldType translateRelativeFieldType(TimePeriodField field) {
switch (field) {
case YEAR:
return RelativeTimeFieldType.YEAR;
case MONTH:
return RelativeTimeFieldType.MONTH;
case WEEK:
return RelativeTimeFieldType.WEEK;
case DAY:
return RelativeTimeFieldType.DAY;
case HOUR:
return RelativeTimeFieldType.HOUR;
case MINUTE:
return RelativeTimeFieldType.MINUTE;
case SECOND:
default:
return RelativeTimeFieldType.SECOND;
}
}
protected R _formatDate(AbstractValue value, CalendarDate date, DateFormatOptions options) {
options = defaulter(options, () -> DATE_FORMAT_OPTIONS_DEFAULT);
CalendarType calendar = this.internals.calendars.selectCalendar(bundle, options.calendar.get());
date = convertDateTo(calendar, date);
NumberParams params = this.privateApi.getNumberParams(options.numberSystem.get(), "default");
DateFormatRequest req = this.manager.getDateFormatRequest(date, options, params);
CalendarContext ctx = this._context(date, params, options.context.get(), options.alt);
return this.internals.calendars.formatDateTime(calendar, ctx, value, true, req.date, req.time, req.wrapper);
}
protected R _formatInterval(AbstractValue value, CalendarDate start, CalendarDate end,
DateIntervalFormatOptions options) {
options = defaulter(options, DateIntervalFormatOptions::build);
CalendarType calendar = this.internals.calendars.selectCalendar(bundle, options.calendar.get());
start = convertDateTo(calendar, start);
end = convertDateTo(calendar, end);
boolean atTime = options.atTime.or(true);
DateTimePatternFieldType fieldDiff = this.fieldOfVisualDifference(start, end);
NumberParams params = this.privateApi.getNumberParams(options.numberSystem.get(), "default");
DateIntervalFormatRequest req =
this.manager.getDateIntervalFormatRequest(calendar, start, fieldDiff, options, params);
if (!isEmpty(req.skeleton)) {
DateFormatOptions opts = DateFormatOptions.build()
.calendar(options.calendar)
.numberSystem(options.numberSystem)
.skeleton(req.skeleton);
DateFormatRequest r = this.manager.getDateFormatRequest(start, opts, params);
CalendarContext ctx = this._context(start, params, options.context.get(), options.alt);
R _start = this.internals.calendars.formatDateTime(calendar, ctx, value, true, r.date, r.time, r.wrapper);
ctx.date = end;
R _end = this.internals.calendars.formatDateTime(calendar, ctx, value, false, r.date, r.time, r.wrapper);
WrapperPattern wrapper = this.internals.general.parseWrapper(req.wrapper);
value.wrap(wrapper, Arrays.asList(_start, _end));
return value.render();
}
R _date = null;
if (req.date != null) {
CalendarContext ctx = this._context(start, params, options.context.get(), options.alt);
_date = this.internals.calendars.formatDateTime(calendar, ctx, value, true, req.date, null, null);
}
if (req.range != null) {
CalendarContext ctx = this._context(start, params, options.context.get(), options.alt);
R _range = this.internals.calendars.formatInterval(calendar, ctx, value, _date == null, end, req.range);
if (_date == null) {
return _range;
}
// Note: This case is covered in ICU but not mentioned in the CLDR docs. Use the MEDIUM
// dateTimeFormat to join a common date with a time range.
// Ticket referencing the discrepancy:
// https://www.unicode.org/cldr/trac/ticket/11158
// Docs don't mention this edge case:
// https://www.unicode.org/reports/tr35/tr35-dates.html#intervalFormats
CalendarPatterns patterns = this.manager.getCalendarPatterns(calendar.value);
WrapperPattern wrapper = this.internals.general.parseWrapper(patterns.getWrapperPattern(FormatWidthType.MEDIUM, atTime));
value.wrap(wrapper, Arrays.asList(_range, _date));
return value.render();
}
return _date == null ? value.empty() : _date;
}
private R _formatDateRaw(AbstractValue value, CalendarDate date, DateRawFormatOptions options) {
String raw = options == null ? "" : options.pattern.or("");
if (isEmpty(raw)) {
return value.empty();
}
DateTimePattern pattern = this.internals.calendars.parseDatePattern(raw);
CalendarType calendar = this.internals.calendars.selectCalendar(bundle, options.calendar.get());
date = convertDateTo(calendar, date);
NumberParams params = this.privateApi.getNumberParams(options.numberSystem.get(), "default");
CalendarContext ctx = this._context(date, params, options.context.get(), options.alt);
return this.internals.calendars.formatDateTime(calendar, ctx, value, true, pattern, null, null);
}
protected CalendarContext _context(T date, NumberParams params, ContextType context,
Option alt) {
Map transform = this.privateApi.getContextTransformInfo();
return new CalendarContext(date, this.bundle, params.system,
params.latnSystem, context, transform, alt.or(DEFAULT_ALT_OPTIONS));
}
protected CalendarDate convertDateTo(CalendarType type, CalendarDate date) {
if (type != null && date.type() != type) {
switch (type) {
case BUDDHIST:
return this.toBuddhistDate(date);
case ISO8601:
return this.toISO8601Date(date);
case JAPANESE:
return this.toJapaneseDate(date);
case PERSIAN:
return this.toPersianDate(date);
default:
break;
}
}
return this.toGregorianDate(date);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy