diff --git a/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendar.java b/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendar.java index e16b581..bc1c2ec 100644 --- a/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendar.java +++ b/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendar.java @@ -17,8 +17,10 @@ package cn.org.codecrafters.webcal; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * {@code WebCalendar} class represents a web calendar in iCalendar format. @@ -189,18 +191,22 @@ public final class WebCalendar { * @return the resolved iCalendar string */ public String resolve() { - var events = new StringBuilder(); + var eventBuilder = new StringBuilder(); if (!nodes.isEmpty()) { - nodes.forEach(item -> - events.append(item.setDomainName(domainName) - .resolve())); + for (var node : nodes) { + if (Objects.isNull(node.getDomainName()) || node.getDomainName().isBlank()) { + node.setDomainName(this.domainName); + } + + eventBuilder.append(node.resolve()); + } } return "BEGIN:" + TAG + "\n" + "PRODID:-//" + companyName + "//" + productName + "//EN\n" + "VERSION:" + version + "\n" + "X-WR-CALNAME:" + name + "\n" + - events + "\n" + + eventBuilder + "\n" + "END:" + TAG; } diff --git a/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarEvent.java b/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarEvent.java index c073d19..2870246 100644 --- a/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarEvent.java +++ b/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarEvent.java @@ -18,10 +18,12 @@ package cn.org.codecrafters.webcal; import cn.org.codecrafters.webcal.config.Classification; +import cn.org.codecrafters.webcal.config.Formatter; +import java.text.MessageFormat; import java.time.Duration; import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; +import java.time.ZoneId; import java.util.Arrays; import java.util.Collection; import java.util.Optional; @@ -257,30 +259,34 @@ public final class WebCalendarEvent extends WebCalendarNode { */ @Override public String resolve() { - return "\nBEGIN:" + TAG + "\n" + - "UID:" + Optional.ofNullable(uid).orElse(UUID.randomUUID().toString()) + "@" + domainName + "\n" + - Optional.ofNullable(summary).map((item) -> "SUMMARY:" + item + "\n").orElse("") + - "DTSTART" + Optional.ofNullable(timezone).map(item -> ";TZID=" + item).orElse("") + ":" + start.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + "\n" + + var now = LocalDateTime.now().atZone(ZoneId.systemDefault()); + return MessageFormat.format(""" + BEGIN:{0} + UID:{1} + DTSTAMP:{2} + DTSTART:{3} + DURATION:PT{6}S + {4}{5}{7}{8}{9}{10}{11}{12} + END:{0}""", + TAG, // 0 - tag + Optional.ofNullable(uid).orElse(UUID.randomUUID().toString()) + "@" + domainName, // 1 - uid + now.format(Formatter.getUtcDatetimeFormatter()), // 2 - dtstamp + start.atZone(ZoneId.systemDefault()).format(Formatter.getUtcDatetimeFormatter()), // 3 - start time + Optional.ofNullable(summary).map((item) -> "\nSUMMARY:" + item).orElse(""), // 4 - summary Optional.ofNullable(categories) - .map((item) -> { - if (!item.isEmpty()) { - return "CATEGORIES:" + resolveCategories() + "\n"; - } - return null; - }).orElse("") + + .map((item) -> !item.isEmpty() ? "\nCATEGORIES:" + resolveCategories() : null).orElse(""), // 5 - categories Optional.ofNullable(duration) - .map((item) -> "DURATION:PT" + item.getSeconds() + "S\n").orElse("") + - Optional.ofNullable(end) - .map((item) -> "DTEND" + Optional.ofNullable(timezone).map(tz -> ";TZID=" + tz).orElse("") + ":" + - end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + "\n").orElse("") + - Optional.ofNullable(classification) - .map((item) -> "CLASS:" + item.name() + "\n").orElse("") + - Optional.ofNullable(comment).map((item) -> "COMMENT:" + item + "\n").orElse("") + - Optional.ofNullable(description).map((item) -> "DESCRIPTION:" + item + "\n").orElse("") + - Optional.ofNullable(location).map((item) -> "LOCATION:" + item + "\n").orElse("") + - Optional.ofNullable(percentComplete).map((item) -> "PERCENT-COMPLETE:" + item + "\n").orElse("") + - Optional.ofNullable(priority).map((item) -> "PRIORITY:" + item + "\n").orElse("") + - "END:" + TAG + "\n"; + .map((_duration) -> String.valueOf(_duration.getSeconds())) + .orElse(Optional.ofNullable(end) + .map((_end) -> String.valueOf(Duration.between(_end, start).getSeconds())) + .orElse("0")), // 6 - duration + Optional.ofNullable(classification).map((_classification) -> "\nCLASS:" + _classification + "\n").orElse(""), /* 7 - classification */ + Optional.ofNullable(comment).map((_comment) -> "\nCOMMENT:" + _comment + "\n").orElse(""), /* 8 - comment */ + Optional.ofNullable(location).map((_location) -> "\nLOCATION:" + _location).orElse("") /* 9 - location */, + Optional.ofNullable(percentComplete).map((_percentComplete) -> "\nPERCENT-COMPLETE:" + _percentComplete).orElse("") /* 10 = percentComplete */, + Optional.ofNullable(description).map((_description) -> "\nDESCRIPTION:" + _description).orElse("") /* 11 - description */, + Optional.ofNullable(priority).map((_priority) -> "\nPRIORITY:" + _priority).orElse("") /* 12 - priority */ + ); } } diff --git a/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarNode.java b/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarNode.java index 5f56b11..3fd5efb 100644 --- a/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarNode.java +++ b/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarNode.java @@ -90,6 +90,10 @@ public abstract sealed class WebCalendarNode return this; } + public String getDomainName() { + return this.domainName; + } + /** * Resolve the list of categories into a comma-separated string. * diff --git a/webcal/src/main/java/cn/org/codecrafters/webcal/config/Formatter.java b/webcal/src/main/java/cn/org/codecrafters/webcal/config/Formatter.java new file mode 100644 index 0000000..4760d79 --- /dev/null +++ b/webcal/src/main/java/cn/org/codecrafters/webcal/config/Formatter.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 CodeCraftersCN. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.org.codecrafters.webcal.config; + +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +/** + * DatetimeFormatters + * + * @author Zihlu Wang + * @since 21 Sept, 2023 + */ +public final class Formatter { + + public static DateTimeFormatter getUtcDatetimeFormatter() { + if (Objects.isNull(utcDateTimeFormatter)) { + utcDateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'").withZone(ZoneOffset.UTC); + } + + return utcDateTimeFormatter; + } + + // public static DateTimeFormatter getLocalDatetimeFormatter() { + // if (Objects.isNull(localDatetimeFormatter)) { + // localDatetimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss"); + // } + // + // return localDatetimeFormatter; + // } + + private static DateTimeFormatter utcDateTimeFormatter; + + // private static DateTimeFormatter localDatetimeFormatter; + +}