diff --git a/README.md b/README.md index 7006b1b..bcdb041 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ First, add this as a dependency to your POM: io.gdcc sitemapgen4j - 2.1.0 + 2.2.0 ``` @@ -59,12 +59,18 @@ wsg.write(); To configure the URLs, construct a WebSitemapUrl with WebSitemapUrl.Options. ```java +import java.time.OffsetDateTime; + WebSitemapGenerator wsg = new WebSitemapGenerator("https://www.example.com", myDir); WebSitemapUrl url = new WebSitemapUrl.Options("https://www.example.com/index.html") - .lastMod(new Date()).priority(1.0).changeFreq(ChangeFreq.HOURLY).build(); + .lastMod(OffsetDateTime.now()).priority(1.0).changeFreq(ChangeFreq.HOURLY).build(); // this will configure the URL with lastmod=now, priority=1.0, changefreq=hourly -wsg.addUrl(url); -wsg.write(); +wsg. + +addUrl(url); +wsg. + +write(); ``` ## Configuring the date format @@ -74,8 +80,8 @@ One important configuration option for the sitemap generator is the date format. ```java // Use DAY pattern (2009-02-07), Greenwich Mean Time timezone -W3CDateFormat dateFormat = new W3CDateFormat(Pattern.DAY); -dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); +ZoneId zoneId = TimeZone.getTimeZone("GMT").toZoneId(); +W3CDateFormat dateFormat = W3CDateFormat.DAY.withZone(zoneId); WebSitemapGenerator wsg = WebSitemapGenerator.builder("https://www.example.com", myDir) .dateFormat(dateFormat).build(); // actually use the configured dateFormat wsg.addUrl("https://www.example.com/index.html"); diff --git a/TODO.txt b/TODO.txt index a97fab7..71111a2 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,4 +1,3 @@ -Migrate to java.time Migrate to java.nio Ping search engines diff --git a/pom.xml b/pom.xml index 5c4e977..1f013e1 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.gdcc sitemapgen4j jar - 2.1.3-SNAPSHOT + 2.2.0-SNAPSHOT SitemapGen4J https://github.com/gdcc/sitemapgen4j/ @@ -25,7 +25,7 @@ scm:git:git://github.com:gdcc/sitemapgen4j.git scm:git:git@github.com:gdcc/sitemapgen4j.git https://github.com/gdcc/sitemapgen4j/ - sitemapgen4j-2.1.2 + sitemapgen4j-2.2.0 https://github.com/gdcc/sitemapgen4j/issues diff --git a/src/main/java/com/redfin/sitemapgenerator/AbstractSitemapGeneratorOptions.java b/src/main/java/com/redfin/sitemapgenerator/AbstractSitemapGeneratorOptions.java index c5d915f..bac710c 100644 --- a/src/main/java/com/redfin/sitemapgenerator/AbstractSitemapGeneratorOptions.java +++ b/src/main/java/com/redfin/sitemapgenerator/AbstractSitemapGeneratorOptions.java @@ -2,6 +2,7 @@ import java.io.File; import java.net.URL; +import java.time.format.DateTimeFormatter; // that weird thing with generics is so sub-classed objects will return themselves // It makes sense, I swear! https://madbean.com/2004/mb2004-3/ @@ -55,7 +56,7 @@ public T allowMultipleSitemaps(boolean allowMultipleSitemaps) { this.allowMultipleSitemaps = allowMultipleSitemaps; return getThis(); } - /** The date formatter, typically configured with a {@link W3CDateFormat.Pattern} and/or a time zone */ + /** The date formatter, typically configured with a {@link W3CDateFormat} and/or a time zone */ public T dateFormat(W3CDateFormat dateFormat) { this.dateFormat = dateFormat; return getThis(); diff --git a/src/main/java/com/redfin/sitemapgenerator/AbstractSitemapUrlOptions.java b/src/main/java/com/redfin/sitemapgenerator/AbstractSitemapUrlOptions.java index 44f35ae..284b2de 100644 --- a/src/main/java/com/redfin/sitemapgenerator/AbstractSitemapUrlOptions.java +++ b/src/main/java/com/redfin/sitemapgenerator/AbstractSitemapUrlOptions.java @@ -3,13 +3,13 @@ import java.net.MalformedURLException; import java.net.URL; import java.text.ParseException; -import java.util.Date; +import java.time.OffsetDateTime; /** Container for optional URL parameters */ //that weird thing with generics is so sub-classed objects will return themselves //It makes sense, I swear! https://madbean.com/2004/mb2004-3/ abstract class AbstractSitemapUrlOptions> { - Date lastMod; + OffsetDateTime lastMod; ChangeFreq changeFreq; Double priority; URL url; @@ -31,7 +31,7 @@ abstract class AbstractSitemapUrlOptions { - private final Date publicationDate; + private final OffsetDateTime publicationDate; private String keywords; private String genres; private final String title; private final GoogleNewsPublication publication; /** Specifies a URL and publication date (which is mandatory for Google News) */ - public Options(String url, Date publicationDate, String title, GoogleNewsPublication publication) throws MalformedURLException { + public Options(String url, OffsetDateTime publicationDate, String title, GoogleNewsPublication publication) throws MalformedURLException { this(new URL(url), publicationDate, title, publication); } - public Options(String url, Date publicationDate, String title, String name, String language) throws MalformedURLException { + public Options(String url, OffsetDateTime publicationDate, String title, String name, String language) throws MalformedURLException { this(new URL(url), publicationDate, title, new GoogleNewsPublication(name, language)); } - public Options(URL url, Date publicationDate, String title, String name, String language) { + public Options(URL url, OffsetDateTime publicationDate, String title, String name, String language) { this(url, publicationDate, title, new GoogleNewsPublication(name, language)); } /** Specifies a URL and publication date (which is mandatory for Google News) */ - public Options(URL url, Date publicationDate, String title, GoogleNewsPublication publication) { + public Options(URL url, OffsetDateTime publicationDate, String title, GoogleNewsPublication publication) { super(url, GoogleNewsSitemapUrl.class); if (publicationDate == null) throw new NullPointerException("publicationDate must not be null"); this.publicationDate = publicationDate; @@ -101,22 +101,22 @@ public Options genres(String... genres) { } /** Specifies a URL and publication date, title and publication (which are mandatory for Google News) */ - public GoogleNewsSitemapUrl(URL url, Date publicationDate, String title, String name, String language) { + public GoogleNewsSitemapUrl(URL url, OffsetDateTime publicationDate, String title, String name, String language) { this(new Options(url, publicationDate, title, name, language)); } /** Specifies a URL and publication date, title and publication (which are mandatory for Google News) */ - public GoogleNewsSitemapUrl(URL url, Date publicationDate, String title, GoogleNewsPublication publication) { + public GoogleNewsSitemapUrl(URL url, OffsetDateTime publicationDate, String title, GoogleNewsPublication publication) { this(new Options(url, publicationDate, title, publication)); } /** Specifies a URL and publication date, title and publication (which are mandatory for Google News) */ - public GoogleNewsSitemapUrl(String url, Date publicationDate, String title, String name, String language) throws MalformedURLException { + public GoogleNewsSitemapUrl(String url, OffsetDateTime publicationDate, String title, String name, String language) throws MalformedURLException { this(new Options(url, publicationDate, title, name, language)); } /** Specifies a URL and publication date, title and publication (which are mandatory for Google News) */ - public GoogleNewsSitemapUrl(String url, Date publicationDate, String title, GoogleNewsPublication publication) throws MalformedURLException { + public GoogleNewsSitemapUrl(String url, OffsetDateTime publicationDate, String title, GoogleNewsPublication publication) throws MalformedURLException { this(new Options(url, publicationDate, title, publication)); } @@ -131,7 +131,7 @@ public GoogleNewsSitemapUrl(Options options) { } /** Retrieves the publication date */ - public Date getPublicationDate() { + public OffsetDateTime getPublicationDate() { return publicationDate; } diff --git a/src/main/java/com/redfin/sitemapgenerator/GoogleVideoSitemapUrl.java b/src/main/java/com/redfin/sitemapgenerator/GoogleVideoSitemapUrl.java index 6180b8c..3ce9f9c 100644 --- a/src/main/java/com/redfin/sitemapgenerator/GoogleVideoSitemapUrl.java +++ b/src/main/java/com/redfin/sitemapgenerator/GoogleVideoSitemapUrl.java @@ -1,9 +1,9 @@ package com.redfin.sitemapgenerator; import java.net.URL; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.List; /** One configurable Google Video Search URL. To configure, use {@link Options} @@ -21,7 +21,7 @@ public class GoogleVideoSitemapUrl extends WebSitemapUrl { private final String description; private final Double rating; private final Integer viewCount; - private final Date publicationDate; + private final OffsetDateTime publicationDate; private final List tags; private final String category; // TODO can there be multiple categories? @@ -40,7 +40,7 @@ public static class Options extends AbstractSitemapUrlOptions tags; private String category; // TODO can there be multiple categories? @@ -137,7 +137,7 @@ public Options viewCount(int viewCount) { } /** The date the video was first published, in {@link W3CDateFormat}. */ - public Options publicationDate(Date publicationDate) { + public Options publicationDate(OffsetDateTime publicationDate) { this.publicationDate = publicationDate; return this; } @@ -308,7 +308,7 @@ public Integer getViewCount() { } /** Retrieves the {@link Options#publicationDate}*/ - public Date getPublicationDate() { + public OffsetDateTime getPublicationDate() { return publicationDate; } diff --git a/src/main/java/com/redfin/sitemapgenerator/ISitemapUrl.java b/src/main/java/com/redfin/sitemapgenerator/ISitemapUrl.java index 27323d2..02b47d5 100644 --- a/src/main/java/com/redfin/sitemapgenerator/ISitemapUrl.java +++ b/src/main/java/com/redfin/sitemapgenerator/ISitemapUrl.java @@ -1,11 +1,11 @@ package com.redfin.sitemapgenerator; import java.net.URL; -import java.util.Date; +import java.time.OffsetDateTime; public interface ISitemapUrl { - Date getLastMod(); + OffsetDateTime getLastMod(); URL getUrl(); diff --git a/src/main/java/com/redfin/sitemapgenerator/SitemapGenerator.java b/src/main/java/com/redfin/sitemapgenerator/SitemapGenerator.java index 6939e28..eba70d5 100644 --- a/src/main/java/com/redfin/sitemapgenerator/SitemapGenerator.java +++ b/src/main/java/com/redfin/sitemapgenerator/SitemapGenerator.java @@ -34,9 +34,9 @@ public SitemapGenerator(AbstractSitemapGeneratorOptions options, ISitemapUrlR baseDir = options.baseDir; baseUrl = options.baseUrl; fileNamePrefix = options.fileNamePrefix; - W3CDateFormat dateFormat = options.dateFormat; - if (dateFormat == null) dateFormat = new W3CDateFormat(); - this.dateFormat = dateFormat; + W3CDateFormat dateFormatter = options.dateFormat; + if (dateFormatter == null) dateFormatter = W3CDateFormat.AUTO; + this.dateFormat = dateFormatter; allowEmptySitemap = options.allowEmptySitemap; allowMultipleSitemaps = options.allowMultipleSitemaps; maxUrls = options.maxUrls; diff --git a/src/main/java/com/redfin/sitemapgenerator/SitemapIndexGenerator.java b/src/main/java/com/redfin/sitemapgenerator/SitemapIndexGenerator.java index 997e9b7..0f92e5e 100644 --- a/src/main/java/com/redfin/sitemapgenerator/SitemapIndexGenerator.java +++ b/src/main/java/com/redfin/sitemapgenerator/SitemapIndexGenerator.java @@ -8,8 +8,10 @@ import java.io.OutputStreamWriter; import java.net.MalformedURLException; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.OffsetDateTime; import java.util.ArrayList; -import java.util.Date; import java.util.List; /** @@ -24,7 +26,7 @@ public class SitemapIndexGenerator { private final List urls = new ArrayList<>(); private final int maxUrls; private final W3CDateFormat dateFormat; - private final Date defaultLastMod; + private final OffsetDateTime defaultLastMod; private final boolean autoValidate; /** Options to configure sitemap index generation */ @@ -34,7 +36,7 @@ public static class Options { private W3CDateFormat dateFormat = null; private boolean allowEmptyIndex = false; private int maxUrls = SitemapConstants.MAX_SITEMAPS_PER_INDEX; - private Date defaultLastMod = new Date(); + private OffsetDateTime defaultLastMod = OffsetDateTime.now(); private boolean autoValidate = false; // TODO GZIP? Is that legal for a sitemap index? @@ -55,7 +57,7 @@ public Options(URL baseUrl, File outFile) { public Options(String baseUrl, File outFile) throws MalformedURLException { this(new URL(baseUrl), outFile); } - /** The date formatter, typically configured with a {@link W3CDateFormat.Pattern} and/or a time zone */ + /** The date formatter, typically configured with a {@link W3CDateFormat} and/or a time zone */ public Options dateFormat(W3CDateFormat dateFormat) { this.dateFormat = dateFormat; return this; @@ -89,7 +91,7 @@ Options maxUrls(int maxUrls) { * now, but you can pass in null to omit a lastMod entirely. We don't * recommend this; Google may not like you as much. */ - public Options defaultLastMod(Date defaultLastMod) { + public Options defaultLastMod(OffsetDateTime defaultLastMod) { this.defaultLastMod = defaultLastMod; return this; } @@ -132,9 +134,9 @@ private SitemapIndexGenerator(Options options) { this.outFile = options.outFile; this.allowEmptyIndex = options.allowEmptyIndex; this.maxUrls = options.maxUrls; - W3CDateFormat dateFormat = options.dateFormat; - if (dateFormat == null) dateFormat = new W3CDateFormat(); - this.dateFormat = dateFormat; + W3CDateFormat dateFormatter = options.dateFormat; + if (dateFormatter == null) dateFormatter = W3CDateFormat.AUTO; + this.dateFormat = dateFormatter; this.defaultLastMod = options.defaultLastMod; this.autoValidate = options.autoValidate; } @@ -184,12 +186,12 @@ public SitemapIndexGenerator addUrl(URL url) { } /** Adds a single sitemap to the index */ - public SitemapIndexGenerator addUrl(URL url, Date lastMod) { + public SitemapIndexGenerator addUrl(URL url, OffsetDateTime lastMod) { return addUrl(new SitemapIndexUrl(url, lastMod)); } /** Adds a single sitemap to the index */ - public SitemapIndexGenerator addUrl(String url, Date lastMod) throws MalformedURLException { + public SitemapIndexGenerator addUrl(String url, OffsetDateTime lastMod) throws MalformedURLException { return addUrl(new SitemapIndexUrl(url, lastMod)); } @@ -258,7 +260,7 @@ private void writeAsString(StringBuilder sb) { sb.append(" "); sb.append(UrlUtils.escapeXml(url.url.toString())); sb.append("\n"); - Date lastMod = url.lastMod; + OffsetDateTime lastMod = url.lastMod; if (lastMod == null) lastMod = defaultLastMod; diff --git a/src/main/java/com/redfin/sitemapgenerator/SitemapIndexUrl.java b/src/main/java/com/redfin/sitemapgenerator/SitemapIndexUrl.java index d19a953..d71ff5b 100644 --- a/src/main/java/com/redfin/sitemapgenerator/SitemapIndexUrl.java +++ b/src/main/java/com/redfin/sitemapgenerator/SitemapIndexUrl.java @@ -2,7 +2,7 @@ import java.net.MalformedURLException; import java.net.URL; -import java.util.Date; +import java.time.OffsetDateTime; /** * Represents a single sitemap for inclusion in a sitemap index. @@ -11,24 +11,24 @@ */ public class SitemapIndexUrl { final URL url; - final Date lastMod; + final OffsetDateTime lastMod; /** Configures the sitemap URL with a specified lastMod */ - public SitemapIndexUrl(URL url, Date lastMod) { + public SitemapIndexUrl(URL url, OffsetDateTime lastMod) { this.url = url; this.lastMod = lastMod; } /** Configures the sitemap URL with a specified lastMod */ - public SitemapIndexUrl(String url, Date lastMod) throws MalformedURLException { + public SitemapIndexUrl(String url, OffsetDateTime lastMod) throws MalformedURLException { this(new URL(url), lastMod); } - /** Configures the sitemap URL with no specified lastMod; we'll use {@link SitemapIndexGenerator.Options#defaultLastMod(Date)} or leave it blank if no default is specified */ + /** Configures the sitemap URL with no specified lastMod; we'll use {@link SitemapIndexGenerator.Options#defaultLastMod(OffsetDateTime)} or leave it blank if no default is specified */ public SitemapIndexUrl(URL url) { this(url, null); } - /** Configures the sitemap URL with no specified lastMod; we'll use {@link SitemapIndexGenerator.Options#defaultLastMod(Date)} or leave it blank if no default is specified */ + /** Configures the sitemap URL with no specified lastMod; we'll use {@link SitemapIndexGenerator.Options#defaultLastMod(OffsetDateTime)} or leave it blank if no default is specified */ public SitemapIndexUrl(String url) throws MalformedURLException { this(new URL(url)); } diff --git a/src/main/java/com/redfin/sitemapgenerator/W3CDateFormat.java b/src/main/java/com/redfin/sitemapgenerator/W3CDateFormat.java index ce8a40c..a7ff277 100644 --- a/src/main/java/com/redfin/sitemapgenerator/W3CDateFormat.java +++ b/src/main/java/com/redfin/sitemapgenerator/W3CDateFormat.java @@ -1,29 +1,24 @@ /** - * + * */ package com.redfin.sitemapgenerator; -import java.text.DateFormat; -import java.text.FieldPosition; -import java.text.ParsePosition; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.GregorianCalendar; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.ChronoField; +import java.util.Locale; import java.util.Objects; -import java.util.TimeZone; - -import static java.util.Calendar.HOUR_OF_DAY; -import static java.util.Calendar.MILLISECOND; -import static java.util.Calendar.MINUTE; -import static java.util.Calendar.SECOND; /** *

