InternetCalendarSubscriptionService.java

package com.tdmconsult.ete.ical;

import com.tdmconsult.ete.services.yahoo.YahooFinanceService;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.info.GitProperties;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class InternetCalendarSubscriptionService {

    private static final DateTimeFormatter DATE_TIME =
            DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'")
                    .withZone(ZoneOffset.UTC);
    public static final String CRLF = "\r\n";

    private final YahooFinanceService yahooFinanceService;

    @Autowired
    private Optional<GitProperties> gitProperties;

    @Autowired
    private Optional<BuildProperties> buildProperties;

    public String getEarningsCalendar() {

        final var events = yahooFinanceService.getUpcomingEarningsEvents();
        final var sb = new StringBuilder(8192);

        sb.append("BEGIN:VCALENDAR").append(CRLF);
        sb.append("VERSION:2.0").append(CRLF);
        sb.append("PRODID:-//TDM Consult GmbH//ETE 0.0.0//EN").append(CRLF);
        sb.append("CALSCALE:GREGORIAN").append(CRLF);
        sb.append("METHOD:PUBLISH").append(CRLF);
        sb.append("X-WR-CALNAME:Earnings Calendar").append(CRLF);

        buildProperties.ifPresent(properties -> {
            sb.append("X-APP-VERSION:").append(properties.getVersion()).append(CRLF);
        });
        gitProperties.ifPresent(properties -> {
            sb.append("X-GIT-COMMIT:").append(properties.getShortCommitId()).append(CRLF);
        });

        for (var event : events) {

            final var security = event.security();

            sb.append("BEGIN:VEVENT\r\n");

            sb.append("UID:")
                    .append(event.publicId())
                    .append("@earnings")
                    .append(CRLF);

            sb.append("SEQUENCE:")
                    .append(event.version())
                    .append(CRLF);

            sb.append("LAST-MODIFIED:")
                    .append(event.updatedAt()
                            .atOffset(ZoneOffset.UTC)
                            .format(DATE_TIME))
                    .append(CRLF);

            sb.append("DTSTAMP:")
                    .append(event.createdAt()
                            .atOffset(ZoneOffset.UTC)
                            .format(DATE_TIME))
                    .append(CRLF);

            sb.append("DTSTART;VALUE=DATE:")
                    .append(event.earningsDate().format(DateTimeFormatter.BASIC_ISO_DATE))
                    .append(CRLF);

            sb.append("SUMMARY:")
                    .append(escape(security.name()))
                    .append(" Earnings")
                    .append(CRLF);

            sb.append("DESCRIPTION:")
                    .append("ISIN: ")
                    .append(security.isin())
                    .append("\\n")
                    .append(event.isConfirmed() ? "Confirmed" : "Preliminary")
                    .append(CRLF);

            sb.append("STATUS:")
                    .append(event.isConfirmed() ? "CONFIRMED" : "TENTATIVE")
                    .append(CRLF);

            sb.append("TRANSP:TRANSPARENT\r\n");

            sb.append("END:VEVENT\r\n");
        }

        sb.append("END:VCALENDAR\r\n");
        return sb.toString();
    }

    private static String escape(final String value) {
        if (value == null) {
            return "";
        }

        return value
                .replace("\\", "\\\\")
                .replace(";", "\\;")
                .replace(",", "\\,")
                .replace("\r", "")
                .replace("\n", "\\n");
    }
}