/**
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.basics.date;
import static com.opengamma.strata.collect.Guavate.toImmutableList;
import static com.opengamma.strata.collect.Guavate.toImmutableSet;
import static java.time.temporal.ChronoField.DAY_OF_MONTH;
import static java.time.temporal.ChronoField.DAY_OF_WEEK;
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.MonthDay;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.collect.io.IniFile;
import com.opengamma.strata.collect.io.PropertySet;
import com.opengamma.strata.collect.io.ResourceConfig;
import com.opengamma.strata.collect.io.ResourceLocator;
import com.opengamma.strata.collect.named.NamedLookup;
/**
* Loads holiday calendar implementations from CSV.
* <p>
* These will form the standard holiday calendars available in {@link ReferenceData#standard()}.
*/
final class HolidayCalendarIniLookup
implements NamedLookup<HolidayCalendar> {
/**
* The logger.
*/
private static final Logger log = Logger.getLogger(HolidayCalendarIniLookup.class.getName());
/**
* The singleton instance of the lookup.
*/
public static final HolidayCalendarIniLookup INSTANCE = new HolidayCalendarIniLookup();
/**
* The Weekend key name.
*/
private static final String WEEKEND_KEY = "Weekend";
/**
* The lenient day-of-week parser.
*/
private static final DateTimeFormatter DOW_PARSER = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.parseLenient()
.appendText(DAY_OF_WEEK)
.toFormatter(Locale.ENGLISH);
/**
* The lenient month-day parser.
*/
private static final DateTimeFormatter DAY_MONTH_PARSER = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.parseLenient()
.appendText(MONTH_OF_YEAR)
.appendOptional(new DateTimeFormatterBuilder().appendLiteral('-').toFormatter(Locale.ENGLISH))
.appendValue(DAY_OF_MONTH)
.toFormatter(Locale.ENGLISH);
/**
* The cache by name.
*/
private static final ImmutableMap<String, HolidayCalendar> BY_NAME = loadFromIni("HolidayCalendarData.ini");
/**
* Restricted constructor.
*/
private HolidayCalendarIniLookup() {
}
//-------------------------------------------------------------------------
@Override
public Map<String, HolidayCalendar> lookupAll() {
return BY_NAME;
}
// accessible for testing
static ImmutableMap<String, HolidayCalendar> loadFromIni(String filename) {
List<ResourceLocator> resources = ResourceConfig.orderedResources(filename);
Map<String, HolidayCalendar> map = new HashMap<>();
for (ResourceLocator resource : resources) {
try {
IniFile ini = IniFile.of(resource.getCharSource());
for (String sectionName : ini.sections()) {
PropertySet section = ini.section(sectionName);
HolidayCalendar parsed = parseHolidayCalendar(sectionName, section);
map.put(parsed.getName(), parsed);
map.putIfAbsent(parsed.getName().toUpperCase(Locale.ENGLISH), parsed);
}
} catch (RuntimeException ex) {
log.log(Level.SEVERE, "Error processing resource as Holiday Calendar INI file: " + resource, ex);
return ImmutableMap.of();
}
}
return ImmutableMap.copyOf(map);
}
private static HolidayCalendar parseHolidayCalendar(String calendarName, PropertySet section) {
String weekendStr = section.value(WEEKEND_KEY);
Set<DayOfWeek> weekends = parseWeekends(weekendStr);
List<LocalDate> holidays = new ArrayList<>();
for (String key : section.keys()) {
if (key.equals(WEEKEND_KEY)) {
continue;
}
String value = section.value(key);
if (key.length() == 4) {
int year = Integer.parseInt(key);
holidays.addAll(parseYearDates(year, value));
} else {
holidays.add(LocalDate.parse(key));
}
}
// build result
return ImmutableHolidayCalendar.of(HolidayCalendarId.of(calendarName), holidays, weekends);
}
// parse weekend format, such as 'Sat,Sun'
private static Set<DayOfWeek> parseWeekends(String str) {
List<String> split = Splitter.on(',').splitToList(str);
return split.stream()
.map(v -> DOW_PARSER.parse(v, DayOfWeek::from))
.collect(toImmutableSet());
}
// parse year format, such as 'Jan1,Mar12,Dec25' or '2015-01-01,2015-03-12,2015-12-25'
private static List<LocalDate> parseYearDates(int year, String str) {
List<String> split = Splitter.on(',').splitToList(str);
return split.stream()
.map(v -> parseDate(year, v))
.collect(toImmutableList());
}
private static LocalDate parseDate(int year, String str) {
try {
return MonthDay.parse(str, DAY_MONTH_PARSER).atYear(year);
} catch (DateTimeParseException ex) {
LocalDate date = LocalDate.parse(str);
if (date.getYear() != year) {
throw new IllegalArgumentException("Parsed date had incorrect year: " + str + ", but expected: " + year);
}
return date;
}
}
}