Formats and parses dates in the six defined W3C date time formats. These formats are described in * "Date and Time Formats", * https://www.w3.org/TR/NOTE-datetime.

- * - *

The formats are: - * + * + *

The formats are:

+ * *
    *
  1. YEAR: YYYY (eg 1997) *
  2. MONTH: YYYY-MM (eg 1997-07) @@ -32,157 +27,190 @@ *
  3. SECOND: YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00) *
  4. MILLISECOND: YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00) *
- * - * Note that W3C timezone designators (TZD) are either the letter "Z" (for GMT) or a pattern like "+00:30" or "-08:00". This is unlike + * + *

Note that W3C timezone designators (TZD) are either the letter "Z" (for GMT) or a pattern like "+00:30" or "-08:00". This is unlike * RFC 822 timezones generated by SimpleDateFormat, which omit the ":" like this: "+0030" or "-0800".

- * + * *

This class allows you to either specify which format pattern to use, or (by default) to * automatically guess which pattern to use (AUTO mode). When parsing in AUTO mode, we'll try parsing using each pattern * until we find one that works. When formatting in AUTO mode, we'll use this algorithm: - * + * *

  1. If the date has fractional milliseconds (e.g. 2009-06-06T19:49:04.45Z) we'll use the MILLISECOND pattern *
  2. Otherwise, if the date has non-zero seconds (e.g. 2009-06-06T19:49:04Z) we'll use the SECOND pattern *
  3. Otherwise, if the date is not at exactly midnight (e.g. 2009-06-06T19:49Z) we'll use the MINUTE pattern *
  4. Otherwise, we'll use the DAY pattern. If you want to format using the MONTH or YEAR pattern, you must declare it explicitly. *
- * - * Finally note that, like all classes that inherit from DateFormat, this class is not thread-safe. Also note that you - * can explicitly specify the timezone to use for formatting using the {@link #setTimeZone(TimeZone)} method. - * + * * @author Dan Fabulich * @see Date and Time Formats */ -public class W3CDateFormat extends SimpleDateFormat { - private static final long serialVersionUID = -5733368073260485802L; - - /** The six patterns defined by W3C, plus {@link #AUTO} configuration */ - public enum Pattern { - /** "yyyy-MM-dd'T'HH:mm:ss.SSSZ" */ - MILLISECOND("yyyy-MM-dd'T'HH:mm:ss.SSSZ", true), - /** "yyyy-MM-dd'T'HH:mm:ssZ" */ - SECOND("yyyy-MM-dd'T'HH:mm:ssZ", true), - /** "yyyy-MM-dd'T'HH:mmZ" */ - MINUTE("yyyy-MM-dd'T'HH:mmZ", true), - /** "yyyy-MM-dd" */ - DAY("yyyy-MM-dd", false), - /** "yyyy-MM" */ - MONTH("yyyy-MM", false), - /** "yyyy" */ - YEAR("yyyy", false), - /** Automatically compute the right pattern to use */ - AUTO("", true); - - private final String pattern; - private final boolean includeTimeZone; - - Pattern(String pattern, boolean includeTimeZone) { - this.pattern = pattern; - this.includeTimeZone = includeTimeZone; - } - } - - private final Pattern pattern; - /** The GMT ("zulu") time zone, for your convenience */ - public static final TimeZone ZULU = TimeZone.getTimeZone("GMT"); - - /** Build a formatter in AUTO mode */ - public W3CDateFormat() { - this(Pattern.AUTO); - } - - /** Build a formatter using the specified Pattern, or AUTO mode */ - public W3CDateFormat(Pattern pattern) { - super(pattern.pattern); - this.pattern = pattern; - } - - /** This is what you override when you extend DateFormat; use {@link DateFormat#format(Date)} instead */ - @Override - public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) { - boolean includeTimeZone = pattern.includeTimeZone; - if (pattern == Pattern.AUTO) { - includeTimeZone = autoFormat(date); - } - super.format(date, toAppendTo, pos); - if (includeTimeZone) convertRfc822TimeZoneToW3c(toAppendTo); - return toAppendTo; - } - - private boolean applyPattern(Pattern pattern) { - applyPattern(pattern.pattern); - return pattern.includeTimeZone; - } - - private boolean autoFormat(Date date) { - if (calendar == null) calendar = new GregorianCalendar(); - calendar.setTime(date); - boolean hasMillis = calendar.get(MILLISECOND) > 0; - if (hasMillis) { - return applyPattern(Pattern.MILLISECOND); - } - boolean hasSeconds = calendar.get(SECOND) > 0; - if (hasSeconds) { - return applyPattern(Pattern.SECOND); - } - boolean hasTime = (calendar.get(HOUR_OF_DAY) + calendar.get(MINUTE)) > 0; - if (hasTime) { - return applyPattern(Pattern.MINUTE); - } - return applyPattern(Pattern.DAY); - } - - /** This is what you override when you extend DateFormat; use {@link DateFormat#parse(String)} instead */ - @Override - public Date parse(String text, ParsePosition pos) { - text = convertW3cTimeZoneToRfc822(text); - if (pattern == Pattern.AUTO) { - return autoParse(text, pos); - } - return super.parse(text, pos); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof W3CDateFormat)) return false; - if (!super.equals(o)) return false; - W3CDateFormat that = (W3CDateFormat) o; - return pattern == that.pattern; - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), pattern); - } - - private Date autoParse(String text, ParsePosition pos) { - for (Pattern pattern : Pattern.values()) { - if (pattern == Pattern.AUTO) continue; - applyPattern(pattern); - Date out = super.parse(text, pos); - if (out != null) return out; - } - return null; // this will force a ParseException - } - - private void convertRfc822TimeZoneToW3c(StringBuffer toAppendTo) { - int length = toAppendTo.length(); - if (ZULU.equals(calendar.getTimeZone())) { - toAppendTo.replace(length - 5, length, "Z"); - } else { - toAppendTo.insert(length - 2, ':'); - } - } - - private String convertW3cTimeZoneToRfc822(String source) { - int length = source.length(); - if (source.endsWith("Z")) { - return source.substring(0, length-1) + "+0000"; - } - if (source.charAt(length-3) == ':') { - return source.substring(0, length-3) + source.substring(length - 2); - } - return source; - } - +public class W3CDateFormat { + public static final DateTimeFormatter FORMAT_RFC_822 = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz").withZone(ZoneOffset.UTC); + + private static final DateTimeFormatter FORMAT_AUTO = new DateTimeFormatterBuilder() + .appendPattern("yyyy[-MM[-dd]]['T'HH[:mm[:ss[.SSS]]]][XXX]") + .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1) + .parseDefaulting(ChronoField.DAY_OF_MONTH, 1) + .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) + .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) + .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) + .parseDefaulting(ChronoField.NANO_OF_SECOND, 0) + .parseDefaulting(ChronoField.OFFSET_SECONDS, 0) + .toFormatter(Locale.ENGLISH); + public static final W3CDateFormat AUTO = new W3CDateFormat(FORMAT_AUTO, true, ZoneOffset.UTC,true); + + private static final DateTimeFormatter FORMAT_MILLISECONDS = new DateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd'T'HH:mm:ss.SSS[XXX]") + .parseDefaulting(ChronoField.NANO_OF_SECOND, 0) + .parseDefaulting(ChronoField.OFFSET_SECONDS, 0) + .toFormatter(Locale.ENGLISH); + public static final W3CDateFormat MILLISECOND = new W3CDateFormat(FORMAT_MILLISECONDS, true, ZoneOffset.UTC); + + private static final DateTimeFormatter FORMAT_SECONDS = new DateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd'T'HH:mm:ss[XXX]") + .parseDefaulting(ChronoField.NANO_OF_SECOND, 0) + .parseDefaulting(ChronoField.OFFSET_SECONDS, 0) + .toFormatter(Locale.ENGLISH); + public static final W3CDateFormat SECOND = new W3CDateFormat(FORMAT_SECONDS, true, ZoneOffset.UTC); + + private static final DateTimeFormatter FORMAT_MINUTES = new DateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd'T'HH:mm[XXX]") + .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) + .parseDefaulting(ChronoField.NANO_OF_SECOND, 0) + .parseDefaulting(ChronoField.OFFSET_SECONDS, 0) + .toFormatter(Locale.ENGLISH); + public static final W3CDateFormat MINUTE = new W3CDateFormat(FORMAT_MINUTES, true, ZoneOffset.UTC); + + private static final DateTimeFormatter FORMAT_DAYS = new DateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd") + .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) + .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) + .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) + .parseDefaulting(ChronoField.NANO_OF_SECOND, 0) + .parseDefaulting(ChronoField.OFFSET_SECONDS, 0) + .toFormatter(Locale.ENGLISH); + public static final W3CDateFormat DAY = new W3CDateFormat(FORMAT_DAYS, false, ZoneOffset.UTC); + + private static final DateTimeFormatter FORMAT_MONTHS = new DateTimeFormatterBuilder() + .appendPattern("yyyy-MM") + .parseDefaulting(ChronoField.DAY_OF_MONTH, 1) + .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) + .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) + .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) + .parseDefaulting(ChronoField.NANO_OF_SECOND, 0) + .parseDefaulting(ChronoField.OFFSET_SECONDS, 0) + .toFormatter(Locale.ENGLISH); + public static final W3CDateFormat MONTH = new W3CDateFormat(FORMAT_MONTHS, false, ZoneOffset.UTC); + + private static final DateTimeFormatter FORMAT_YEAR = new DateTimeFormatterBuilder() + .appendPattern("yyyy") + .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1) + .parseDefaulting(ChronoField.DAY_OF_MONTH, 1) + .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) + .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) + .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) + .parseDefaulting(ChronoField.NANO_OF_SECOND, 0) + .parseDefaulting(ChronoField.OFFSET_SECONDS, 0) + .toFormatter(Locale.ENGLISH); + public static final W3CDateFormat YEAR = new W3CDateFormat(FORMAT_YEAR, false, ZoneOffset.UTC); + + private final DateTimeFormatter dateTimeFormatter; + private final boolean includeTimeZone; + private final ZoneId timeZone; + private final boolean isAuto; + + + public W3CDateFormat() { + this.dateTimeFormatter = FORMAT_AUTO; + this.isAuto = true; + this.includeTimeZone = true; + this.timeZone = ZoneOffset.UTC; + } + + public W3CDateFormat(DateTimeFormatter formatter, boolean includeTimeZone, ZoneId zoneId) { + this.dateTimeFormatter = formatter; + this.includeTimeZone = includeTimeZone; + this.timeZone = zoneId; + this.isAuto = false; + } + + private W3CDateFormat(DateTimeFormatter formatter, boolean includeTimeZone, ZoneId zoneId,boolean isAuto){ + this.dateTimeFormatter = formatter; + this.includeTimeZone = includeTimeZone; + this.timeZone = zoneId; + this.isAuto = isAuto; + } + + /** + * Parses a string representation of a date into an OffsetDateTime object using the configured DateTimeFormatter. + * + * @param date The date string to parse. + * @return The parsed OffsetDateTime object. + */ + public OffsetDateTime parse(final String date) { + return OffsetDateTime.parse(date, dateTimeFormatter); + } + + /** + * Creates a new W3CDateFormat with the specified ZoneId. + * + * @param zone the desired ZoneId + * @return a new instance of W3CDateFormat with the specified ZoneId + */ + public W3CDateFormat withZone(final ZoneId zone) { + return new W3CDateFormat(dateTimeFormatter, includeTimeZone, zone,isAuto); + } + + /** + * Formats the given OffsetDateTime object according to the configured settings. + * + * @param date The OffsetDateTime object to be formatted. + * @return The formatted date as a String. + */ + public String format(OffsetDateTime date) { + if (!ZoneOffset.UTC.equals(timeZone)) { + date = date.atZoneSameInstant(timeZone).toOffsetDateTime(); + } + + DateTimeFormatter formatter = dateTimeFormatter; + if (isAuto) { + formatter = autoFormat(date); + } + if (includeTimeZone) { + return formatter.withZone(timeZone).format(date); + } else { + return formatter.format(date); + } + + } + + + private DateTimeFormatter autoFormat(OffsetDateTime date) { + if (date.get(ChronoField.MILLI_OF_SECOND) > 0) { + return FORMAT_MILLISECONDS; + } else if (date.getSecond() > 0) { + return FORMAT_SECONDS; + } else if ((date.getHour() + date.getMinute()) > 0) { + return FORMAT_MINUTES; + } else { + return FORMAT_DAYS; + } + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof W3CDateFormat)) return false; + if (!super.equals(o)) return false; + W3CDateFormat that = (W3CDateFormat) o; + return dateTimeFormatter == that.dateTimeFormatter; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), dateTimeFormatter); + } + + } \ No newline at end of file diff --git a/src/main/java/com/redfin/sitemapgenerator/WebSitemapUrl.java b/src/main/java/com/redfin/sitemapgenerator/WebSitemapUrl.java index 0bcc504..7a099a4 100644 --- a/src/main/java/com/redfin/sitemapgenerator/WebSitemapUrl.java +++ b/src/main/java/com/redfin/sitemapgenerator/WebSitemapUrl.java @@ -2,7 +2,7 @@ import java.net.MalformedURLException; import java.net.URL; -import java.util.Date; +import java.time.OffsetDateTime; /** * Encapsulates a single URL to be inserted into a Web sitemap (as opposed to a Geo sitemap, a Mobile sitemap, a Video sitemap, etc which are Google specific). @@ -14,7 +14,7 @@ */ public class WebSitemapUrl implements ISitemapUrl { private final URL url; - private final Date lastMod; + private final OffsetDateTime lastMod; private final ChangeFreq changeFreq; private final Double priority; @@ -43,8 +43,10 @@ public WebSitemapUrl(Options options) { this.priority = options.priority; } - /** Retrieves the {@link Options#lastMod(Date)} */ - public Date getLastMod() { return lastMod; } + /** + * Retrieves the {@link Options#lastMod(OffsetDateTime)} + */ + public OffsetDateTime getLastMod() { return lastMod; } /** Retrieves the {@link Options#changeFreq(ChangeFreq)} */ public ChangeFreq getChangeFreq() { return changeFreq; } /** Retrieves the {@link Options#priority(Double)} */ diff --git a/src/test/java/com/redfin/sitemapgenerator/GoogleImageSitemapUrlTest.java b/src/test/java/com/redfin/sitemapgenerator/GoogleImageSitemapUrlTest.java index 55ba63c..362d624 100644 --- a/src/test/java/com/redfin/sitemapgenerator/GoogleImageSitemapUrlTest.java +++ b/src/test/java/com/redfin/sitemapgenerator/GoogleImageSitemapUrlTest.java @@ -10,9 +10,7 @@ import java.util.ArrayList; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; class GoogleImageSitemapUrlTest { diff --git a/src/test/java/com/redfin/sitemapgenerator/GoogleLinkSitemapUrlTest.java b/src/test/java/com/redfin/sitemapgenerator/GoogleLinkSitemapUrlTest.java index 62d12ac..bd85d3a 100644 --- a/src/test/java/com/redfin/sitemapgenerator/GoogleLinkSitemapUrlTest.java +++ b/src/test/java/com/redfin/sitemapgenerator/GoogleLinkSitemapUrlTest.java @@ -1,7 +1,6 @@ package com.redfin.sitemapgenerator; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/redfin/sitemapgenerator/GoogleNewsSitemapUrlTest.java b/src/test/java/com/redfin/sitemapgenerator/GoogleNewsSitemapUrlTest.java index 22faee8..50c30a3 100644 --- a/src/test/java/com/redfin/sitemapgenerator/GoogleNewsSitemapUrlTest.java +++ b/src/test/java/com/redfin/sitemapgenerator/GoogleNewsSitemapUrlTest.java @@ -1,12 +1,14 @@ package com.redfin.sitemapgenerator; -import com.redfin.sitemapgenerator.W3CDateFormat.Pattern; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.File; -import java.util.Date; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -37,11 +39,9 @@ public void tearDown() { @Test void testSimpleUrl() throws Exception { - W3CDateFormat dateFormat = new W3CDateFormat(Pattern.SECOND); - dateFormat.setTimeZone(W3CDateFormat.ZULU); wsg = GoogleNewsSitemapGenerator.builder("https://www.example.com", dir) - .dateFormat(dateFormat).build(); - GoogleNewsSitemapUrl url = new GoogleNewsSitemapUrl("https://www.example.com/index.html", new Date(0), "Example Title", "The Example Times", "en"); + .dateFormat(W3CDateFormat.SECOND.withZone(ZoneOffset.UTC)).build(); + GoogleNewsSitemapUrl url = new GoogleNewsSitemapUrl("https://www.example.com/index.html", TestUtil.getEpochOffsetDateTime(), "Example Title", "The Example Times", "en"); wsg.addUrl(url); String expected = "\n" + String.format("\n", @@ -64,11 +64,9 @@ void testSimpleUrl() throws Exception { @Test void testKeywords() throws Exception { - W3CDateFormat dateFormat = new W3CDateFormat(Pattern.SECOND); - dateFormat.setTimeZone(W3CDateFormat.ZULU); wsg = GoogleNewsSitemapGenerator.builder("https://www.example.com", dir) - .dateFormat(dateFormat).build(); - GoogleNewsSitemapUrl url = new GoogleNewsSitemapUrl.Options("https://www.example.com/index.html", new Date(0), "Example Title", "The Example Times", "en") + .dateFormat(W3CDateFormat.SECOND.withZone(ZoneOffset.UTC)).build(); + GoogleNewsSitemapUrl url = new GoogleNewsSitemapUrl.Options("https://www.example.com/index.html", TestUtil.getEpochOffsetDateTime(), "Example Title", "The Example Times", "en") .keywords("Klaatu", "Barrata", "Nicto") .build(); wsg.addUrl(url); @@ -94,11 +92,9 @@ void testKeywords() throws Exception { @Test void testGenres() throws Exception { - W3CDateFormat dateFormat = new W3CDateFormat(Pattern.SECOND); - dateFormat.setTimeZone(W3CDateFormat.ZULU); wsg = GoogleNewsSitemapGenerator.builder("https://www.example.com", dir) - .dateFormat(dateFormat).build(); - GoogleNewsSitemapUrl url = new GoogleNewsSitemapUrl.Options("https://www.example.com/index.html", new Date(0), "Example Title", "The Example Times", "en") + .dateFormat(W3CDateFormat.SECOND.withZone(ZoneOffset.UTC)).build(); + GoogleNewsSitemapUrl url = new GoogleNewsSitemapUrl.Options("https://www.example.com/index.html", TestUtil.getEpochOffsetDateTime(), "Example Title", "The Example Times", "en") .genres("persbericht") .build(); wsg.addUrl(url); diff --git a/src/test/java/com/redfin/sitemapgenerator/GoogleVideoSitemapUrlTest.java b/src/test/java/com/redfin/sitemapgenerator/GoogleVideoSitemapUrlTest.java index 498e491..e9a6b69 100644 --- a/src/test/java/com/redfin/sitemapgenerator/GoogleVideoSitemapUrlTest.java +++ b/src/test/java/com/redfin/sitemapgenerator/GoogleVideoSitemapUrlTest.java @@ -8,7 +8,9 @@ import java.io.File; import java.net.MalformedURLException; import java.net.URL; -import java.util.Date; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -68,15 +70,13 @@ void testSimpleUrl() throws Exception { @Test void testOptions() throws Exception { - W3CDateFormat dateFormat = new W3CDateFormat(); - dateFormat.setTimeZone(W3CDateFormat.ZULU); wsg = GoogleVideoSitemapGenerator.builder("https://www.example.com", dir) - .dateFormat(dateFormat).build(); + .dateFormat(W3CDateFormat.AUTO.withZone(ZoneOffset.UTC)).build(); GoogleVideoSitemapUrl url = new Options(LANDING_URL, CONTENT_URL) .playerUrl(new URL("https://www.example.com/index.swf"), true) .thumbnailUrl(new URL("https://www.example.com/thumbnail.jpg")) .title("This is a video!").description("A great video about dinosaurs") - .rating(5.0).viewCount(500000).publicationDate(new Date(0)).tags("dinosaurs", "example", "awesome") + .rating(5.0).viewCount(500000).publicationDate(TestUtil.getEpochOffsetDateTime()).tags("dinosaurs", "example", "awesome") .category("example").familyFriendly(false).durationInSeconds(60*30) .build(); wsg.addUrl(url); diff --git a/src/test/java/com/redfin/sitemapgenerator/SitemapGeneratorTest.java b/src/test/java/com/redfin/sitemapgenerator/SitemapGeneratorTest.java index 9106cbb..3142fb7 100644 --- a/src/test/java/com/redfin/sitemapgenerator/SitemapGeneratorTest.java +++ b/src/test/java/com/redfin/sitemapgenerator/SitemapGeneratorTest.java @@ -9,14 +9,13 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; -import java.util.Date; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.List; import java.util.zip.GZIPInputStream; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; class SitemapGeneratorTest { @@ -151,11 +150,9 @@ void testTwoUrl() throws Exception { @Test void testAllUrlOptions() throws Exception { - W3CDateFormat df = new W3CDateFormat(); - df.setTimeZone(W3CDateFormat.ZULU); - wsg = WebSitemapGenerator.builder("https://www.example.com", dir).dateFormat(df).autoValidate(true).build(); + wsg = WebSitemapGenerator.builder("https://www.example.com", dir).dateFormat(W3CDateFormat.AUTO.withZone(ZoneOffset.UTC)).autoValidate(true).build(); WebSitemapUrl url = new WebSitemapUrl.Options("https://www.example.com/index.html") - .changeFreq(ChangeFreq.DAILY).lastMod(new Date(0)).priority(1.0).build(); + .changeFreq(ChangeFreq.DAILY).lastMod(TestUtil.getEpochOffsetDateTime()).priority(1.0).build(); wsg.addUrl(url); String expected = "\n" + String.format("\n", SitemapConstants.SITEMAP_NS_URI) + @@ -168,7 +165,7 @@ void testAllUrlOptions() throws Exception { ""; String sitemap = writeSingleSiteMap(wsg); assertEquals(expected, sitemap); - + TestUtil.isValidSitemap(sitemap); } diff --git a/src/test/java/com/redfin/sitemapgenerator/SitemapIndexGeneratorTest.java b/src/test/java/com/redfin/sitemapgenerator/SitemapIndexGeneratorTest.java index f819d54..78749dd 100644 --- a/src/test/java/com/redfin/sitemapgenerator/SitemapIndexGeneratorTest.java +++ b/src/test/java/com/redfin/sitemapgenerator/SitemapIndexGeneratorTest.java @@ -6,13 +6,15 @@ import java.io.File; import java.net.MalformedURLException; -import java.util.Date; +import java.time.ZoneOffset; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; public class SitemapIndexGeneratorTest { + private static final W3CDateFormat ZULU = new W3CDateFormat().withZone(ZoneOffset.UTC); + private static final String INDEX = "\n" + String.format("\n", SitemapConstants.SITEMAP_NS_URI) + " \n" + @@ -58,13 +60,11 @@ public class SitemapIndexGeneratorTest { ""; private static final String EXAMPLE = "https://www.example.com/"; - private static final W3CDateFormat ZULU = new W3CDateFormat(); File outFile; SitemapIndexGenerator sig; @BeforeEach public void setUp() throws Exception { - ZULU.setTimeZone(W3CDateFormat.ZULU); outFile = File.createTempFile(SitemapGeneratorTest.class.getSimpleName(), ".xml"); outFile.deleteOnExit(); } @@ -106,7 +106,7 @@ void testNoUrlsEmptyIndexAllowed() throws Exception { @Test void testMaxUrls() throws Exception { sig = new SitemapIndexGenerator.Options(EXAMPLE, outFile).autoValidate(true) - .maxUrls(10).defaultLastMod(new Date(0)).dateFormat(ZULU).build(); + .maxUrls(10).defaultLastMod(TestUtil.getEpochOffsetDateTime()).dateFormat(ZULU).build(); for (int i = 1; i <= 9; i++) { sig.addUrl(EXAMPLE+"sitemap"+i+".xml"); } @@ -120,7 +120,7 @@ void testMaxUrls() throws Exception { @Test void testOneUrl() throws Exception { sig = new SitemapIndexGenerator.Options(EXAMPLE, outFile).dateFormat(ZULU).autoValidate(true).build(); - SitemapIndexUrl url = new SitemapIndexUrl(EXAMPLE+"index.html", new Date(0)); + SitemapIndexUrl url = new SitemapIndexUrl(EXAMPLE+"index.html", TestUtil.getEpochOffsetDateTime()); sig.addUrl(url); sig.write(); String actual = TestUtil.slurpFileAndDelete(outFile); @@ -138,7 +138,7 @@ void testOneUrl() throws Exception { @Test void testAddByPrefix() throws MalformedURLException { sig = new SitemapIndexGenerator.Options(EXAMPLE, outFile).autoValidate(true) - .defaultLastMod(new Date(0)).dateFormat(ZULU).build(); + .defaultLastMod(TestUtil.getEpochOffsetDateTime()).dateFormat(ZULU).build(); sig.addUrls("sitemap", ".xml", 10); sig.write(); String actual = TestUtil.slurpFileAndDelete(outFile); diff --git a/src/test/java/com/redfin/sitemapgenerator/TestUtil.java b/src/test/java/com/redfin/sitemapgenerator/TestUtil.java index 2761fca..6239b48 100644 --- a/src/test/java/com/redfin/sitemapgenerator/TestUtil.java +++ b/src/test/java/com/redfin/sitemapgenerator/TestUtil.java @@ -11,6 +11,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -59,4 +62,8 @@ public static void isValidSitemap(String xml) { ValidationResult vr = validator.validateInstance(Input.fromString(xml).build()); assertTrue(vr.isValid(), vr.getProblems().toString()); } + + public static OffsetDateTime getEpochOffsetDateTime(){ + return OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC); + } } diff --git a/src/test/java/com/redfin/sitemapgenerator/TutorialExampleTest.java b/src/test/java/com/redfin/sitemapgenerator/TutorialExampleTest.java index a53ffa0..4287d12 100644 --- a/src/test/java/com/redfin/sitemapgenerator/TutorialExampleTest.java +++ b/src/test/java/com/redfin/sitemapgenerator/TutorialExampleTest.java @@ -1,12 +1,11 @@ package com.redfin.sitemapgenerator; -import com.redfin.sitemapgenerator.W3CDateFormat.Pattern; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.File; -import java.util.Date; +import java.time.OffsetDateTime; import java.util.TimeZone; public class TutorialExampleTest { @@ -52,7 +51,7 @@ void testConfiguringWsgOptions() throws Exception { void testConfiguringUrlOptions() throws Exception { WebSitemapGenerator wsg = new WebSitemapGenerator("https://www.example.com", myDir); WebSitemapUrl url = new WebSitemapUrl.Options("https://www.example.com/index.html") - .lastMod(new Date()).priority(1.0).changeFreq(ChangeFreq.HOURLY).build(); + .lastMod(OffsetDateTime.now()).priority(1.0).changeFreq(ChangeFreq.HOURLY).build(); // this will configure the URL with lastmod=now, priority=1.0, changefreq=hourly wsg.addUrl(url); wsg.write(); @@ -60,10 +59,8 @@ void testConfiguringUrlOptions() throws Exception { @Test void testConfiguringDateFormat() throws Exception { - W3CDateFormat dateFormat = new W3CDateFormat(Pattern.DAY); // e.g. 2008-01-29 - dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); // Use Greenwich Mean Time timezone WebSitemapGenerator wsg = WebSitemapGenerator.builder("https://www.example.com", myDir) - .dateFormat(dateFormat).build(); // actually use the configured dateFormat + .dateFormat(W3CDateFormat.DAY.withZone(TimeZone.getTimeZone("GMT").toZoneId())).build(); // actually use the configured dateFormat wsg.addUrl("https://www.example.com/index.html"); wsg.write(); } diff --git a/src/test/java/com/redfin/sitemapgenerator/W3CDateFormatTest.java b/src/test/java/com/redfin/sitemapgenerator/W3CDateFormatTest.java index b86b9d4..dfc3121 100644 --- a/src/test/java/com/redfin/sitemapgenerator/W3CDateFormatTest.java +++ b/src/test/java/com/redfin/sitemapgenerator/W3CDateFormatTest.java @@ -1,25 +1,21 @@ package com.redfin.sitemapgenerator; -import com.redfin.sitemapgenerator.W3CDateFormat.Pattern; import org.junit.jupiter.api.Test; import java.text.ParseException; -import java.util.Date; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; import java.util.TimeZone; -import static com.redfin.sitemapgenerator.W3CDateFormat.Pattern.AUTO; -import static com.redfin.sitemapgenerator.W3CDateFormat.Pattern.DAY; -import static com.redfin.sitemapgenerator.W3CDateFormat.Pattern.MILLISECOND; -import static com.redfin.sitemapgenerator.W3CDateFormat.Pattern.MINUTE; -import static com.redfin.sitemapgenerator.W3CDateFormat.Pattern.MONTH; -import static com.redfin.sitemapgenerator.W3CDateFormat.Pattern.SECOND; -import static com.redfin.sitemapgenerator.W3CDateFormat.Pattern.YEAR; +import static com.redfin.sitemapgenerator.W3CDateFormat.*; import static org.junit.jupiter.api.Assertions.assertEquals; public class W3CDateFormatTest { @Test void testFormatEpoch() { - Date epoch = new Date(0); + OffsetDateTime epoch = TestUtil.getEpochOffsetDateTime(); verifyPatternFormat(epoch, MILLISECOND, "1970-01-01T00:00:00.000Z"); verifyPatternFormat(epoch, SECOND, "1970-01-01T00:00:00Z"); verifyPatternFormat(epoch, MINUTE, "1970-01-01T00:00Z"); @@ -31,29 +27,29 @@ void testFormatEpoch() { @Test void testAutoFormat() { - Date date = new Date(0); + OffsetDateTime date = TestUtil.getEpochOffsetDateTime(); verifyPatternFormat(date, AUTO, "1970-01-01"); - date = new Date(1); + date = OffsetDateTime.ofInstant(Instant.ofEpochMilli(1), ZoneOffset.UTC); verifyPatternFormat(date, AUTO, "1970-01-01T00:00:00.001Z"); - date = new Date(1000); + date = OffsetDateTime.ofInstant(Instant.ofEpochMilli(1000), ZoneOffset.UTC); verifyPatternFormat(date, AUTO, "1970-01-01T00:00:01Z"); - date = new Date(60000); + date = OffsetDateTime.ofInstant(Instant.ofEpochMilli(60000), ZoneOffset.UTC); verifyPatternFormat(date, AUTO, "1970-01-01T00:01Z"); - date = new Date(60000 * 60 * 24); + date = OffsetDateTime.ofInstant(Instant.ofEpochMilli(60000 * 60 * 24), ZoneOffset.UTC); verifyPatternFormat(date, AUTO, "1970-01-02"); } @Test void testFormatTimeZone() { - Date epoch = new Date(0); - TimeZone tz = TimeZone.getTimeZone("PST"); - verifyPatternFormat(epoch, MILLISECOND, "1969-12-31T16:00:00.000-08:00", tz); - verifyPatternFormat(epoch, AUTO, "1969-12-31T16:00-08:00", tz); + OffsetDateTime epoch = TestUtil.getEpochOffsetDateTime(); + ZoneId tz = TimeZone.getTimeZone("PST").toZoneId(); + verifyPatternFormat(epoch, MILLISECOND.withZone(tz), "1969-12-31T16:00:00.000-08:00", tz); + verifyPatternFormat(epoch, AUTO.withZone(tz), "1969-12-31T16:00-08:00", tz); } @Test void testParseEpoch() throws ParseException { - Date date = new Date(0); + OffsetDateTime date = TestUtil.getEpochOffsetDateTime(); verifyPatternParse("1970-01-01T00:00:00.000Z", MILLISECOND, date); verifyPatternParse("1970-01-01T00:00:00Z", SECOND, date); verifyPatternParse("1970-01-01T00:00Z", MINUTE, date); @@ -65,7 +61,7 @@ void testParseEpoch() throws ParseException { @Test void testAutoParse() throws ParseException { - Date date = new Date(0); + OffsetDateTime date = TestUtil.getEpochOffsetDateTime(); verifyPatternParse("1970-01-01T00:00:00.000Z", AUTO, date); verifyPatternParse("1970-01-01T00:00:00Z", AUTO, date); verifyPatternParse("1970-01-01T00:00Z", AUTO, date); @@ -76,29 +72,26 @@ void testAutoParse() throws ParseException { @Test void testParseTimeZone() throws ParseException { - Date epoch = new Date(0); + OffsetDateTime epoch = TestUtil.getEpochOffsetDateTime(); verifyPatternParse("1969-12-31T16:00:00.000-08:00", MILLISECOND, epoch); verifyPatternParse("1969-12-31T16:00:00.000-08:00", AUTO, epoch); } - private void verifyPatternFormat(Date date, Pattern pattern, String expected) { - verifyPatternFormat(date, pattern, expected, W3CDateFormat.ZULU); + private void verifyPatternFormat(OffsetDateTime date, W3CDateFormat pattern, String expected) { + verifyPatternFormat(date, pattern, expected, ZoneOffset.UTC); } - private void verifyPatternFormat(Date date, Pattern pattern, String expected, TimeZone tz) { - W3CDateFormat format = new W3CDateFormat(pattern); - format.setTimeZone(tz); - assertEquals(expected, format.format(date), date.toString() + " " + pattern); + private void verifyPatternFormat(OffsetDateTime date, W3CDateFormat pattern, String expected, ZoneId tz) { + + assertEquals(expected, pattern.format(date), date.toString() + " " + pattern); } - private void verifyPatternParse(String source, Pattern pattern, Date expected) throws ParseException { - verifyPatternParse(source, pattern, expected, W3CDateFormat.ZULU); + private void verifyPatternParse(String source, W3CDateFormat pattern, OffsetDateTime expected) throws ParseException { + verifyPatternParse(source, pattern, expected, ZoneOffset.UTC); } - private void verifyPatternParse(String source, Pattern pattern, Date expected, TimeZone tz) throws ParseException { - W3CDateFormat format = new W3CDateFormat(pattern); - format.setTimeZone(tz); - Date actual = format.parse(source); + private void verifyPatternParse(String source, W3CDateFormat pattern, OffsetDateTime expected, ZoneId tz) throws ParseException { + OffsetDateTime actual = pattern.parse(source).atZoneSameInstant(tz).toOffsetDateTime(); assertEquals(expected, actual, source + " " + pattern); }