diff --git a/CREDITS.txt b/CREDITS.txt index c42ab7d..cb99950 100644 --- a/CREDITS.txt +++ b/CREDITS.txt @@ -7,6 +7,8 @@ org.xbib.time is based upon the following software: - prettytime https://github.com/ocpsoft/prettytime (Apache 2.0) +- cron expression https://github.com/anderswisch/cron-expression/ (MIT License) + with improvements by Jörg Prante including - converted to Java 8 java.time API @@ -16,3 +18,5 @@ with improvements by Jörg Prante including - refactoring classes - rewritten code to simplify and ease development + +- added nextExecution() method to cron expression and scheduling via Callable diff --git a/build.gradle b/build.gradle index 2b28c4a..3a96d10 100644 --- a/build.gradle +++ b/build.gradle @@ -1,76 +1,134 @@ plugins { - id "org.sonarqube" version "2.2" - id 'org.ajoberstar.github-pages' version '1.6.0-rc.1' - id "org.xbib.gradle.plugin.jbake" version "1.2.1" + id "com.github.spotbugs" version "2.0.0" + id "io.codearte.nexus-staging" version "0.11.0" } -printf "Host: %s\nOS: %s %s %s\nJVM: %s %s %s %s\nGroovy: %s\nGradle: %s\n" + - "Build: group: ${project.group} name: ${project.name} version: ${project.version}\n", - InetAddress.getLocalHost(), - System.getProperty("os.name"), - System.getProperty("os.arch"), - System.getProperty("os.version"), - System.getProperty("java.version"), - System.getProperty("java.vm.version"), - System.getProperty("java.vm.vendor"), - System.getProperty("java.vm.name"), - GroovySystem.getVersion(), - gradle.gradleVersion - apply plugin: 'java' -apply plugin: 'maven' -apply plugin: 'signing' -apply plugin: 'findbugs' apply plugin: 'pmd' apply plugin: 'checkstyle' -apply plugin: "jacoco" -apply plugin: 'org.ajoberstar.github-pages' - -configurations { - wagon - provided - testCompile.extendsFrom(provided) -} +apply plugin: "com.github.spotbugs" dependencies { - testCompile 'junit:junit:4.12' - testCompile 'org.apache.logging.log4j:log4j-core:2.7' - testCompile 'org.apache.logging.log4j:log4j-jul:2.7' - wagon 'org.apache.maven.wagon:wagon-ssh-external:2.10' + testCompile "junit:junit:${project.property('junit.version')}" + testCompile "org.quartz-scheduler:quartz:${project.property('quartz.version')}" + testCompile "com.google.caliper:caliper:${project.property('caliper.version')}" } -sourceCompatibility = JavaVersion.VERSION_1_8 -targetCompatibility = JavaVersion.VERSION_1_8 +compileJava { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +compileTestJava { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} -[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' tasks.withType(JavaCompile) { - options.compilerArgs << "-Xlint:all" << "-profile" << "compact2" + options.compilerArgs << "-Xlint:all" } test { testLogging { - showStandardStreams = false + showStandardStreams = true exceptionFormat = 'full' } - systemProperty 'java.util.logging.manager', 'org.apache.logging.log4j.jul.LogManager' +} + +spotbugs { + toolVersion = '3.1.12' + sourceSets = [sourceSets.main] + ignoreFailures = true + effort = "max" + reportLevel = "high" +} + +tasks.withType(Pmd) { + ignoreFailures = true + reports { + xml.enabled = true + html.enabled = true + } +} + +tasks.withType(Checkstyle) { + ignoreFailures = true + reports { + xml.enabled = true + html.enabled = true + } } task sourcesJar(type: Jar, dependsOn: classes) { classifier 'sources' from sourceSets.main.allSource } + task javadocJar(type: Jar, dependsOn: javadoc) { classifier 'javadoc' } + artifacts { archives sourcesJar, javadocJar } -if (project.hasProperty('signing.keyId')) { - signing { - sign configurations.archives + +ext { + user = 'xbib' + projectName = 'time' + projectDescription = 'A bundle of Chronic, Prettytime, and org.joda.time.format optimized for Java 8 Time API' + scmUrl = 'https://github.com/xbib/time' + scmConnection = 'scm:git:git://github.com/xbib/time.git' + scmDeveloperConnection = 'scm:git:git://github.com/xbib/time.git' +} + +task sonatypeUpload(type: Upload) { + configuration = configurations.archives + uploadDescriptor = true + repositories { + if (project.hasProperty('ossrhUsername')) { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + repository(url: uri(ossrhReleaseUrl)) { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + snapshotRepository(url: uri(ossrhSnapshotUrl)) { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + pom.project { + name projectName + description projectDescription + packaging 'jar' + inceptionYear '2016' + url scmUrl + organization { + name 'xbib' + url 'http://xbib.org' + } + developers { + developer { + id user + name 'Jörg Prante' + email 'joergprante@gmail.com' + url 'https://github.com/jprante' + } + } + scm { + url scmUrl + connection scmConnection + developerConnection scmDeveloperConnection + } + licenses { + license { + name 'The Apache License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + } + } + } + } + } } } -apply from: 'gradle/ext.gradle' -apply from: 'gradle/publish.gradle' -apply from: 'gradle/sonarqube.gradle' +nexusStaging { + packageGroup = "org.xbib" +} diff --git a/gradle.properties b/gradle.properties index 852f762..ae672f0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,8 @@ group = org.xbib name = time -version = 1.0.0 +version = 2.0.0 + +# test +junit.version = 4.12 +quartz.version = 2.3.0 +caliper.version = 1.0-beta-2 diff --git a/gradle/ext.gradle b/gradle/ext.gradle deleted file mode 100644 index 2146626..0000000 --- a/gradle/ext.gradle +++ /dev/null @@ -1,8 +0,0 @@ -ext { - user = 'xbib' - projectName = 'time' - projectDescription = 'A bundle of Chronic, Prettytime, and org.joda.time.format optimized for Java 8 Time API' - scmUrl = 'https://github.com/xbib/time' - scmConnection = 'scm:git:git://github.com/xbib/time.git' - scmDeveloperConnection = 'scm:git:git://github.com/xbib/time.git' -} diff --git a/gradle/publish.gradle b/gradle/publish.gradle deleted file mode 100644 index 59f73fd..0000000 --- a/gradle/publish.gradle +++ /dev/null @@ -1,63 +0,0 @@ - -task xbibUpload(type: Upload) { - configuration = configurations.archives - uploadDescriptor = true - repositories { - if (project.hasProperty('xbibUsername')) { - mavenDeployer { - configuration = configurations.wagon - repository(url: uri('scpexe://xbib.org/repository')) { - authentication(userName: xbibUsername, privateKey: xbibPrivateKey) - } - } - } - } -} - -task sonatypeUpload(type: Upload) { - configuration = configurations.archives - uploadDescriptor = true - repositories { - if (project.hasProperty('ossrhUsername')) { - mavenDeployer { - beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } - repository(url: uri(ossrhReleaseUrl)) { - authentication(userName: ossrhUsername, password: ossrhPassword) - } - snapshotRepository(url: uri(ossrhSnapshotUrl)) { - authentication(userName: ossrhUsername, password: ossrhPassword) - } - pom.project { - name projectName - description projectDescription - packaging 'jar' - inceptionYear '2016' - url scmUrl - organization { - name 'xbib' - url 'http://xbib.org' - } - developers { - developer { - id user - name 'Jörg Prante' - email 'joergprante@gmail.com' - url 'https://github.com/jprante' - } - } - scm { - url scmUrl - connection scmConnection - developerConnection scmDeveloperConnection - } - licenses { - license { - name 'The Apache License, Version 2.0' - url 'http://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - } - } - } - } -} diff --git a/gradle/sonarqube.gradle b/gradle/sonarqube.gradle deleted file mode 100644 index b31eafb..0000000 --- a/gradle/sonarqube.gradle +++ /dev/null @@ -1,36 +0,0 @@ -tasks.withType(FindBugs) { - ignoreFailures = true - reports { - xml.enabled = true - } -} -tasks.withType(Pmd) { - ignoreFailures = true - reports { - xml.enabled = true - } -} -tasks.withType(Checkstyle) { - ignoreFailures = true - reports { - xml.enabled = true - } -} - -jacocoTestReport { - reports { - xml.enabled true - xml.destination "${buildDir}/reports/jacoco-xml" - } -} - -sonarqube { - properties { - property "sonar.projectName", "${project.group} ${project.name}" - property "sonar.sourceEncoding", "UTF-8" - property "sonar.tests", "src/test/java" - property "sonar.scm.provider", "git" - property "sonar.java.coveragePlugin", "jacoco" - property "sonar.junit.reportsPath", "build/test-results/test/" - } -} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 51288f9..5c2d1cf 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 580ab8d..22f0587 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Nov 30 14:21:32 CET 2016 +#Mon Sep 09 15:32:28 CEST 2019 +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.2.1-all.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 4453cce..83f2acf 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# 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 +# +# https://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. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,16 +44,16 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -109,8 +125,8 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` @@ -155,7 +171,7 @@ if $cygwin ; then fi # Escape application args -save ( ) { +save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } diff --git a/gradlew.bat b/gradlew.bat index e95643d..24467a1 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/src/main/java/org/xbib/time/chronic/Chronic.java b/src/main/java/org/xbib/time/chronic/Chronic.java index 5711388..9ec7f74 100644 --- a/src/main/java/org/xbib/time/chronic/Chronic.java +++ b/src/main/java/org/xbib/time/chronic/Chronic.java @@ -33,39 +33,39 @@ public class Chronic { /** * Parses a string containing a natural language date or time. If the parser * can find a date or time, either a Time or Chronic::Span will be returned - * (depending on the value of :guess). If no date or time can be found, + * (depending on the value of {@code :guess}). If no date or time can be found, * +nil+ will be returned. *

* Options are: *

- * [:context] - * :past or :future (defaults to :future) + * [{@code :context}] + * {@code :past} or {@code :future} (defaults to {@code :future}) *

- * If your string represents a birthday, you can set :context to :past + * If your string represents a birthday, you can set {@code :context} to {@code :past} * and if an ambiguous string is given, it will assume it is in the - * past. Specify :future or omit to set a future context. + * past. Specify {@code :future<} or omit to set a future context. *

- * [:now] + * [{@code :now}] * Time (defaults to Time.now) *

- * By setting :now to a Time, all computations will be based off + * By setting {@code :now} to a Time, all computations will be based off * of that time instead of Time.now *

- * [:guess] + * [{@code :guess<}] * +true+ or +false+ (defaults to +true+) *

* By default, the parser will guess a single point in time for the * given date or time. If you'd rather have the entire time span returned, - * set :guess to +false+ and a Chronic::Span will be returned. + * set {@code :guess} to +false+ and a Chronic::Span will be returned. *

- * [:ambiguous_time_range] - * Integer or :none (defaults to 6 (6am-6pm)) + * [{@code :ambiguous_time_range}] + * Integer or {@code :none} (defaults to {@code 6} (6am-6pm)) *

* If an Integer is given, ambiguous times (like 5:00) will be * assumed to be within the range of that time in the AM to that time - * in the PM. For example, if you set it to 7, then the parser will + * in the PM. For example, if you set it to {@code 7}, then the parser will * look for the time between 7am and 7pm. In the case of 5:00, it would - * assume that means 5:00pm. If :none is given, no assumption + * assume that means 5:00pm. If {@code :none} is given, no assumption * will be made, and the first matching instance of that time will * be used. * @param text text diff --git a/src/main/java/org/xbib/time/chronic/repeaters/RepeaterUnit.java b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterUnit.java index dd70bee..d758ce5 100644 --- a/src/main/java/org/xbib/time/chronic/repeaters/RepeaterUnit.java +++ b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterUnit.java @@ -43,7 +43,9 @@ public abstract class RepeaterUnit extends Repeater { String unitName = unitNameEnum.name(); String capitalizedUnitName = unitName.substring(0, 1) + unitName.substring(1).toLowerCase(); String repeaterClassName = RepeaterUnit.class.getPackage().getName() + ".Repeater" + capitalizedUnitName; - return Class.forName(repeaterClassName).asSubclass(RepeaterUnit.class).newInstance(); + return Class.forName(repeaterClassName) + .asSubclass(RepeaterUnit.class) + .getConstructor().newInstance(); } } return null; diff --git a/src/main/java/org/xbib/time/schedule/CronExpression.java b/src/main/java/org/xbib/time/schedule/CronExpression.java new file mode 100644 index 0000000..4ae3d89 --- /dev/null +++ b/src/main/java/org/xbib/time/schedule/CronExpression.java @@ -0,0 +1,130 @@ +package org.xbib.time.schedule; + +import java.time.ZonedDateTime; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class CronExpression { + + public abstract boolean matches(ZonedDateTime t); + + public abstract ZonedDateTime nextExecution(ZonedDateTime from, ZonedDateTime to); + + private static final String YEARLY = "0 0 1 1 *", + MONTHLY = "0 0 1 * *", + WEEKLY = "0 0 * * 7", + DAILY = "0 0 * * *", + HOURLY = "0 * * * *"; + + private static final Map ALIASES = Map.ofEntries( + Map.entry("yearly", YEARLY), + Map.entry("annually", YEARLY), + Map.entry("monthly", MONTHLY), + Map.entry("weekly", WEEKLY), + Map.entry("daily", DAILY), + Map.entry("midnight", DAILY), + Map.entry("hourly", HOURLY)); + + private static final Pattern ALIAS_PATTERN = Pattern.compile("[a-z]+"); + + private static final boolean DEFAULT_ONE_BASED_DAY_OF_WEEK = false; + + private static final boolean DEFAULT_SECONDS = false; + + private static final boolean DEFAULT_ALLOW_BOTH_DAYS = true; + + public static CronExpression yearly() { + return parse(YEARLY); + } + + public static CronExpression monthly() { + return parse(MONTHLY); + } + + public static CronExpression weekly() { + return parse(WEEKLY); + } + + public static CronExpression daily() { + return parse(DAILY); + } + + public static CronExpression hourly() { + return parse(HOURLY); + } + + public static boolean isValid(String s) { + return isValid(s, DEFAULT_ONE_BASED_DAY_OF_WEEK, DEFAULT_SECONDS, DEFAULT_ALLOW_BOTH_DAYS); + } + + public static CronExpression parse(String s) { + return parse(s, DEFAULT_ONE_BASED_DAY_OF_WEEK, DEFAULT_SECONDS, DEFAULT_ALLOW_BOTH_DAYS); + } + + private static boolean isValid(String s, boolean oneBasedDayOfWeek, boolean seconds, boolean allowBothDays) { + boolean valid; + try { + parse(s, oneBasedDayOfWeek, seconds, allowBothDays); + valid = true; + } catch (Exception e) { + valid = false; + } + return valid; + } + + private static CronExpression parse(String s, boolean oneBasedDayOfWeek, boolean seconds, boolean allowBothDays) { + Objects.requireNonNull(s); + if (s.charAt(0) == '@') { + Matcher aliasMatcher = ALIAS_PATTERN.matcher(s); + if (aliasMatcher.find(1)) { + String alias = aliasMatcher.group(); + if (ALIASES.containsKey(alias)) { + return new DefaultCronExpression(ALIASES.get(alias), + DEFAULT_ONE_BASED_DAY_OF_WEEK, DEFAULT_SECONDS, DEFAULT_ALLOW_BOTH_DAYS); + } else if ("reboot".equals(alias)) { + return new RebootCronExpression(); + } + } + } + return new DefaultCronExpression(s, seconds, oneBasedDayOfWeek, allowBothDays); + } + + public static Parser parser() { + return new Parser(); + } + + public static class Parser { + private boolean oneBasedDayOfWeek, seconds, allowBothDays; + + private Parser() { + oneBasedDayOfWeek = DEFAULT_ONE_BASED_DAY_OF_WEEK; + seconds = DEFAULT_SECONDS; + allowBothDays = DEFAULT_ALLOW_BOTH_DAYS; + } + + public boolean isValid(String s) { + return CronExpression.isValid(s, oneBasedDayOfWeek, seconds, allowBothDays); + } + + public CronExpression parse(String s) { + return CronExpression.parse(s, oneBasedDayOfWeek, seconds, allowBothDays); + } + + public Parser withOneBasedDayOfWeek(boolean oneBasedDayOfWeek) { + this.oneBasedDayOfWeek = oneBasedDayOfWeek; + return this; + } + + public Parser withSecondsField(boolean secondsField) { + this.seconds = secondsField; + return this; + } + + public Parser allowBothDayFields(boolean allowBothDayFields) { + this.allowBothDays = allowBothDayFields; + return this; + } + } +} diff --git a/src/main/java/org/xbib/time/schedule/CronSchedule.java b/src/main/java/org/xbib/time/schedule/CronSchedule.java new file mode 100644 index 0000000..7c7d2bf --- /dev/null +++ b/src/main/java/org/xbib/time/schedule/CronSchedule.java @@ -0,0 +1,82 @@ +package org.xbib.time.schedule; + +import java.io.Closeable; +import java.io.IOException; +import java.time.Clock; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class CronSchedule implements Closeable { + + private final ScheduledExecutorService executor; + + private final List> entries; + + private final int periodInMilliseconds; + + private ScheduledFuture future; + + public CronSchedule(ScheduledExecutorService scheduledExecutorServices) { + this(scheduledExecutorServices, 60000); + } + + public CronSchedule(ScheduledExecutorService scheduledExecutorServices, + int periodInMilliseconds) { + this.executor = scheduledExecutorServices; + this.entries = new ArrayList<>(); + this.periodInMilliseconds = periodInMilliseconds; + } + + public void add(String name, CronExpression expression, Callable callable) { + entries.add(new Entry(name, expression, callable)); + } + + public void remove(String name) { + entries.removeIf(entry -> name.equals(entry.getName())); + } + + public void start() { + long initialDelay = periodInMilliseconds - (Clock.systemDefaultZone().millis() % periodInMilliseconds); + this.future = executor.scheduleAtFixedRate(CronSchedule.this::run, + initialDelay, periodInMilliseconds, TimeUnit.MILLISECONDS); + } + + public void run() { + run(ZonedDateTime.now()); + } + + public void run(ZonedDateTime time) { + for (Entry entry : entries) { + if (entry.getCronExpression().matches(time)) { + entry.setLastCalled(time); + executor.submit(entry.getCallable()); + } + } + } + + @Override + public void close() throws IOException { + if (future != null) { + future.cancel(true); + future = null; + } + if (executor != null) { + executor.shutdownNow(); + try { + executor.awaitTermination(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new IOException(e); + } + } + } + + @Override + public String toString() { + return entries.toString(); + } +} diff --git a/src/main/java/org/xbib/time/schedule/DayOfMonthField.java b/src/main/java/org/xbib/time/schedule/DayOfMonthField.java new file mode 100644 index 0000000..a0f1c25 --- /dev/null +++ b/src/main/java/org/xbib/time/schedule/DayOfMonthField.java @@ -0,0 +1,122 @@ +package org.xbib.time.schedule; + +import java.time.DayOfWeek; +import java.time.ZonedDateTime; +import java.time.temporal.TemporalAdjusters; + +public class DayOfMonthField extends DefaultField { + + private final boolean lastDay; + + private final boolean nearestWeekday; + + private final boolean unspecified; + + private DayOfMonthField(Builder b) { + super(b); + this.lastDay = b.lastDay; + this.nearestWeekday = b.nearestWeekday; + this.unspecified = b.unspecified; + } + + boolean isUnspecified() { + return unspecified; + } + + public boolean matches(ZonedDateTime time) { + if (unspecified) { + return true; + } + final int dayOfMonth = time.getDayOfMonth(); + if (lastDay) { + return dayOfMonth == time.with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth(); + } else if (nearestWeekday) { + DayOfWeek dayOfWeek = time.getDayOfWeek(); + if ((dayOfWeek == DayOfWeek.MONDAY && contains(time.minusDays(1).getDayOfMonth())) + || (dayOfWeek == DayOfWeek.FRIDAY && contains(time.plusDays(1).getDayOfMonth()))) { + return true; + } + } + return contains(dayOfMonth); + } + + public static DayOfMonthField parse(Tokens s) { + return new Builder().parse(s).build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + DayOfMonthField that = (DayOfMonthField) o; + return lastDay == that.lastDay && nearestWeekday == that.nearestWeekday; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (lastDay ? 1 : 0); + result = 31 * result + (nearestWeekday ? 1 : 0); + return result; + } + + @Override + public String toString() { + return isFullRange() ? "*" : getNumbers().toString(); + } + + public static class Builder extends DefaultField.Builder { + + private boolean lastDay; + + private boolean nearestWeekday; + + private boolean unspecified; + + Builder() { + super(1, 31); + } + + @Override + public DayOfMonthField build() { + return new DayOfMonthField(this); + } + + @Override + protected Builder parse(Tokens tokens) { + super.parse(tokens); + return this; + } + + @Override + protected boolean parseValue(Tokens tokens, Token token, int first, int last) { + if (token == Token.MATCH_ONE) { + unspecified = true; + return false; + } else if (token == Token.LAST) { + lastDay = true; + return false; + } else { + return super.parseValue(tokens, token, first, last); + } + } + + @Override + protected boolean parseNumber(Tokens tokens, Token token, int first, int last) { + if (token == Token.WEEKDAY) { + add(first); + nearestWeekday = true; + return false; + } else { + return super.parseNumber(tokens, token, first, last); + } + } + } +} diff --git a/src/main/java/org/xbib/time/schedule/DayOfWeekField.java b/src/main/java/org/xbib/time/schedule/DayOfWeekField.java new file mode 100644 index 0000000..4451868 --- /dev/null +++ b/src/main/java/org/xbib/time/schedule/DayOfWeekField.java @@ -0,0 +1,173 @@ +package org.xbib.time.schedule; + +import org.xbib.time.util.LinkedHashSetMultiMap; +import org.xbib.time.util.MultiMap; +import java.time.DayOfWeek; +import java.time.ZonedDateTime; +import java.time.temporal.TemporalAdjusters; +import java.util.LinkedHashSet; +import java.util.Set; + +public class DayOfWeekField extends DefaultField { + + private final MultiMap nth; + + private final Set last; + + private final boolean hasNth; + + private final boolean hasLast; + + private final boolean unspecified; + + private DayOfWeekField(Builder b) { + super(b); + this.nth = b.nth; + hasNth = !nth.isEmpty(); + this.last = b.last; + hasLast = !last.isEmpty(); + unspecified = b.unspecified; + } + + public boolean isUnspecified() { + return unspecified; + } + + public boolean matches(ZonedDateTime time) { + if (unspecified) { + return true; + } + final DayOfWeek dayOfWeek = time.getDayOfWeek(); + //int number = dayOfWeek.getValue() % 7; + int number = dayOfWeek.getValue(); + if (hasLast) { + return last.contains(number) && time.getMonth() != time.plusWeeks(1).getMonth(); + } else if (hasNth) { + int dayOfYear = time.getDayOfYear(); + if (nth.containsKey(number)) { + for (int possibleMatch : nth.get(number)) { + if (dayOfYear == time.with(TemporalAdjusters.dayOfWeekInMonth(possibleMatch, dayOfWeek)).getDayOfYear()) { + return true; + } + } + } + } + return contains(number); + } + + private int number(int dayOfWeek) { + return dayOfWeek % 7; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + DayOfWeekField that = (DayOfWeekField) o; + if (hasLast != that.hasLast) { + return false; + } + if (hasNth != that.hasNth) { + return false; + } + if (!last.equals(that.last)) { + return false; + } + return nth.equals(that.nth); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + nth.hashCode(); + result = 31 * result + last.hashCode(); + result = 31 * result + (hasNth ? 1 : 0); + result = 31 * result + (hasLast ? 1 : 0); + return result; + } + + public static DayOfWeekField parse(Tokens s, boolean oneBased) { + return new Builder(oneBased).parse(s).build(); + } + + public static class Builder extends DefaultField.Builder { + static final Keywords KEYWORDS = new Keywords(); + + static { + KEYWORDS.put("MON", 1); + KEYWORDS.put("TUE", 2); + KEYWORDS.put("WED", 3); + KEYWORDS.put("THU", 4); + KEYWORDS.put("FRI", 5); + KEYWORDS.put("SAT", 6); + KEYWORDS.put("SUN", 7); + } + + private boolean oneBased; + + private boolean unspecified; + + private final Set last; + + private final MultiMap nth; + + Builder(boolean oneBased) { + super(1, 7); + this.oneBased = oneBased; + last = new LinkedHashSet<>(); + nth = new LinkedHashSetMultiMap<>(); + } + + @Override + protected Builder parse(Tokens tokens) { + tokens.keywords(KEYWORDS); + if (oneBased) { + tokens.offset(1); + } + super.parse(tokens); + tokens.reset(); + return this; + } + + @Override + protected boolean parseValue(Tokens tokens, Token token, int first, int last) { + if (token == Token.MATCH_ONE) { + unspecified = true; + return false; + } else { + return super.parseValue(tokens, token, first, last); + } + } + + @Override + protected boolean parseNumber(Tokens tokens, Token token, int first, int last) { + if (token == Token.LAST) { + this.last.add(first); + } else if (token == Token.NTH) { + int number = nextNumber(tokens); + if (oneBased) { + number += 1; + } + if (number == 0) { + number = 7; + } + nth.put(first, number); + } else { + return super.parseNumber(tokens, token, first, last); + } + return false; + } + + @Override + public DayOfWeekField build() { + return new DayOfWeekField(this); + } + } +} diff --git a/src/main/java/org/xbib/time/schedule/DefaultCronExpression.java b/src/main/java/org/xbib/time/schedule/DefaultCronExpression.java new file mode 100644 index 0000000..11bc125 --- /dev/null +++ b/src/main/java/org/xbib/time/schedule/DefaultCronExpression.java @@ -0,0 +1,228 @@ +package org.xbib.time.schedule; + +import java.time.DayOfWeek; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAdjusters; +import java.util.Objects; +import java.util.SortedSet; +import java.util.logging.Logger; + +public class DefaultCronExpression extends CronExpression { + + private final String string; + + private final TimeField second; + + private final TimeField minute; + + private final TimeField hour; + + private final TimeField month; + + private final TimeField year; + + private final DayOfWeekField dayOfWeek; + + private final DayOfMonthField dayOfMonth; + + DefaultCronExpression(String string, boolean seconds, boolean oneBasedDayOfWeek, boolean allowBothDayFields) { + this.string = string; + if (string.isEmpty()) { + throw new IllegalArgumentException("empty spec not allowed"); + } + String s = string.toUpperCase(); + Tokens tokens = new Tokens(s); + if (seconds) { + second = DefaultField.parse(tokens, 0, 59); + } else { + second = MatchAllField.instance; + } + minute = DefaultField.parse(tokens, 0, 59); + hour = DefaultField.parse(tokens, 0, 23); + dayOfMonth = DayOfMonthField.parse(tokens); + month = MonthField.parse(tokens); + dayOfWeek = DayOfWeekField.parse(tokens, oneBasedDayOfWeek); + if (tokens.hasNext()) { + year = DefaultField.parse(tokens, 0, 0); + } else { + year = MatchAllField.instance; + } + if (!allowBothDayFields && !dayOfMonth.isUnspecified() && !dayOfWeek.isUnspecified()) { + throw new IllegalArgumentException("Day of month and day of week may not both be specified"); + } + } + + @Override + public boolean matches(ZonedDateTime t) { + return second.contains(t.getSecond()) && + minute.contains(t.getMinute()) && + hour.contains(t.getHour()) && + month.contains(t.getMonthValue()) && + year.contains(t.getYear()) && + dayOfWeek.matches(t) && + dayOfMonth.matches(t); + } + + @Override + public ZonedDateTime nextExecution(ZonedDateTime from, + ZonedDateTime to) { + ZonedDateTime next = second instanceof MatchAllField ? + from.plusMinutes(1).truncatedTo(ChronoUnit.MINUTES) : from.plusSeconds(1).truncatedTo(ChronoUnit.SECONDS); + while (true) { + if (next.isBefore(from) || next.isAfter(to)) { + throw new IllegalStateException("out of range: " + from + " < " + next + " < " + to + " -> " + this); + } + SortedSet set; + if (!year.contains(next.getYear())) { + if (!year.isFullRange()) { + set = year.getNumbers().tailSet(next.getYear()); + if (set.isEmpty()) { + next = next.plusYears(1); + continue; + } else { + next = next.plusYears(set.first() - next.getYear()); + } + } + } + if (!month.contains(next.getMonthValue())) { + if (!month.isFullRange()) { + set = month.getNumbers().tailSet(next.getMonthValue()); + if (set.isEmpty()) { + next = next.plusMonths(1); + continue; + } else { + next = next.plusMonths(set.first() - next.getMonthValue()); + } + } + } + if (!dayOfMonth.isUnspecified()) { + if (!dayOfMonth.contains(next.getDayOfMonth())) { + if (!dayOfMonth.isFullRange()) { + set = dayOfMonth.getNumbers().tailSet(next.getDayOfMonth()); + if (set.isEmpty()) { + next = next.plusDays(1).truncatedTo(ChronoUnit.DAYS); + continue; + } else { + next = next.plusDays(set.first() - next.getDayOfMonth()) + .truncatedTo(ChronoUnit.DAYS); + } + } + } + } + if (!dayOfWeek.isUnspecified()) { + if (!dayOfWeek.contains(next.getDayOfWeek().getValue())) { + if (!dayOfWeek.isFullRange()) { + set = dayOfWeek.getNumbers().tailSet(next.getDayOfWeek().getValue()); + if (set.isEmpty()) { + next = next.plusDays(1).truncatedTo(ChronoUnit.DAYS); + continue; + } else { + DayOfWeek dayOfWeek = DayOfWeek.of(set.first()); + next = next.with(TemporalAdjusters.next(dayOfWeek)); + + } + } + } + } + if (!hour.contains(next.getHour())) { + if (!hour.isFullRange()) { + set = hour.getNumbers().tailSet(next.getHour()); + if (set.isEmpty()) { + next = next.plusHours(1).truncatedTo(ChronoUnit.HOURS); + continue; + } else { + next = next.plusHours(set.first() - next.getHour()) + .truncatedTo(ChronoUnit.HOURS); + } + } + } + if (!minute.contains(next.getMinute())) { + if (!minute.isFullRange()) { + set = minute.getNumbers().tailSet(next.getMinute()); + if (set.isEmpty()) { + next = next.plusMinutes(1).truncatedTo(ChronoUnit.MINUTES); + continue; + } else { + next = next.plusMinutes(set.first() - next.getMinute()) + .truncatedTo(ChronoUnit.MINUTES); + } + } + } + if (!(second instanceof MatchAllField)) { + if (!second.contains(next.getSecond())) { + if (!second.isFullRange()) { + set = second.getNumbers().tailSet(next.getSecond()); + if (set.isEmpty()) { + next = next.plusSeconds(1).truncatedTo(ChronoUnit.SECONDS); + continue; + } else { + next = next.plusSeconds(set.first() - next.getSecond()) + .truncatedTo(ChronoUnit.SECONDS); + } + } + } + } + break; + } + return next; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DefaultCronExpression that = (DefaultCronExpression) o; + if (!Objects.equals(dayOfMonth, that.dayOfMonth)) { + return false; + } + if (!Objects.equals(dayOfWeek, that.dayOfWeek)) { + return false; + } + if (!Objects.equals(hour, that.hour)) { + return false; + } + if (!Objects.equals(minute, that.minute)) { + return false; + } + if (!Objects.equals(month, that.month)) { + return false; + } + if (!Objects.equals(second, that.second)) { + return false; + } + if (!string.equals(that.string)) { + return false; + } + return Objects.equals(year, that.year); + } + + @Override + public int hashCode() { + int result = string.hashCode(); + result = 31 * result + (second != null ? second.hashCode() : 0); + result = 31 * result + (minute != null ? minute.hashCode() : 0); + result = 31 * result + (hour != null ? hour.hashCode() : 0); + result = 31 * result + (month != null ? month.hashCode() : 0); + result = 31 * result + (year != null ? year.hashCode() : 0); + result = 31 * result + (dayOfWeek != null ? dayOfWeek.hashCode() : 0); + result = 31 * result + (dayOfMonth != null ? dayOfMonth.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "CronExpression[" + string + " -> seconds=" + second + + ",mins=" + minute + + ",hrs=" + hour + + ",dayOfMonths=" + dayOfMonth + + ",dayOfWeek=" + dayOfWeek + + ",months=" + month + + ",yrs=" + year + + "]"; + } +} diff --git a/src/main/java/org/xbib/time/schedule/DefaultField.java b/src/main/java/org/xbib/time/schedule/DefaultField.java new file mode 100644 index 0000000..c7a1715 --- /dev/null +++ b/src/main/java/org/xbib/time/schedule/DefaultField.java @@ -0,0 +1,177 @@ +package org.xbib.time.schedule; + +import java.util.NavigableSet; +import java.util.Objects; +import java.util.TreeSet; + +public class DefaultField implements TimeField { + + private final boolean fullRange; + + private final NavigableSet numbers; + + DefaultField(Builder b) { + fullRange = b.fullRange; + numbers = fullRange ? null : b.numbers; + } + + public static DefaultField parse(Tokens s, int min, int max) { + return new Builder(min, max).parse(s).build(); + } + + @Override + public boolean contains(int number) { + return fullRange || numbers.contains(number); + } + + @Override + public NavigableSet getNumbers() { + return numbers; + } + + @Override + public boolean isFullRange() { + return fullRange; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DefaultField that = (DefaultField) o; + if (fullRange != that.fullRange) { + return false; + } + return Objects.equals(numbers, that.numbers); + } + + @Override + public int hashCode() { + int result = (fullRange ? 1 : 0); + result = 31 * result + (numbers != null ? numbers.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return isFullRange() ? "*" : getNumbers().toString(); + } + + public static class Builder { + + private final NavigableSet numbers; + + private final int min; + + private final int max; + + private boolean fullRange; + + Builder(int min, int max) { + this.min = min; + this.max = max; + numbers = new TreeSet<>(); + } + + protected Builder parse(Tokens tokens) { + Token token; + while (!endOfField(token = tokens.next())) { + if (parseValue(tokens, token, min, max)) { + break; + } + } + return this; + } + + protected boolean parseValue(Tokens tokens, Token token, int first, int last) { + if (token == Token.NUMBER) { + return parseNumber(tokens, tokens.next(), tokens.number(), last); + } else if (token == Token.MATCH_ALL) { + token = tokens.next(); + if (token == Token.SKIP) { + rangeSkip(first, last, nextNumber(tokens)); + } else if (token == Token.VALUE_SEPARATOR) { + range(first, last); + } else if (endOfField(token)) { + range(first, last); + return true; + } + } + return false; + } + + /** + * Returns true if the end of this field has been reached. + * @param tokens tokens + * @param token token + * @param first first + * @param last last + * @return true if end reached + */ + protected boolean parseNumber(Tokens tokens, Token token, int first, int last) { + Token t = token; + int l = last; + if (t == Token.SKIP) { + rangeSkip(first, l, nextNumber(tokens)); + } else if (t == Token.RANGE) { + l = nextNumber(tokens); + t = tokens.next(); + if (t == Token.SKIP) { + rangeSkip(first, l, nextNumber(tokens)); + } else if (t == Token.VALUE_SEPARATOR) { + range(first, l); + } else if (endOfField(t)) { + range(first, l); + return true; + } + } else if (t == Token.VALUE_SEPARATOR) { + add(first); + } else if (endOfField(t)) { + add(first); + return true; + } + return false; + } + + int nextNumber(Tokens tokens) { + if (tokens.next() == Token.NUMBER) { + return tokens.number(); + } + throw new IllegalStateException("Expected number"); + } + + private boolean endOfField(Token token) { + return token == Token.FIELD_SEPARATOR || token == Token.END_OF_INPUT; + } + + void rangeSkip(int first, int last, int skip) { + for (int i = first; i <= last; i++) { + if ((i - min) % skip == 0) { + add(i); + } + } + } + + protected void range(int first, int last) { + if (first == min && last == max) { + fullRange = true; + } else { + for (int i = first; i <= last; i++) { + add(i); + } + } + } + + protected void add(int value) { + numbers.add(value); + } + + public DefaultField build() { + return new DefaultField(this); + } + } +} diff --git a/src/main/java/org/xbib/time/schedule/Entry.java b/src/main/java/org/xbib/time/schedule/Entry.java new file mode 100644 index 0000000..776498d --- /dev/null +++ b/src/main/java/org/xbib/time/schedule/Entry.java @@ -0,0 +1,57 @@ +package org.xbib.time.schedule; + +import java.time.ZonedDateTime; +import java.util.concurrent.Callable; + +class Entry { + + private String name; + + private CronExpression cronExpression; + + private Callable callable; + + private ZonedDateTime lastCalled; + + private ZonedDateTime nextCall; + + Entry(String name, CronExpression cronExpression, Callable callable) { + this.name = name; + this.cronExpression = cronExpression; + this.callable = callable; + } + + public String getName() { + return name; + } + + public CronExpression getCronExpression() { + return cronExpression; + } + + public Callable getCallable() { + return callable; + } + + public void setLastCalled(ZonedDateTime lastCalled) { + this.lastCalled = lastCalled; + // heuristic, limit to 1 year ahead + this.nextCall = cronExpression.nextExecution(lastCalled, lastCalled.plusYears(1)); + } + + public ZonedDateTime getLastCalled() { + return lastCalled; + } + + public ZonedDateTime getNextCall() { + return nextCall; + } + + @Override + public String toString() { + return "Entry[name=" + name + ", expression=" + cronExpression + + ",callable= " + callable + + ",lastcalled=" + lastCalled + + ",nextcall=" + nextCall + "]"; + } +} diff --git a/src/main/java/org/xbib/time/schedule/Keywords.java b/src/main/java/org/xbib/time/schedule/Keywords.java new file mode 100644 index 0000000..ed6fa29 --- /dev/null +++ b/src/main/java/org/xbib/time/schedule/Keywords.java @@ -0,0 +1,42 @@ +package org.xbib.time.schedule; + +import java.util.Arrays; + +final class Keywords { + private final int[][][] keywords = new int[26][26][26]; + + public Keywords() { + for (int[][] second : keywords) { + for (int[] third : second) { + Arrays.fill(third, -1); + } + } + } + + public void put(String keyword, int value) { + keywords[letterAt(keyword, 0)][letterAt(keyword, 1)][letterAt(keyword, 2)] = value; + } + + public int get(String s, int start, int end) { + if (end - start != 3) { + throw new IllegalArgumentException(); + } + int number = keywords[arrayIndex(s, start)][arrayIndex(s, start + 1)][arrayIndex(s, start + 2)]; + if (number >= 0) { + return number; + } + throw new IllegalArgumentException(); + } + + private int arrayIndex(String s, int charIndex) { + int index = letterAt(s, charIndex); + if (index < 0 || index >= keywords.length) { + throw new IllegalArgumentException(); + } + return index; + } + + private static int letterAt(String s, int charIndex) { + return s.charAt(charIndex) - 'A'; + } +} \ No newline at end of file diff --git a/src/main/java/org/xbib/time/schedule/MatchAllField.java b/src/main/java/org/xbib/time/schedule/MatchAllField.java new file mode 100644 index 0000000..09d3831 --- /dev/null +++ b/src/main/java/org/xbib/time/schedule/MatchAllField.java @@ -0,0 +1,23 @@ +package org.xbib.time.schedule; + +import java.util.NavigableSet; + +public enum MatchAllField implements TimeField { + + instance; + + @Override + public boolean contains(int number) { + return true; + } + + @Override + public NavigableSet getNumbers() { + return null; + } + + @Override + public boolean isFullRange() { + return true; + } +} diff --git a/src/main/java/org/xbib/time/schedule/MonthField.java b/src/main/java/org/xbib/time/schedule/MonthField.java new file mode 100644 index 0000000..8dc91a8 --- /dev/null +++ b/src/main/java/org/xbib/time/schedule/MonthField.java @@ -0,0 +1,48 @@ +package org.xbib.time.schedule; + +public class MonthField extends DefaultField { + + private MonthField(Builder b) { + super(b); + } + + public static MonthField parse(Tokens s) { + return new Builder().parse(s).build(); + } + + public static class Builder extends DefaultField.Builder { + protected static final Keywords KEYWORDS = new Keywords(); + + static { + KEYWORDS.put("JAN", 1); + KEYWORDS.put("FEB", 2); + KEYWORDS.put("MAR", 3); + KEYWORDS.put("APR", 4); + KEYWORDS.put("MAY", 5); + KEYWORDS.put("JUN", 6); + KEYWORDS.put("JUL", 7); + KEYWORDS.put("AUG", 8); + KEYWORDS.put("SEP", 9); + KEYWORDS.put("OCT", 10); + KEYWORDS.put("NOV", 11); + KEYWORDS.put("DEC", 12); + } + + Builder() { + super(1, 12); + } + + @Override + protected Builder parse(Tokens tokens) { + tokens.keywords(KEYWORDS); + super.parse(tokens); + tokens.reset(); + return this; + } + + @Override + public MonthField build() { + return new MonthField(this); + } + } +} diff --git a/src/main/java/org/xbib/time/schedule/RebootCronExpression.java b/src/main/java/org/xbib/time/schedule/RebootCronExpression.java new file mode 100644 index 0000000..311d854 --- /dev/null +++ b/src/main/java/org/xbib/time/schedule/RebootCronExpression.java @@ -0,0 +1,26 @@ +package org.xbib.time.schedule; + +import java.time.ZonedDateTime; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Matches once only. + */ +final class RebootCronExpression extends CronExpression { + + private final AtomicBoolean matchOnce; + + RebootCronExpression() { + matchOnce = new AtomicBoolean(true); + } + + @Override + public boolean matches(ZonedDateTime t) { + return matchOnce.getAndSet(false); + } + + @Override + public ZonedDateTime nextExecution(ZonedDateTime from, ZonedDateTime to) { + return null; + } +} diff --git a/src/main/java/org/xbib/time/schedule/TimeField.java b/src/main/java/org/xbib/time/schedule/TimeField.java new file mode 100644 index 0000000..2eae374 --- /dev/null +++ b/src/main/java/org/xbib/time/schedule/TimeField.java @@ -0,0 +1,12 @@ +package org.xbib.time.schedule; + +import java.util.NavigableSet; + +public interface TimeField { + + boolean contains(int number); + + NavigableSet getNumbers(); + + boolean isFullRange(); +} diff --git a/src/main/java/org/xbib/time/schedule/Token.java b/src/main/java/org/xbib/time/schedule/Token.java new file mode 100644 index 0000000..c5eb584 --- /dev/null +++ b/src/main/java/org/xbib/time/schedule/Token.java @@ -0,0 +1,15 @@ +package org.xbib.time.schedule; + +enum Token { + END_OF_INPUT, + FIELD_SEPARATOR, + LAST, + MATCH_ALL, + MATCH_ONE, + NTH, + NUMBER, + RANGE, + SKIP, + VALUE_SEPARATOR, + WEEKDAY +} diff --git a/src/main/java/org/xbib/time/schedule/Tokens.java b/src/main/java/org/xbib/time/schedule/Tokens.java new file mode 100644 index 0000000..ad950f0 --- /dev/null +++ b/src/main/java/org/xbib/time/schedule/Tokens.java @@ -0,0 +1,189 @@ +package org.xbib.time.schedule; + +final class Tokens { + + private int number; + + private int offset; + + private Keywords keywords; + + private final String source; + private final int length; + private int position; + + public Tokens(String s) { + source = s; + length = s.length(); + position = 0; + } + + public int number() { + return number; + } + + public void offset(int offset) { + this.offset = offset; + } + + public void keywords(Keywords k) { + keywords = k; + } + + public void reset() { + offset = 0; + keywords = null; + } + + public boolean hasNext() { + return hasNextChar(); + } + + public Token next() { + if (position >= length) { + return Token.END_OF_INPUT; + } + int start = position; + char c = currentChar(); + switch (c) { + case ' ': + case '\t': + do { + if (!hasNextChar()) { + position++; + break; + } + c = nextChar(); + } while (isWhitespace(c)); + return Token.FIELD_SEPARATOR; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + do { + if (!hasNextChar()) { + position++; + break; + } + c = nextChar(); + } while (isDigit(c)); + number = Integer.parseInt(substringFrom(start)) - offset; + return Token.NUMBER; + case ',': + position++; + return Token.VALUE_SEPARATOR; + case '*': + position++; + return Token.MATCH_ALL; + case '-': + position++; + return Token.RANGE; + case '/': + position++; + return Token.SKIP; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + do { + if (!hasNextChar()) { + position++; + break; + } + c = nextChar(); + } while (isLetter(c)); + if (position - start == 1) { + c = source.charAt(start); + if (c == 'L') { + return Token.LAST; + } else if (c == 'W') { + return Token.WEEKDAY; + } + throw new IllegalArgumentException(badCharacter(c, start)); + } else { + if (keywords != null) { + try { + int mapped = keywords.get(source, start, position); + if (mapped != -1) { + number = mapped; + return Token.NUMBER; + } + } catch (IllegalArgumentException ignore) { + } + } + throw new IllegalArgumentException(badKeyword(start)); + } + case '?': + position++; + return Token.MATCH_ONE; + case '#': + position++; + return Token.NTH; + } + throw new IllegalArgumentException(badCharacter(c, position)); + } + + private String badCharacter(char c, int index) { + return "Bad character '" + c + "' at position " + index + " in string: " + source; + } + + private String badKeyword(int start) { + return "Bad keyword '" + substringFrom(start) + "' at position " + start + " in string: " + source; + } + + private String substringFrom(int start) { + return source.substring(start, position); + } + + private boolean hasNextChar() { + return position < length - 1; + } + + private char nextChar() { + return source.charAt(++position); + } + + private char currentChar() { + return source.charAt(position); + } + + private static boolean isLetter(char c) { + return 'A' <= c && c <= 'Z'; + } + + private static boolean isDigit(char c) { + return '0' <= c && c <= '9'; + } + + private static boolean isWhitespace(char c) { + return c == ' ' || c == '\t'; + } +} diff --git a/src/main/java/org/xbib/time/schedule/package-info.java b/src/main/java/org/xbib/time/schedule/package-info.java new file mode 100644 index 0000000..15b052e --- /dev/null +++ b/src/main/java/org/xbib/time/schedule/package-info.java @@ -0,0 +1,4 @@ +/** + * Schedule jobs (like cron). + */ +package org.xbib.time.schedule; diff --git a/src/main/java/org/xbib/time/util/AbstractMultiMap.java b/src/main/java/org/xbib/time/util/AbstractMultiMap.java new file mode 100644 index 0000000..41a2b30 --- /dev/null +++ b/src/main/java/org/xbib/time/util/AbstractMultiMap.java @@ -0,0 +1,126 @@ +package org.xbib.time.util; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * Abstract multi map. + * + * @param the key type parameter + * @param the value type parameter + */ +abstract class AbstractMultiMap implements MultiMap { + + private final Map> map; + + AbstractMultiMap() { + this(null); + } + + private AbstractMultiMap(MultiMap map) { + this.map = newMap(); + if (map != null) { + putAll(map); + } + } + + @Override + public int size() { + return map.size(); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(K key) { + return map.containsKey(key); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public boolean put(K key, V value) { + Collection set = map.get(key); + if (set == null) { + set = newValues(); + set.add(value); + map.put(key, set); + return true; + } else { + set.add(value); + return false; + } + } + + @Override + public void putAll(K key, Iterable values) { + if (values == null) { + return; + } + Collection set = map.computeIfAbsent(key, k -> newValues()); + for (V v : values) { + set.add(v); + } + } + + @Override + public Collection get(K key) { + return map.get(key); + } + + @Override + public Collection remove(K key) { + return map.remove(key); + } + + @Override + public boolean remove(K key, V value) { + Collection set = map.get(key); + return set != null && set.remove(value); + } + + @Override + public void putAll(MultiMap map) { + if (map != null) { + for (K key : map.keySet()) { + putAll(key, map.get(key)); + } + } + } + + @Override + public Map> asMap() { + return map; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof AbstractMultiMap && map.equals(((AbstractMultiMap) obj).map); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + @Override + public String toString() { + return map.toString(); + } + + abstract Collection newValues(); + + abstract Map> newMap(); +} diff --git a/src/main/java/org/xbib/time/util/LinkedHashSetMultiMap.java b/src/main/java/org/xbib/time/util/LinkedHashSetMultiMap.java new file mode 100644 index 0000000..80fad1a --- /dev/null +++ b/src/main/java/org/xbib/time/util/LinkedHashSetMultiMap.java @@ -0,0 +1,29 @@ +package org.xbib.time.util; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; + +/** + * Linked multi map. + * + * @param the key type parameter + * @param the value type parameter + */ +public class LinkedHashSetMultiMap extends AbstractMultiMap { + + public LinkedHashSetMultiMap() { + super(); + } + + @Override + Collection newValues() { + return new LinkedHashSet<>(); + } + + @Override + Map> newMap() { + return new LinkedHashMap<>(); + } +} diff --git a/src/main/java/org/xbib/time/util/MultiMap.java b/src/main/java/org/xbib/time/util/MultiMap.java new file mode 100644 index 0000000..9f1df0e --- /dev/null +++ b/src/main/java/org/xbib/time/util/MultiMap.java @@ -0,0 +1,38 @@ +package org.xbib.time.util; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * MultiMap interface. + * + * @param the key type parameter + * @param the value type parameter + */ +public interface MultiMap { + + void clear(); + + int size(); + + boolean isEmpty(); + + boolean containsKey(K key); + + Collection get(K key); + + Set keySet(); + + boolean put(K key, V value); + + void putAll(K key, Iterable values); + + void putAll(MultiMap map); + + Collection remove(K key); + + boolean remove(K key, V value); + + Map> asMap(); +} diff --git a/src/main/java/org/xbib/time/util/TreeMultiMap.java b/src/main/java/org/xbib/time/util/TreeMultiMap.java new file mode 100644 index 0000000..89ef157 --- /dev/null +++ b/src/main/java/org/xbib/time/util/TreeMultiMap.java @@ -0,0 +1,31 @@ +package org.xbib.time.util; + +import java.util.Collection; +import java.util.Comparator; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.TreeMap; + +/** + * A {@link TreeMap} based multi map. The keys ore ordered by a comparator. + * @param te key type + * @param the value type + */ +public class TreeMultiMap extends AbstractMultiMap { + + private final Comparator comparator; + + public TreeMultiMap(Comparator comparator) { + this.comparator = comparator; + } + + @Override + Map> newMap() { + return new TreeMap<>(comparator); + } + + @Override + Collection newValues() { + return new LinkedHashSet<>(); + } +} diff --git a/src/main/java/org/xbib/time/util/package-info.java b/src/main/java/org/xbib/time/util/package-info.java new file mode 100644 index 0000000..5163410 --- /dev/null +++ b/src/main/java/org/xbib/time/util/package-info.java @@ -0,0 +1,4 @@ +/** + * + */ +package org.xbib.time.util; diff --git a/src/test/java/org/xbib/time/FormatterTest.java b/src/test/java/org/xbib/time/DateTimeFormatterTest.java similarity index 95% rename from src/test/java/org/xbib/time/FormatterTest.java rename to src/test/java/org/xbib/time/DateTimeFormatterTest.java index 83f15d8..fd67d18 100644 --- a/src/test/java/org/xbib/time/FormatterTest.java +++ b/src/test/java/org/xbib/time/DateTimeFormatterTest.java @@ -10,7 +10,7 @@ import java.util.Locale; import static org.junit.Assert.assertEquals; -public class FormatterTest { +public class DateTimeFormatterTest { @Test public void testLocalDate() { diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeAPIManipulationTest.java b/src/test/java/org/xbib/time/pretty/PrettyTimeAPIManipulationTest.java index b61d578..4164d38 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeAPIManipulationTest.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeAPIManipulationTest.java @@ -10,8 +10,10 @@ import static org.junit.Assert.assertNotNull; public class PrettyTimeAPIManipulationTest { - List list = null; - PrettyTime t = new PrettyTime(); + private List list = null; + + private PrettyTime t = new PrettyTime(); + private TimeUnitQuantity timeUnitQuantity = null; @Test diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_AR_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_AR_Test.java index 7f2675c..8159155 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_AR_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_AR_Test.java @@ -1,250 +1,252 @@ -package org.xbib.time.pretty; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.temporal.ChronoField; -import java.util.List; -import java.util.Locale; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class PrettyTimeI18n_AR_Test { - - private Locale locale; - - @Before - public void setUp() throws Exception { - locale = new Locale("ar"); - Locale.setDefault(locale); - } - - @Test - public void testCeilingInterval() throws Exception { - LocalDateTime localDateTime = LocalDateTime.of(2009, 6, 17, 0, 0); - PrettyTime p = new PrettyTime(localDateTime); - assertEquals("1 شهر مضت", p.format(LocalDateTime.of(2009, 5, 20, 0, 0))); - } - - @Test - public void testRightNow() throws Exception { - PrettyTime t = new PrettyTime(); - assertEquals("بعد لحظات", t.format(LocalDateTime.now())); - } - - @Test - public void testRightNowVariance() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("بعد لحظات", t.format(600)); - } - - @Test - public void testMinutesFromNow() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("12 دقائق من الآن", t.format(1000 * 60 * 12)); - } - - @Test - public void testHoursFromNow() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("3 ساعات من الآن", t.format(1000 * 60 * 60 * 3)); - } - - @Test - public void testDaysFromNow() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("3 ايام من الآن", t.format(1000 * 60 * 60 * 24 * 3)); - } - - @Test - public void testWeeksFromNow() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("3 أسابيع من الآن", t.format(1000 * 60 * 60 * 24 * 7 * 3)); - } - - @Test - public void testMonthsFromNow() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("3 أشهر من الآن", t.format(2629743830L * 3L)); - } - - @Test - public void testYearsFromNow() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("3 سنوات من الآن", t.format(2629743830L * 12L * 3L)); - } - - @Test - public void testDecadesFromNow() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("3 عقود من الآن", t.format(315569259747L * 3L)); - } - - @Test - public void testCenturiesFromNow() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("3 قرون من الآن", t.format(3155692597470L * 3L)); - } - - @Test - public void testMomentsAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(6000), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("منذ لحظات", t.format(0)); - } - - @Test - public void testMinutesAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 12), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("12 دقائق مضت", t.format(0)); - } - - @Test - public void testHoursAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 3), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("3 ساعات مضت", t.format(0)); - } - - @Test - public void testDaysAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("3 ايام مضت", t.format(0)); - } - - @Test - public void testWeeksAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 7 * 3), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("3 أسابيع مضت", t.format(0)); - } - - @Test - public void testMonthsAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L * 3L), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("3 أشهر مضت", t.format(0)); - } - - @Test - public void testCustomFormat() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - TimeUnit unit = new TimeUnit() { - @Override - public long getMaxQuantity() { - return 0; - } - - @Override - public long getMillisPerUnit() { - return 5000; - } - }; - t.clearUnits(); - t.registerUnit(unit, new SimpleTimeFormat() - .setSingularName("tick").setPluralName("ticks") - .setPattern("%n %u").setRoundingTolerance(20) - .setFutureSuffix("... RUN!") - .setFuturePrefix("self destruct in: ").setPastPrefix("self destruct was: ").setPastSuffix( - " ago...")); - - assertEquals("self destruct in: 5 ticks ... RUN!", t.format(25000)); - localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(25000), ZoneId.systemDefault()); - t.setReference(localDateTime); - assertEquals("self destruct was: 5 ticks ago...", t.format(0)); - } - - @Test - public void testYearsAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L * 12L * 3L), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("3 سنوات مضت", t.format(0)); - } - - @Test - public void testDecadesAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(315569259747L * 3L), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("3 عقود مضت", t.format(0)); - } - - @Test - public void testCenturiesAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(3155692597470L * 3L), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("3 قرون مضت", t.format(0)); - } - - @Test - public void testWithinTwoHoursRounding() throws Exception { - PrettyTime t = new PrettyTime(); - LocalDateTime localDateTime = LocalDateTime.now().minus(6543990, ChronoField.MILLI_OF_SECOND.getBaseUnit()); - assertEquals("2 ساعات مضت", t.format(localDateTime)); - } - - @Test - public void testPreciseInTheFuture() throws Exception { - PrettyTime t = new PrettyTime(); - LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(10 * 60 + 5 * 60 * 60); - List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); - assertTrue(timeUnitQuantities.size() >= 2); - assertEquals(5, timeUnitQuantities.get(0).getQuantity()); - assertEquals(10, timeUnitQuantities.get(1).getQuantity()); - } - - @Test - public void testPreciseInThePast() throws Exception { - PrettyTime t = new PrettyTime(); - LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(10 * 60 + 5 * 60 * 60); - List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); - assertTrue(timeUnitQuantities.size() >= 2); - assertEquals(-5, timeUnitQuantities.get(0).getQuantity()); - assertTrue(timeUnitQuantities.get(1).getQuantity() == -9 || timeUnitQuantities.get(1).getQuantity() == -10); - } - - @Test - public void testFormattingDurationListInThePast() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - List timeUnitQuantities = t.calculatePreciseDuration(LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault())); - assertEquals("3 ايام 15 ساعات 38 دقائق مضت", t.format(timeUnitQuantities)); - } - - @Test - public void testFormattingDurationListInTheFuture() throws Exception { - PrettyTime t = new PrettyTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault())); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38), ZoneId.systemDefault()); - List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); - assertEquals("3 ايام 15 ساعات 38 دقائق من الآن", t.format(timeUnitQuantities)); - } - - @Test - public void testSetLocale() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(315569259747L * 3L), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("3 عقود مضت", t.format(0)); - } - - @After - public void tearDown() throws Exception { - Locale.setDefault(locale); - } - -} +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoField; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PrettyTimeI18n_AR_Test { + + private Locale defaultLocale; + + private Locale locale; + + @Before + public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); + locale = new Locale("ar"); + Locale.setDefault(locale); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime localDateTime = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime p = new PrettyTime(localDateTime); + assertEquals("1 شهر مضت", p.format(LocalDateTime.of(2009, 5, 20, 0, 0))); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(); + assertEquals("بعد لحظات", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("بعد لحظات", t.format(600)); + } + + @Test + public void testMinutesFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("12 دقائق من الآن", t.format(1000 * 60 * 12)); + } + + @Test + public void testHoursFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 ساعات من الآن", t.format(1000 * 60 * 60 * 3)); + } + + @Test + public void testDaysFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 ايام من الآن", t.format(1000 * 60 * 60 * 24 * 3)); + } + + @Test + public void testWeeksFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 أسابيع من الآن", t.format(1000 * 60 * 60 * 24 * 7 * 3)); + } + + @Test + public void testMonthsFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 أشهر من الآن", t.format(2629743830L * 3L)); + } + + @Test + public void testYearsFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 سنوات من الآن", t.format(2629743830L * 12L * 3L)); + } + + @Test + public void testDecadesFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 عقود من الآن", t.format(315569259747L * 3L)); + } + + @Test + public void testCenturiesFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 قرون من الآن", t.format(3155692597470L * 3L)); + } + + @Test + public void testMomentsAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(6000), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("منذ لحظات", t.format(0)); + } + + @Test + public void testMinutesAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 12), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("12 دقائق مضت", t.format(0)); + } + + @Test + public void testHoursAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 3), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 ساعات مضت", t.format(0)); + } + + @Test + public void testDaysAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 ايام مضت", t.format(0)); + } + + @Test + public void testWeeksAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 7 * 3), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 أسابيع مضت", t.format(0)); + } + + @Test + public void testMonthsAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 أشهر مضت", t.format(0)); + } + + @Test + public void testCustomFormat() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + TimeUnit unit = new TimeUnit() { + @Override + public long getMaxQuantity() { + return 0; + } + + @Override + public long getMillisPerUnit() { + return 5000; + } + }; + t.clearUnits(); + t.registerUnit(unit, new SimpleTimeFormat() + .setSingularName("tick").setPluralName("ticks") + .setPattern("%n %u").setRoundingTolerance(20) + .setFutureSuffix("... RUN!") + .setFuturePrefix("self destruct in: ").setPastPrefix("self destruct was: ").setPastSuffix( + " ago...")); + + assertEquals("self destruct in: 5 ticks ... RUN!", t.format(25000)); + localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(25000), ZoneId.systemDefault()); + t.setReference(localDateTime); + assertEquals("self destruct was: 5 ticks ago...", t.format(0)); + } + + @Test + public void testYearsAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L * 12L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 سنوات مضت", t.format(0)); + } + + @Test + public void testDecadesAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(315569259747L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 عقود مضت", t.format(0)); + } + + @Test + public void testCenturiesAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(3155692597470L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 قرون مضت", t.format(0)); + } + + @Test + public void testWithinTwoHoursRounding() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minus(6543990, ChronoField.MILLI_OF_SECOND.getBaseUnit()); + assertEquals("2 ساعات مضت", t.format(localDateTime)); + } + + @Test + public void testPreciseInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); + assertEquals(5, timeUnitQuantities.get(0).getQuantity()); + assertEquals(10, timeUnitQuantities.get(1).getQuantity()); + } + + @Test + public void testPreciseInThePast() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); + assertEquals(-5, timeUnitQuantities.get(0).getQuantity()); + assertTrue(timeUnitQuantities.get(1).getQuantity() == -9 || timeUnitQuantities.get(1).getQuantity() == -10); + } + + @Test + public void testFormattingDurationListInThePast() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + List timeUnitQuantities = t.calculatePreciseDuration(LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault())); + assertEquals("3 ايام 15 ساعات 38 دقائق مضت", t.format(timeUnitQuantities)); + } + + @Test + public void testFormattingDurationListInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault())); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 ايام 15 ساعات 38 دقائق من الآن", t.format(timeUnitQuantities)); + } + + @Test + public void testSetLocale() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(315569259747L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 عقود مضت", t.format(0)); + } + + @After + public void tearDown() throws Exception { + Locale.setDefault(defaultLocale); + } +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_BG_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_BG_Test.java index 0ddcc86..dd9cd16 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_BG_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_BG_Test.java @@ -1,224 +1,224 @@ -package org.xbib.time.pretty; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.List; -import java.util.Locale; - -import static org.junit.Assert.assertEquals; - -public class PrettyTimeI18n_BG_Test { - - // Stores current locale so that it can be restored - private Locale locale; - - // Method setUp() is called automatically before every test method - @Before - public void setUp() throws Exception { - locale = Locale.getDefault(); - Locale.setDefault(new Locale("bg")); - } - - @Test - public void testCenturiesFromNow() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0L), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("след 3 века", t.format(3155692597470L * 3L)); - } - - @Test - public void testCenturiesAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(3155692597470L * 3L), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("преди 3 века", t.format(0)); - } - - @Test - public void testCenturySingular() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(3155692597470L), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("преди 1 век", t.format(0)); - } - - @Test - public void testDaysFromNow() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("след 3 дни", t.format(1000 * 60 * 60 * 24 * 3)); - } - - @Test - public void testDaysAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("преди 3 дни", t.format(0)); - } - - @Test - public void testDaySingular() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("преди 1 ден", t.format(0)); - } - - @Test - public void testDecadesAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(315569259747L * 3L), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("преди 3 десетилетия", t.format(0)); - } - - @Test - public void testDecadesFromNow() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("след 3 десетилетия", t.format(315569259747L * 3L)); - } - - @Test - public void testDecadeSingular() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("след 1 десетилетие", t.format(315569259747L)); - } - - @Test - public void testHoursFromNow() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("след 3 часа", t.format(1000 * 60 * 60 * 3)); - } - - @Test - public void testHoursAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 3), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("преди 3 часа", t.format(0)); - } - - @Test - public void testHourSingular() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("преди 1 час", t.format(0)); - } - - @Test - public void testRightNow() throws Exception { - PrettyTime t = new PrettyTime(); - assertEquals("в момента", t.format(LocalDateTime.now())); - } - - @Test - public void testMomentsAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(6000), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("току що", t.format(0)); - } - - @Test - public void testMinutesFromNow() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("след 12 минути", t.format(1000 * 60 * 12)); - } - - @Test - public void testMinutesAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 12), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("преди 12 минути", t.format(0)); - } - - @Test - public void testMonthsFromNow() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("след 3 месеца", t.format((2629743830L * 3L))); - } - - @Test - public void testMonthsAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L * 3L), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("преди 3 месеца", t.format(0)); - } - - @Test - public void testMonthSingular() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("преди 1 месец", t.format(0)); - } - - @Test - public void testWeeksFromNow() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("след 3 седмици", t.format(1000 * 60 * 60 * 24 * 7 * 3)); - } - - @Test - public void testWeeksAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 7 * 3), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("преди 3 седмици", t.format(0)); - } - - @Test - public void testWeekSingular() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 7), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("преди 1 седмица", t.format(0)); - } - - @Test - public void testYearsFromNow() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("след 3 години", t.format(2629743830L * 12L * 3L)); - } - - @Test - public void testYearsAgo() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L * 12L * 3L), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("преди 3 години", t.format(0)); - } - - @Test - public void testYearSingular() throws Exception { - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L * 12L), ZoneId.systemDefault()); - PrettyTime t = new PrettyTime(localDateTime); - assertEquals("преди 1 година", t.format(0)); - } - - @Test - public void testFormattingDurationListInThePast() throws Exception { - PrettyTime t = new PrettyTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38), ZoneId.systemDefault())); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0L), ZoneId.systemDefault()); - List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); - assertEquals("преди 3 дни 15 часа 38 минути", t.format(timeUnitQuantities)); - } - - @Test - public void testFormattingDurationListInTheFuture() throws Exception { - PrettyTime t = new PrettyTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(0L), ZoneId.systemDefault())); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 - + 1000 * 60 * 38), ZoneId.systemDefault()); - List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); - assertEquals("след 3 дни 15 часа 38 минути", t.format(timeUnitQuantities)); - } - - // Method tearDown() is called automatically after every test method - @After - public void tearDown() throws Exception { - Locale.setDefault(locale); - } - -} +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeI18n_BG_Test { + + private Locale defaultLocale; + + private Locale locale; + + @Before + public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); + locale = new Locale("bg"); + Locale.setDefault(locale); + } + + @Test + public void testCenturiesFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 3 века", t.format(3155692597470L * 3L)); + } + + @Test + public void testCenturiesAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(3155692597470L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 3 века", t.format(0)); + } + + @Test + public void testCenturySingular() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(3155692597470L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 1 век", t.format(0)); + } + + @Test + public void testDaysFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 3 дни", t.format(1000 * 60 * 60 * 24 * 3)); + } + + @Test + public void testDaysAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 3 дни", t.format(0)); + } + + @Test + public void testDaySingular() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 1 ден", t.format(0)); + } + + @Test + public void testDecadesAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(315569259747L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 3 десетилетия", t.format(0)); + } + + @Test + public void testDecadesFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 3 десетилетия", t.format(315569259747L * 3L)); + } + + @Test + public void testDecadeSingular() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 1 десетилетие", t.format(315569259747L)); + } + + @Test + public void testHoursFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 3 часа", t.format(1000 * 60 * 60 * 3)); + } + + @Test + public void testHoursAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 3), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 3 часа", t.format(0)); + } + + @Test + public void testHourSingular() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 1 час", t.format(0)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(); + assertEquals("в момента", t.format(LocalDateTime.now())); + } + + @Test + public void testMomentsAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(6000), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("току що", t.format(0)); + } + + @Test + public void testMinutesFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 12 минути", t.format(1000 * 60 * 12)); + } + + @Test + public void testMinutesAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 12), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 12 минути", t.format(0)); + } + + @Test + public void testMonthsFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 3 месеца", t.format((2629743830L * 3L))); + } + + @Test + public void testMonthsAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 3 месеца", t.format(0)); + } + + @Test + public void testMonthSingular() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 1 месец", t.format(0)); + } + + @Test + public void testWeeksFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 3 седмици", t.format(1000 * 60 * 60 * 24 * 7 * 3)); + } + + @Test + public void testWeeksAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 7 * 3), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 3 седмици", t.format(0)); + } + + @Test + public void testWeekSingular() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 7), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 1 седмица", t.format(0)); + } + + @Test + public void testYearsFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 3 години", t.format(2629743830L * 12L * 3L)); + } + + @Test + public void testYearsAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L * 12L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 3 години", t.format(0)); + } + + @Test + public void testYearSingular() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L * 12L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 1 година", t.format(0)); + } + + @Test + public void testFormattingDurationListInThePast() throws Exception { + PrettyTime t = new PrettyTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38), ZoneId.systemDefault())); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0L), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("преди 3 дни 15 часа 38 минути", t.format(timeUnitQuantities)); + } + + @Test + public void testFormattingDurationListInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(0L), ZoneId.systemDefault())); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + + 1000 * 60 * 38), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("след 3 дни 15 часа 38 минути", t.format(timeUnitQuantities)); + } + + @After + public void tearDown() throws Exception { + Locale.setDefault(defaultLocale); + } + +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_CA_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_CA_Test.java index c9f5df5..73d20f1 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_CA_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_CA_Test.java @@ -1,7 +1,7 @@ package org.xbib.time.pretty; -import org.junit.AfterClass; -import org.junit.BeforeClass; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import java.time.Instant; @@ -15,16 +15,14 @@ import static org.junit.Assert.assertTrue; public class PrettyTimeI18n_CA_Test { - protected static Locale locale; + private Locale defaultLocale; - @BeforeClass - public static void setUp() throws Exception { - locale = Locale.getDefault(); - Locale.setDefault(new Locale("ca")); - } + private Locale locale; - @AfterClass - public static void tearDown() throws Exception { + @Before + public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); + locale = new Locale("ca"); Locale.setDefault(locale); } @@ -230,4 +228,8 @@ public class PrettyTimeI18n_CA_Test { assertEquals("vor 3 Jahrzehnten", t.format((0))); } + @After + public void tearDown() throws Exception { + Locale.setDefault(defaultLocale); + } } diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_CS_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_CS_Test.java index 5baf377..90c24a1 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_CS_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_CS_Test.java @@ -1,8 +1,8 @@ package org.xbib.time.pretty; -import org.junit.AfterClass; +import org.junit.After; import org.junit.Assert; -import org.junit.BeforeClass; +import org.junit.Before; import org.junit.Test; import org.xbib.time.pretty.units.JustNow; import org.xbib.time.pretty.units.Month; @@ -17,16 +17,15 @@ import java.util.Locale; import static org.junit.Assert.assertEquals; public class PrettyTimeI18n_CS_Test { - private static Locale locale; - @BeforeClass - public static void setUp() throws Exception { - locale = Locale.getDefault(); - Locale.setDefault(new Locale("cs")); - } + private Locale defaultLocale; - @AfterClass - public static void tearDown() throws Exception { + private Locale locale; + + @Before + public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); + locale = new Locale("cs"); Locale.setDefault(locale); } @@ -213,7 +212,7 @@ public class PrettyTimeI18n_CS_Test { /** * Tests formatApproximateDuration and by proxy, formatDuration. * - * @throws Exception + * @throws Exception exception */ @Test public void testFormatApproximateDuration() throws Exception { @@ -224,4 +223,8 @@ public class PrettyTimeI18n_CS_Test { Assert.assertEquals("10 minutami", result); } + @After + public void tearDown() throws Exception { + Locale.setDefault(defaultLocale); + } } diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_DA_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_DA_Test.java index 0febb08..87bac2b 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_DA_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_DA_Test.java @@ -1,155 +1,166 @@ -package org.xbib.time.pretty; - -import org.junit.Before; -import org.junit.Test; - -import java.time.LocalDateTime; -import java.util.Locale; - -import static org.junit.Assert.assertEquals; - -public class PrettyTimeI18n_DA_Test { - private Locale locale; - - @Before - public void setUp() throws Exception { - locale = new Locale("da"); - } - - @Test - public void testPrettyTime() { - PrettyTime p = new PrettyTime(locale); - assertEquals("straks", p.format(LocalDateTime.now())); - } - - @Test - public void testPrettyTimeCenturies() { - PrettyTime p = new PrettyTime(3155692597470L * 3L, locale); - assertEquals("3 århundreder siden", p.format(0)); - - p = new PrettyTime(0, locale); - assertEquals("3 århundreder fra nu", p.format(3155692597470L * 3L)); - } - - @Test - public void testCeilingInterval() throws Exception { - LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); - LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); - PrettyTime t = new PrettyTime(ref, locale); - assertEquals("1 måned siden", t.format(then)); - } - - @Test - public void testRightNow() throws Exception { - PrettyTime t = new PrettyTime(locale); - assertEquals("straks", t.format(LocalDateTime.now())); - } - - @Test - public void testRightNowVariance() throws Exception { - PrettyTime t = new PrettyTime(0, locale); - assertEquals("straks", t.format(600)); - } - - @Test - public void testMinutesFromNow() throws Exception { - PrettyTime t = new PrettyTime(0, locale); - assertEquals("om 12 minutter", t.format(1000 * 60 * 12)); - } - - @Test - public void testHoursFromNow() throws Exception { - PrettyTime t = new PrettyTime(0, locale); - assertEquals("om 3 timer", t.format(1000 * 60 * 60 * 3)); - } - - @Test - public void testDaysFromNow() throws Exception { - PrettyTime t = new PrettyTime(0, locale); - assertEquals("om 3 dage", t.format(1000 * 60 * 60 * 24 * 3)); - } - - @Test - public void testWeeksFromNow() throws Exception { - PrettyTime t = new PrettyTime(0, locale); - assertEquals("om 3 uger", t.format(1000 * 60 * 60 * 24 * 7 * 3)); - } - - @Test - public void testMonthsFromNow() throws Exception { - PrettyTime t = new PrettyTime(0, locale); - assertEquals("om 3 måneder", t.format(2629743830L * 3L)); - } - - @Test - public void testYearsFromNow() throws Exception { - PrettyTime t = new PrettyTime(0, locale); - assertEquals("om 3 år", t.format(2629743830L * 12L * 3L)); - } - - @Test - public void testDecadesFromNow() throws Exception { - PrettyTime t = new PrettyTime(0, locale); - assertEquals("3 årtier fra nu", t.format(315569259747L * 3L)); - } - - @Test - public void testCenturiesFromNow() throws Exception { - PrettyTime t = new PrettyTime(0, locale); - assertEquals("3 århundreder fra nu", t.format(3155692597470L * 3L)); - } - - @Test - public void testMomentsAgo() throws Exception { - PrettyTime t = new PrettyTime(6000, locale); - assertEquals("et øjeblik siden", t.format(0)); - } - - @Test - public void testMinutesAgo() throws Exception { - PrettyTime t = new PrettyTime((1000 * 60 * 12), locale); - assertEquals("12 minutter siden", t.format(0)); - } - - @Test - public void testHoursAgo() throws Exception { - PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3), locale); - assertEquals("3 timer siden", t.format(0)); - } - - @Test - public void testDaysAgo() throws Exception { - PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3), locale); - assertEquals("3 dage siden", t.format(0)); - } - - @Test - public void testWeeksAgo() throws Exception { - PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3), locale); - assertEquals("3 uger siden", t.format(0)); - } - - @Test - public void testMonthsAgo() throws Exception { - PrettyTime t = new PrettyTime((2629743830L * 3L), locale); - assertEquals("3 måneder siden", t.format(0)); - } - - @Test - public void testYearsAgo() throws Exception { - PrettyTime t = new PrettyTime((2629743830L * 12L * 3L), locale); - assertEquals("3 år siden", t.format(0)); - } - - @Test - public void testDecadesAgo() throws Exception { - PrettyTime t = new PrettyTime((315569259747L * 3L), locale); - assertEquals("3 årtier siden", t.format(0)); - } - - @Test - public void testCenturiesAgo() throws Exception { - PrettyTime t = new PrettyTime((3155692597470L * 3L), locale); - assertEquals("3 århundreder siden", t.format(0)); - } -} \ No newline at end of file +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.LocalDateTime; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeI18n_DA_Test { + + private Locale defaultLocale; + + private Locale locale; + + @Before + public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); + locale = new Locale("da"); + Locale.setDefault(locale); + } + + @Test + public void testPrettyTime() { + PrettyTime p = new PrettyTime(locale); + assertEquals("straks", p.format(LocalDateTime.now())); + } + + @Test + public void testPrettyTimeCenturies() { + PrettyTime p = new PrettyTime(3155692597470L * 3L, locale); + assertEquals("3 århundreder siden", p.format(0)); + + p = new PrettyTime(0, locale); + assertEquals("3 århundreder fra nu", p.format(3155692597470L * 3L)); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref, locale); + assertEquals("1 måned siden", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(locale); + assertEquals("straks", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("straks", t.format(600)); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("om 12 minutter", t.format(1000 * 60 * 12)); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("om 3 timer", t.format(1000 * 60 * 60 * 3)); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("om 3 dage", t.format(1000 * 60 * 60 * 24 * 3)); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("om 3 uger", t.format(1000 * 60 * 60 * 24 * 7 * 3)); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("om 3 måneder", t.format(2629743830L * 3L)); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("om 3 år", t.format(2629743830L * 12L * 3L)); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("3 årtier fra nu", t.format(315569259747L * 3L)); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("3 århundreder fra nu", t.format(3155692597470L * 3L)); + } + + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime(6000, locale); + assertEquals("et øjeblik siden", t.format(0)); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 12), locale); + assertEquals("12 minutter siden", t.format(0)); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3), locale); + assertEquals("3 timer siden", t.format(0)); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3), locale); + assertEquals("3 dage siden", t.format(0)); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3), locale); + assertEquals("3 uger siden", t.format(0)); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 3L), locale); + assertEquals("3 måneder siden", t.format(0)); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 12L * 3L), locale); + assertEquals("3 år siden", t.format(0)); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 3L), locale); + assertEquals("3 årtier siden", t.format(0)); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime((3155692597470L * 3L), locale); + assertEquals("3 århundreder siden", t.format(0)); + } + + @After + public void tearDown() throws Exception { + Locale.setDefault(defaultLocale); + } +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_ET_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_ET_Test.java index 546f3dd..0eaf932 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_ET_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_ET_Test.java @@ -1,5 +1,6 @@ package org.xbib.time.pretty; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.xbib.time.pretty.units.JustNow; @@ -14,11 +15,16 @@ import java.util.Locale; import static org.junit.Assert.assertEquals; public class PrettyTimeI18n_ET_Test { + + private Locale defaultLocale; + private Locale locale; @Before public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); locale = new Locale("et"); + Locale.setDefault(locale); } @Test @@ -333,4 +339,8 @@ public class PrettyTimeI18n_ET_Test { return t; } + @After + public void tearDown() throws Exception { + Locale.setDefault(defaultLocale); + } } diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FA_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FA_Test.java index dc96683..a476989 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FA_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FA_Test.java @@ -1,239 +1,238 @@ -package org.xbib.time.pretty; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.List; -import java.util.Locale; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class PrettyTimeI18n_FA_Test { - - // Stores current locale so that it can be restored - private Locale locale; - - // Method setUp() is called automatically before every test method - @Before - public void setUp() throws Exception { - locale = new Locale("fa"); - Locale.setDefault(locale); - } - - @Test - public void testCeilingInterval() throws Exception { - LocalDateTime then = LocalDateTime.of(2012, 5, 20, 0, 0); - LocalDateTime ref = LocalDateTime.of(2012, 6, 17, 0, 0); - PrettyTime t = new PrettyTime(ref); - assertEquals("1 ماه پیش", t.format(then)); - } - - @Test - public void testRightNow() throws Exception { - PrettyTime t = new PrettyTime(); - assertEquals("چند لحظه دیگر", t.format(LocalDateTime.now())); - } - - @Test - public void testRightNowVariance() throws Exception { - PrettyTime t = new PrettyTime(0); - assertEquals("چند لحظه دیگر", t.format((600))); - } - - @Test - public void testMinutesFromNow() throws Exception { - PrettyTime t = new PrettyTime(0); - assertEquals("12 دقیقه دیگر", t.format((1000 * 60 * 12))); - } - - @Test - public void testHoursFromNow() throws Exception { - PrettyTime t = new PrettyTime(0); - assertEquals("3 ساعت دیگر", t.format((1000 * 60 * 60 * 3))); - } - - @Test - public void testDaysFromNow() throws Exception { - PrettyTime t = new PrettyTime(0); - assertEquals("3 روز دیگر", t.format((1000 * 60 * 60 * 24 * 3))); - } - - @Test - public void testWeeksFromNow() throws Exception { - PrettyTime t = new PrettyTime(0); - assertEquals("3 هفته دیگر", t.format((1000 * 60 * 60 * 24 * 7 * 3))); - } - - @Test - public void testMonthsFromNow() throws Exception { - PrettyTime t = new PrettyTime(0); - assertEquals("3 ماه دیگر", t.format((2629743830L * 3L))); - } - - @Test - public void testYearsFromNow() throws Exception { - PrettyTime t = new PrettyTime(0); - assertEquals("3 سال دیگر", t.format((2629743830L * 12L * 3L))); - } - - @Test - public void testDecadesFromNow() throws Exception { - PrettyTime t = new PrettyTime(0); - assertEquals("3 دهه دیگر", t.format((315569259747L * 3L))); - } - - @Test - public void testCenturiesFromNow() throws Exception { - PrettyTime t = new PrettyTime(0); - assertEquals("3 قرن دیگر", t.format((3155692597470L * 3L))); - } - - /* - * Past - */ - @Test - public void testMomentsAgo() throws Exception { - PrettyTime t = new PrettyTime(6000); - assertEquals("چند لحظه پیش", t.format((0))); - } - - @Test - public void testMinutesAgo() throws Exception { - PrettyTime t = new PrettyTime(1000 * 60 * 12); - assertEquals("12 دقیقه پیش", t.format((0))); - } - - @Test - public void testHoursAgo() throws Exception { - PrettyTime t = new PrettyTime(1000 * 60 * 60 * 3); - assertEquals("3 ساعت پیش", t.format((0))); - } - - @Test - public void testDaysAgo() throws Exception { - PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24 * 3); - assertEquals("3 روز پیش", t.format((0))); - } - - @Test - public void testWeeksAgo() throws Exception { - PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24 * 7 * 3); - assertEquals("3 هفته پیش", t.format((0))); - } - - @Test - public void testMonthsAgo() throws Exception { - PrettyTime t = new PrettyTime(2629743830L * 3L); - assertEquals("3 ماه پیش", t.format((0))); - } - - @Test - public void testCustomFormat() throws Exception { - PrettyTime t = new PrettyTime(0); - TimeUnit unit = new TimeUnit() { - @Override - public long getMaxQuantity() { - return 0; - } - - @Override - public long getMillisPerUnit() { - return 5000; - } - }; - t.clearUnits(); - t.registerUnit(unit, new SimpleTimeFormat() - .setSingularName("tick").setPluralName("ticks") - .setPattern("%n %u").setRoundingTolerance(20) - .setFutureSuffix("... RUN!") - .setFuturePrefix("self destruct in: ").setPastPrefix("self destruct was: ").setPastSuffix( - " ago...")); - - assertEquals("self destruct in: 5 ticks ... RUN!", t.format((25000))); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(25000), ZoneId.systemDefault()); - t.setReference(localDateTime); - assertEquals("self destruct was: 5 ticks ago...", t.format((0))); - } - - @Test - public void testYearsAgo() throws Exception { - PrettyTime t = new PrettyTime(2629743830L * 12L * 3L); - assertEquals("3 سال پیش", t.format((0))); - } - - @Test - public void testDecadesAgo() throws Exception { - PrettyTime t = new PrettyTime(315569259747L * 3L); - assertEquals("3 دهه پیش", t.format((0))); - } - - @Test - public void testCenturiesAgo() throws Exception { - PrettyTime t = new PrettyTime(3155692597470L * 3L); - assertEquals("3 قرن پیش", t.format((0))); - } - - @Test - public void testWithinTwoHoursRounding() throws Exception { - PrettyTime t = new PrettyTime(); - LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(6543); - assertEquals("2 ساعت پیش", t.format(localDateTime)); - } - - @Test - public void testPreciseInTheFuture() throws Exception { - PrettyTime t = new PrettyTime(); - LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(10 * 60 + 5 * 60 * 60); - List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); - assertTrue(timeUnitQuantities.size() >= 2); // might be more because of milliseconds between date capturing and result - // calculation - assertEquals(5, timeUnitQuantities.get(0).getQuantity()); - assertEquals(10, timeUnitQuantities.get(1).getQuantity()); - } - - @Test - public void testPreciseInThePast() throws Exception { - PrettyTime t = new PrettyTime(); - LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(10 * 60 + 5 * 60 * 60); - List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); - assertTrue(timeUnitQuantities.size() >= 2); // might be more because of milliseconds between date capturing and result - // calculation - assertEquals(-5, timeUnitQuantities.get(0).getQuantity()); - assertTrue(-10 == timeUnitQuantities.get(1).getQuantity() || -9 == timeUnitQuantities.get(1).getQuantity()); - } - - @Test - public void testFormattingDurationListInThePast() throws Exception { - PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); - assertEquals("3 روز 15 ساعت 38 دقیقه پیش", t.format(timeUnitQuantities)); - } - - @Test - public void testFormattingDurationListInTheFuture() throws Exception { - PrettyTime t = new PrettyTime(0); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 - + 1000 * 60 * 38), ZoneId.systemDefault()); - List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); - assertEquals("3 روز 15 ساعت 38 دقیقه دیگر", t.format(timeUnitQuantities)); - } - - @Test - public void testSetLocale() throws Exception { - PrettyTime t = new PrettyTime(315569259747L * 3L); - assertEquals("3 دهه پیش", t.format((0))); - } - - // Method tearDown() is called automatically after every test method - @After - public void tearDown() throws Exception { - Locale.setDefault(locale); - } - -} +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PrettyTimeI18n_FA_Test { + + private Locale defaultLocale; + + private Locale locale; + + @Before + public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); + locale = new Locale("fa"); + Locale.setDefault(locale); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2012, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2012, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref); + assertEquals("1 ماه پیش", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(); + assertEquals("چند لحظه دیگر", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("چند لحظه دیگر", t.format((600))); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("12 دقیقه دیگر", t.format((1000 * 60 * 12))); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("3 ساعت دیگر", t.format((1000 * 60 * 60 * 3))); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("3 روز دیگر", t.format((1000 * 60 * 60 * 24 * 3))); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("3 هفته دیگر", t.format((1000 * 60 * 60 * 24 * 7 * 3))); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("3 ماه دیگر", t.format((2629743830L * 3L))); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("3 سال دیگر", t.format((2629743830L * 12L * 3L))); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("3 دهه دیگر", t.format((315569259747L * 3L))); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("3 قرن دیگر", t.format((3155692597470L * 3L))); + } + + /* + * Past + */ + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime(6000); + assertEquals("چند لحظه پیش", t.format((0))); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 12); + assertEquals("12 دقیقه پیش", t.format((0))); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 3); + assertEquals("3 ساعت پیش", t.format((0))); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24 * 3); + assertEquals("3 روز پیش", t.format((0))); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24 * 7 * 3); + assertEquals("3 هفته پیش", t.format((0))); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime(2629743830L * 3L); + assertEquals("3 ماه پیش", t.format((0))); + } + + @Test + public void testCustomFormat() throws Exception { + PrettyTime t = new PrettyTime(0); + TimeUnit unit = new TimeUnit() { + @Override + public long getMaxQuantity() { + return 0; + } + + @Override + public long getMillisPerUnit() { + return 5000; + } + }; + t.clearUnits(); + t.registerUnit(unit, new SimpleTimeFormat() + .setSingularName("tick").setPluralName("ticks") + .setPattern("%n %u").setRoundingTolerance(20) + .setFutureSuffix("... RUN!") + .setFuturePrefix("self destruct in: ").setPastPrefix("self destruct was: ").setPastSuffix( + " ago...")); + + assertEquals("self destruct in: 5 ticks ... RUN!", t.format((25000))); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(25000), ZoneId.systemDefault()); + t.setReference(localDateTime); + assertEquals("self destruct was: 5 ticks ago...", t.format((0))); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime(2629743830L * 12L * 3L); + assertEquals("3 سال پیش", t.format((0))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime(315569259747L * 3L); + assertEquals("3 دهه پیش", t.format((0))); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime(3155692597470L * 3L); + assertEquals("3 قرن پیش", t.format((0))); + } + + @Test + public void testWithinTwoHoursRounding() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(6543); + assertEquals("2 ساعت پیش", t.format(localDateTime)); + } + + @Test + public void testPreciseInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); // might be more because of milliseconds between date capturing and result + // calculation + assertEquals(5, timeUnitQuantities.get(0).getQuantity()); + assertEquals(10, timeUnitQuantities.get(1).getQuantity()); + } + + @Test + public void testPreciseInThePast() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); // might be more because of milliseconds between date capturing and result + // calculation + assertEquals(-5, timeUnitQuantities.get(0).getQuantity()); + assertTrue(-10 == timeUnitQuantities.get(1).getQuantity() || -9 == timeUnitQuantities.get(1).getQuantity()); + } + + @Test + public void testFormattingDurationListInThePast() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 روز 15 ساعت 38 دقیقه پیش", t.format(timeUnitQuantities)); + } + + @Test + public void testFormattingDurationListInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(0); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + + 1000 * 60 * 38), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 روز 15 ساعت 38 دقیقه دیگر", t.format(timeUnitQuantities)); + } + + @Test + public void testSetLocale() throws Exception { + PrettyTime t = new PrettyTime(315569259747L * 3L); + assertEquals("3 دهه پیش", t.format((0))); + } + + @After + public void tearDown() throws Exception { + Locale.setDefault(defaultLocale); + } +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FI_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FI_Test.java index 7fd0d44..f708a24 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FI_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FI_Test.java @@ -1,5 +1,6 @@ package org.xbib.time.pretty; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.xbib.time.pretty.units.JustNow; @@ -14,11 +15,16 @@ import java.util.Locale; import static org.junit.Assert.assertEquals; public class PrettyTimeI18n_FI_Test { + + private Locale defaultLocale; + private Locale locale; @Before public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); locale = new Locale("fi"); + Locale.setDefault(locale); } @Test @@ -333,4 +339,9 @@ public class PrettyTimeI18n_FI_Test { } return t; } + + @After + public void tearDown() throws Exception { + Locale.setDefault(defaultLocale); + } } diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FR_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FR_Test.java index 73a5927..cdfe3c9 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FR_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FR_Test.java @@ -12,37 +12,34 @@ import java.util.Locale; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -/** - * All the tests for PrettyTime. - */ public class PrettyTimeI18n_FR_Test { - // Stores current locale so that it can be restored + private Locale defaultLocale; + private Locale locale; - // Method setUp() is called automatically before every test method @Before public void setUp() throws Exception { - locale = Locale.getDefault(); + defaultLocale = Locale.getDefault(); + locale = Locale.FRENCH; + Locale.setDefault(locale); } @Test public void testPrettyTimeFRENCH() { // The FRENCH resource bundle should be used - PrettyTime p = new PrettyTime(Locale.FRENCH); + PrettyTime p = new PrettyTime(locale); assertEquals("à l'instant", p.format(LocalDateTime.now())); } @Test public void testPrettyTimeFRENCHCenturies() { - PrettyTime p = new PrettyTime((3155692597470L * 3L), Locale.FRENCH); + PrettyTime p = new PrettyTime((3155692597470L * 3L), locale); assertEquals(p.format(0), "il y a 3 siècles"); } @Test public void testPrettyTimeViaDefaultLocaleFRENCH() { - // The FRENCH resource bundle should be used - Locale.setDefault(Locale.FRENCH); PrettyTime p = new PrettyTime(); assertEquals(p.format(LocalDateTime.now()), "à l'instant"); } @@ -50,7 +47,7 @@ public class PrettyTimeI18n_FR_Test { @Test public void testPrettyTimeFRENCHLocale() { long t = 1L; - PrettyTime p = new PrettyTime((0), Locale.FRENCH); + PrettyTime p = new PrettyTime((0), locale); while (1000L * 60L * 60L * 24L * 365L * 1000000L > t) { LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); assertTrue(p.format(localDateTime).startsWith("dans") || p.format(localDateTime).startsWith("à l'instant")); @@ -58,10 +55,9 @@ public class PrettyTimeI18n_FR_Test { } } - // Method tearDown() is called automatically after every test method @After public void tearDown() throws Exception { - Locale.setDefault(locale); + Locale.setDefault(defaultLocale); } } diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_IT_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_IT_Test.java index 24df2ff..2e96182 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_IT_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_IT_Test.java @@ -1,5 +1,6 @@ package org.xbib.time.pretty; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.xbib.time.pretty.units.JustNow; @@ -14,11 +15,16 @@ import java.util.Locale; import static org.junit.Assert.assertEquals; public class PrettyTimeI18n_IT_Test { + + private Locale defaultLocale; + private Locale locale; @Before public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); locale = new Locale("it"); + Locale.setDefault(locale); } @Test @@ -333,4 +339,9 @@ public class PrettyTimeI18n_IT_Test { } return t; } + + @After + public void tearDown() throws Exception { + Locale.setDefault(defaultLocale); + } } diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_KO_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_KO_Test.java index 8e1607e..e5d51cc 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_KO_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_KO_Test.java @@ -15,13 +15,15 @@ import static org.junit.Assert.assertTrue; public class PrettyTimeI18n_KO_Test { + private Locale defaultLocale; + private Locale locale; - // Method setUp() is called automatically before every test method @Before public void setUp() throws Exception { - locale = Locale.getDefault(); - Locale.setDefault(Locale.KOREA); + defaultLocale = Locale.getDefault(); + locale = Locale.KOREA; + Locale.setDefault(locale); } @Test @@ -227,10 +229,8 @@ public class PrettyTimeI18n_KO_Test { assertEquals("vor 3 Jahrzehnten", t.format((0))); } - // Method tearDown() is called automatically after every test method @After public void tearDown() throws Exception { - Locale.setDefault(locale); + Locale.setDefault(defaultLocale); } - } diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_NL_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_NL_Test.java index 86f19bc..b25f2d5 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_NL_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_NL_Test.java @@ -1,5 +1,6 @@ package org.xbib.time.pretty; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -11,11 +12,16 @@ import java.util.Locale; import static org.junit.Assert.assertEquals; public class PrettyTimeI18n_NL_Test { + + private Locale defaultLocale; + private Locale locale; @Before public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); locale = new Locale("nl"); + Locale.setDefault(locale); } @Test @@ -159,4 +165,9 @@ public class PrettyTimeI18n_NL_Test { PrettyTime t = new PrettyTime((3155692597470L * 3L), locale); assertEquals("3 eeuwen geleden", t.format((0))); } -} \ No newline at end of file + + @After + public void tearDown() throws Exception { + Locale.setDefault(defaultLocale); + } +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_NO_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_NO_Test.java index 364e9fe..68fd08e 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_NO_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_NO_Test.java @@ -1,163 +1,173 @@ -package org.xbib.time.pretty; - -import org.junit.Before; -import org.junit.Test; - -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Locale; - -import static org.junit.Assert.assertEquals; - -public class PrettyTimeI18n_NO_Test { - - private Locale locale; - - @Before - public void setUp() throws Exception { - locale = new Locale("no"); - } - - @Test - public void testPrettyTime() { - PrettyTime p = new PrettyTime(locale); - assertEquals("straks", p.format(LocalDateTime.now())); - } - - @Test - public void testPrettyTimeCenturies() { - PrettyTime p = new PrettyTime((3155692597470L * 3L), locale); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - assertEquals("3 århundre siden", p.format(localDateTime)); - - p = new PrettyTime(0, locale); - localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(3155692597470L * 3L), ZoneId.systemDefault()); - assertEquals("3 århundre fra nå", p.format(localDateTime)); - } - - @Test - public void testCeilingInterval() throws Exception { - LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); - LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); - PrettyTime t = new PrettyTime(ref, locale); - assertEquals("1 måned siden", t.format(then)); - } - - @Test - public void testRightNow() throws Exception { - PrettyTime t = new PrettyTime(locale); - assertEquals("straks", t.format(LocalDateTime.now())); - } - - @Test - public void testRightNowVariance() throws Exception { - PrettyTime t = new PrettyTime((0), locale); - assertEquals("straks", t.format((600))); - } - - @Test - public void testMinutesFromNow() throws Exception { - PrettyTime t = new PrettyTime((0), locale); - assertEquals("om 12 minutter", t.format((1000 * 60 * 12))); - } - - @Test - public void testHoursFromNow() throws Exception { - PrettyTime t = new PrettyTime((0), locale); - assertEquals("om 3 timer", t.format((1000 * 60 * 60 * 3))); - } - - @Test - public void testDaysFromNow() throws Exception { - PrettyTime t = new PrettyTime((0), locale); - assertEquals("om 3 dager", t.format((1000 * 60 * 60 * 24 * 3))); - } - - @Test - public void testWeeksFromNow() throws Exception { - PrettyTime t = new PrettyTime((0), locale); - assertEquals("om 3 uker", t.format((1000 * 60 * 60 * 24 * 7 * 3))); - } - - @Test - public void testMonthsFromNow() throws Exception { - PrettyTime t = new PrettyTime((0), locale); - assertEquals("om 3 måneder", t.format((2629743830L * 3L))); - } - - @Test - public void testYearsFromNow() throws Exception { - PrettyTime t = new PrettyTime((0), locale); - assertEquals("om 3 år", t.format((2629743830L * 12L * 3L))); - } - - @Test - public void testDecadesFromNow() throws Exception { - PrettyTime t = new PrettyTime((0), locale); - assertEquals("3 tiår fra nå", t.format((315569259747L * 3L))); - } - - @Test - public void testCenturiesFromNow() throws Exception { - PrettyTime t = new PrettyTime((0), locale); - assertEquals("3 århundre fra nå", t.format((3155692597470L * 3L))); - } - - /* - * Past - */ - @Test - public void testMomentsAgo() throws Exception { - PrettyTime t = new PrettyTime((6000), locale); - assertEquals("et øyeblikk siden", t.format((0))); - } - - @Test - public void testMinutesAgo() throws Exception { - PrettyTime t = new PrettyTime((1000 * 60 * 12), locale); - assertEquals("12 minutter siden", t.format((0))); - } - - @Test - public void testHoursAgo() throws Exception { - PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3), locale); - assertEquals("3 timer siden", t.format((0))); - } - - @Test - public void testDaysAgo() throws Exception { - PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3), locale); - assertEquals("3 dager siden", t.format((0))); - } - - @Test - public void testWeeksAgo() throws Exception { - PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3), locale); - assertEquals("3 uker siden", t.format((0))); - } - - @Test - public void testMonthsAgo() throws Exception { - PrettyTime t = new PrettyTime((2629743830L * 3L), locale); - assertEquals("3 måneder siden", t.format((0))); - } - - @Test - public void testYearsAgo() throws Exception { - PrettyTime t = new PrettyTime((2629743830L * 12L * 3L), locale); - assertEquals("3 år siden", t.format((0))); - } - - @Test - public void testDecadesAgo() throws Exception { - PrettyTime t = new PrettyTime((315569259747L * 3L), locale); - assertEquals("3 tiår siden", t.format((0))); - } - - @Test - public void testCenturiesAgo() throws Exception { - PrettyTime t = new PrettyTime((3155692597470L * 3L), locale); - assertEquals("3 århundre siden", t.format((0))); - } -} \ No newline at end of file +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeI18n_NO_Test { + + private Locale defaultLocale; + + private Locale locale; + + @Before + public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); + locale = new Locale("no"); + Locale.setDefault(locale); + } + + @Test + public void testPrettyTime() { + PrettyTime p = new PrettyTime(locale); + assertEquals("straks", p.format(LocalDateTime.now())); + } + + @Test + public void testPrettyTimeCenturies() { + PrettyTime p = new PrettyTime((3155692597470L * 3L), locale); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + assertEquals("3 århundre siden", p.format(localDateTime)); + + p = new PrettyTime(0, locale); + localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(3155692597470L * 3L), ZoneId.systemDefault()); + assertEquals("3 århundre fra nå", p.format(localDateTime)); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref, locale); + assertEquals("1 måned siden", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(locale); + assertEquals("straks", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("straks", t.format((600))); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 12 minutter", t.format((1000 * 60 * 12))); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 timer", t.format((1000 * 60 * 60 * 3))); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 dager", t.format((1000 * 60 * 60 * 24 * 3))); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 uker", t.format((1000 * 60 * 60 * 24 * 7 * 3))); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 måneder", t.format((2629743830L * 3L))); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 år", t.format((2629743830L * 12L * 3L))); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 tiår fra nå", t.format((315569259747L * 3L))); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 århundre fra nå", t.format((3155692597470L * 3L))); + } + + /* + * Past + */ + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime((6000), locale); + assertEquals("et øyeblikk siden", t.format((0))); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 12), locale); + assertEquals("12 minutter siden", t.format((0))); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3), locale); + assertEquals("3 timer siden", t.format((0))); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3), locale); + assertEquals("3 dager siden", t.format((0))); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3), locale); + assertEquals("3 uker siden", t.format((0))); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 3L), locale); + assertEquals("3 måneder siden", t.format((0))); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 12L * 3L), locale); + assertEquals("3 år siden", t.format((0))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 3L), locale); + assertEquals("3 tiår siden", t.format((0))); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime((3155692597470L * 3L), locale); + assertEquals("3 århundre siden", t.format((0))); + } + + @After + public void tearDown() throws Exception { + Locale.setDefault(defaultLocale); + } +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_RU_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_RU_Test.java index 5a912ac..aa595cc 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_RU_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_RU_Test.java @@ -13,10 +13,13 @@ import static org.junit.Assert.assertEquals; public class PrettyTimeI18n_RU_Test { + private Locale defaultLocale; + private Locale locale; @Before public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); locale = new Locale("ru"); Locale.setDefault(locale); } @@ -163,9 +166,8 @@ public class PrettyTimeI18n_RU_Test { assertEquals("3 века назад", t.format((0))); } - @After public void tearDown() throws Exception { - Locale.setDefault(locale); + Locale.setDefault(defaultLocale); } } diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_SV_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_SV_Test.java index a833e4d..0a5d0a1 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_SV_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_SV_Test.java @@ -1,5 +1,6 @@ package org.xbib.time.pretty; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -12,11 +13,15 @@ import static org.junit.Assert.assertEquals; public class PrettyTimeI18n_SV_Test { + private Locale defaultLocale; + private Locale locale; @Before public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); locale = new Locale("sv"); + Locale.setDefault(locale); } @Test @@ -161,4 +166,9 @@ public class PrettyTimeI18n_SV_Test { PrettyTime t = new PrettyTime((3155692597470L * 3L), locale); assertEquals("3 århundraden sedan", t.format((0))); } + + @After + public void tearDown() throws Exception { + Locale.setDefault(defaultLocale); + } } diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_Test.java index b9bfe7b..6726925 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_Test.java @@ -1,130 +1,128 @@ -package org.xbib.time.pretty; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Locale; - -import static org.junit.Assert.assertEquals; - -public class PrettyTimeI18n_Test { - - // Stores current locale so that it can be restored - private Locale locale; - - // Method setUp() is called automatically before every test method - @Before - public void setUp() throws Exception { - locale = Locale.getDefault(); - } - - @Test - public void testPrettyTimeDefault() { - // The default resource bundle should be used - PrettyTime p = new PrettyTime(0, Locale.ROOT); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1), ZoneId.systemDefault()); - assertEquals("moments from now", p.format(localDateTime)); - } - - @Test - public void testPrettyTimeGerman() { - // The German resource bundle should be used - PrettyTime p = new PrettyTime(Locale.GERMAN); - LocalDateTime ref = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - p.setReference(ref); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1), ZoneId.systemDefault()); - assertEquals("Jetzt", p.format(localDateTime)); - } - - @Test - public void testPrettyTimeSpanish() { - // The Spanish resource bundle should be used - PrettyTime p = new PrettyTime(new Locale("es")); - assertEquals("en un instante", p.format(LocalDateTime.now())); - } - - @Test - public void testPrettyTimeDefaultCenturies() { - // The default resource bundle should be used - PrettyTime p = new PrettyTime((3155692597470L * 3L), Locale.ROOT); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - assertEquals("3 centuries ago", p.format(localDateTime)); - } - - @Test - public void testPrettyTimeGermanCenturies() { - // The default resource bundle should be used - PrettyTime p = new PrettyTime((3155692597470L * 3L), Locale.GERMAN); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - assertEquals("vor 3 Jahrhunderten", p.format(localDateTime)); - } - - @Test - public void testPrettyTimeViaDefaultLocaleDefault() { - // The default resource bundle should be used - Locale.setDefault(Locale.ROOT); - PrettyTime p = new PrettyTime((0)); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1), ZoneId.systemDefault()); - assertEquals("moments from now", p.format(localDateTime)); - } - - @Test - public void testPrettyTimeViaDefaultLocaleGerman() { - // The German resource bundle should be used - Locale.setDefault(Locale.GERMAN); - PrettyTime p = new PrettyTime((0)); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1), ZoneId.systemDefault()); - assertEquals("Jetzt", p.format(localDateTime)); - } - - @Test - public void testPrettyTimeViaDefaultLocaleDefaultCenturies() { - // The default resource bundle should be used - Locale.setDefault(Locale.ROOT); - PrettyTime p = new PrettyTime((3155692597470L * 3L)); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - assertEquals("3 centuries ago", p.format(localDateTime)); - } - - @Test - public void testPrettyTimeViaDefaultLocaleGermanCenturies() { - // The default resource bundle should be used - Locale.setDefault(Locale.GERMAN); - PrettyTime p = new PrettyTime((3155692597470L * 3L)); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - assertEquals("vor 3 Jahrhunderten", p.format(localDateTime)); - } - - @Test - public void testPrettyTimeRootLocale() { - long t = 1L; - PrettyTime p = new PrettyTime(0, Locale.ROOT); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - while (1000L * 60L * 60L * 24L * 365L * 1000000L > t) { - assertEquals(p.format(localDateTime).endsWith("now"), true); - t *= 2L; - } - } - - @Test - public void testPrettyTimeGermanLocale() { - long t = 1L; - PrettyTime p = new PrettyTime(0, Locale.GERMAN); - LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - while (1000L * 60L * 60L * 24L * 365L * 1000000L > t) { - assertEquals(p.format(localDateTime).startsWith("in") || p.format(localDateTime).startsWith("Jetzt"), true); - t *= 2L; - } - } - - // Method tearDown() is called automatically after every test method - @After - public void tearDown() throws Exception { - Locale.setDefault(locale); - } - -} +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PrettyTimeI18n_Test { + + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = Locale.getDefault(); + } + + @Test + public void testPrettyTimeDefault() { + // The default resource bundle should be used + PrettyTime p = new PrettyTime(0, Locale.ROOT); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1), ZoneId.systemDefault()); + assertEquals("moments from now", p.format(localDateTime)); + } + + @Test + public void testPrettyTimeGerman() { + // The German resource bundle should be used + PrettyTime p = new PrettyTime(Locale.GERMAN); + LocalDateTime ref = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + p.setReference(ref); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1), ZoneId.systemDefault()); + assertEquals("Jetzt", p.format(localDateTime)); + } + + @Test + public void testPrettyTimeSpanish() { + // The Spanish resource bundle should be used + PrettyTime p = new PrettyTime(new Locale("es")); + assertEquals("en un instante", p.format(LocalDateTime.now())); + } + + @Test + public void testPrettyTimeDefaultCenturies() { + // The default resource bundle should be used + PrettyTime p = new PrettyTime((3155692597470L * 3L), Locale.ROOT); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + assertEquals("3 centuries ago", p.format(localDateTime)); + } + + @Test + public void testPrettyTimeGermanCenturies() { + // The default resource bundle should be used + PrettyTime p = new PrettyTime((3155692597470L * 3L), Locale.GERMAN); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + assertEquals("vor 3 Jahrhunderten", p.format(localDateTime)); + } + + @Test + public void testPrettyTimeViaDefaultLocaleDefault() { + // The default resource bundle should be used + Locale.setDefault(Locale.ROOT); + PrettyTime p = new PrettyTime((0)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1), ZoneId.systemDefault()); + assertEquals("moments from now", p.format(localDateTime)); + } + + @Test + public void testPrettyTimeViaDefaultLocaleGerman() { + // The German resource bundle should be used + Locale.setDefault(Locale.GERMAN); + PrettyTime p = new PrettyTime((0)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1), ZoneId.systemDefault()); + assertEquals("Jetzt", p.format(localDateTime)); + } + + @Test + public void testPrettyTimeViaDefaultLocaleDefaultCenturies() { + // The default resource bundle should be used + Locale.setDefault(Locale.ROOT); + PrettyTime p = new PrettyTime((3155692597470L * 3L)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + assertEquals("3 centuries ago", p.format(localDateTime)); + } + + @Test + public void testPrettyTimeViaDefaultLocaleGermanCenturies() { + // The default resource bundle should be used + Locale.setDefault(Locale.GERMAN); + PrettyTime p = new PrettyTime((3155692597470L * 3L)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + assertEquals("vor 3 Jahrhunderten", p.format(localDateTime)); + } + + @Test + public void testPrettyTimeRootLocale() { + long t = 1L; + PrettyTime p = new PrettyTime(0, Locale.ROOT); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + while (1000L * 60L * 60L * 24L * 365L * 1000000L > t) { + assertTrue(p.format(localDateTime).endsWith("now")); + t *= 2L; + } + } + + @Test + public void testPrettyTimeGermanLocale() { + long t = 1L; + PrettyTime p = new PrettyTime(0, Locale.GERMAN); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + while (1000L * 60L * 60L * 24L * 365L * 1000000L > t) { + assertTrue(p.format(localDateTime).startsWith("in") || p.format(localDateTime).startsWith("Jetzt")); + t *= 2L; + } + } + + @After + public void tearDown() throws Exception { + Locale.setDefault(locale); + } + +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_UA_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_UA_Test.java index babd14b..0292d6d 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_UA_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_UA_Test.java @@ -13,10 +13,13 @@ import static org.junit.Assert.assertEquals; public class PrettyTimeI18n_UA_Test { + private Locale defaultLocale; + private Locale locale; @Before public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); locale = new Locale("ua"); Locale.setDefault(locale); } @@ -189,6 +192,6 @@ public class PrettyTimeI18n_UA_Test { @After public void tearDown() throws Exception { - Locale.setDefault(locale); + Locale.setDefault(defaultLocale); } } diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_hi_IN_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_hi_IN_Test.java index 3629bd1..849bcad 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_hi_IN_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_hi_IN_Test.java @@ -15,10 +15,13 @@ import static org.junit.Assert.assertTrue; public class PrettyTimeI18n_hi_IN_Test { + private Locale defaultLocale; + private Locale locale; @Before public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); locale = new Locale("hi", "IN"); Locale.setDefault(locale); } @@ -27,7 +30,7 @@ public class PrettyTimeI18n_hi_IN_Test { public void testLocaleISOCorrectness() { assertEquals("hi", this.locale.getLanguage()); assertEquals("IN", this.locale.getCountry()); - assertEquals("हिंदी", this.locale.getDisplayLanguage()); + assertEquals("हिन्दी", this.locale.getDisplayLanguage()); assertEquals("भारत", this.locale.getDisplayCountry()); } @@ -243,6 +246,6 @@ public class PrettyTimeI18n_hi_IN_Test { @After public void tearDown() throws Exception { - Locale.setDefault(Locale.ENGLISH); + Locale.setDefault(defaultLocale); } } diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_in_ID_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_in_ID_Test.java index 0f13192..624f820 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_in_ID_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_in_ID_Test.java @@ -15,10 +15,13 @@ import static org.junit.Assert.assertTrue; public class PrettyTimeI18n_in_ID_Test { + private Locale defaultLocale; + private Locale locale; @Before public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); locale = new Locale("in", "ID"); Locale.setDefault(locale); } @@ -242,6 +245,6 @@ public class PrettyTimeI18n_in_ID_Test { @After public void tearDown() throws Exception { - Locale.setDefault(Locale.ENGLISH); + Locale.setDefault(defaultLocale); } } diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_zh_TW_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_zh_TW_Test.java index dd9e14b..176c215 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_zh_TW_Test.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_zh_TW_Test.java @@ -11,12 +11,15 @@ import static org.junit.Assert.assertEquals; public class PrettyTimeI18n_zh_TW_Test { + private Locale defaultLocale; + private Locale locale; @Before public void setUp() throws Exception { + defaultLocale = Locale.getDefault(); locale = Locale.TRADITIONAL_CHINESE; - Locale.setDefault(Locale.TRADITIONAL_CHINESE); + Locale.setDefault(locale); } @Test @@ -185,6 +188,6 @@ public class PrettyTimeI18n_zh_TW_Test { @After public void tearDown() throws Exception { - Locale.setDefault(locale); + Locale.setDefault(defaultLocale); } } diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeLocaleFallbackTest.java b/src/test/java/org/xbib/time/pretty/PrettyTimeLocaleFallbackTest.java index 752bc45..eb581e6 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeLocaleFallbackTest.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeLocaleFallbackTest.java @@ -11,13 +11,11 @@ import static org.junit.Assert.assertEquals; public class PrettyTimeLocaleFallbackTest { - // Stores current locale so that it can be restored - private Locale locale; + private Locale defaultLocale; - // Method setUp() is called automatically before every test method @Before public void setUp() throws Exception { - locale = Locale.getDefault(); + defaultLocale = Locale.getDefault(); Locale.setDefault(new Locale("Foo", "Bar")); } @@ -32,7 +30,7 @@ public class PrettyTimeLocaleFallbackTest { @After public void tearDown() throws Exception { - Locale.setDefault(locale); + Locale.setDefault(defaultLocale); } } diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeTest.java b/src/test/java/org/xbib/time/pretty/PrettyTimeTest.java index f30520c..65ca363 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeTest.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeTest.java @@ -15,11 +15,11 @@ import static org.junit.Assert.assertTrue; public class PrettyTimeTest { - private Locale locale; + private Locale defaultLocale; @Before public void setUp() throws Exception { - locale = Locale.getDefault(); + defaultLocale = Locale.getDefault(); Locale.setDefault(Locale.ROOT); } @@ -236,7 +236,6 @@ public class PrettyTimeTest { @After public void tearDown() throws Exception { - Locale.setDefault(locale); + Locale.setDefault(defaultLocale); } - } diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeUnitConfigurationTest.java b/src/test/java/org/xbib/time/pretty/PrettyTimeUnitConfigurationTest.java index 75f3379..d11884d 100644 --- a/src/test/java/org/xbib/time/pretty/PrettyTimeUnitConfigurationTest.java +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeUnitConfigurationTest.java @@ -17,11 +17,11 @@ import static org.junit.Assert.assertEquals; public class PrettyTimeUnitConfigurationTest { - private Locale locale; + private Locale defaultLocale; @Before public void setUp() throws Exception { - locale = Locale.getDefault(); + defaultLocale = Locale.getDefault(); Locale.setDefault(Locale.ROOT); } @@ -53,7 +53,6 @@ public class PrettyTimeUnitConfigurationTest { @After public void tearDown() throws Exception { - Locale.setDefault(locale); + Locale.setDefault(defaultLocale); } - } diff --git a/src/test/java/org/xbib/time/pretty/SimpleTimeFormatTest.java b/src/test/java/org/xbib/time/pretty/SimpleTimeFormatTest.java index 0d46260..4b1736a 100644 --- a/src/test/java/org/xbib/time/pretty/SimpleTimeFormatTest.java +++ b/src/test/java/org/xbib/time/pretty/SimpleTimeFormatTest.java @@ -13,11 +13,11 @@ import static org.junit.Assert.assertEquals; public class SimpleTimeFormatTest { - private Locale locale; + private Locale defaultLocale; @Before public void setUp() throws Exception { - locale = Locale.getDefault(); + defaultLocale = Locale.getDefault(); Locale.setDefault(Locale.ROOT); } @@ -46,7 +46,7 @@ public class SimpleTimeFormatTest { @After public void tearDown() throws Exception { - Locale.setDefault(locale); + Locale.setDefault(defaultLocale); } } diff --git a/src/test/java/org/xbib/time/pretty/i18n/SimpleTimeFormatTimeQuantifiedNameTest.java b/src/test/java/org/xbib/time/pretty/i18n/SimpleTimeFormatTimeQuantifiedNameTest.java index 8ec02cc..f5f2467 100644 --- a/src/test/java/org/xbib/time/pretty/i18n/SimpleTimeFormatTimeQuantifiedNameTest.java +++ b/src/test/java/org/xbib/time/pretty/i18n/SimpleTimeFormatTimeQuantifiedNameTest.java @@ -12,12 +12,16 @@ import java.time.ZoneId; import java.util.Locale; public class SimpleTimeFormatTimeQuantifiedNameTest { + + private Locale defaultLocale; + private Locale locale; @Before public void setUp() throws Exception { - locale = Locale.getDefault(); - Locale.setDefault(new Locale("yy")); + defaultLocale = Locale.getDefault(); + locale = new Locale("yy"); + Locale.setDefault(locale); } @Test @@ -76,10 +80,8 @@ public class SimpleTimeFormatTimeQuantifiedNameTest { Assert.assertEquals("1 hour ago", p.format(0)); } - // Method tearDown() is called automatically after every test method @After public void tearDown() throws Exception { - Locale.setDefault(locale); + Locale.setDefault(defaultLocale); } - } diff --git a/src/test/java/org/xbib/time/pretty/i18n/TimeFormatProviderTest.java b/src/test/java/org/xbib/time/pretty/i18n/TimeFormatProviderTest.java index 34e5f7f..a4c8c76 100644 --- a/src/test/java/org/xbib/time/pretty/i18n/TimeFormatProviderTest.java +++ b/src/test/java/org/xbib/time/pretty/i18n/TimeFormatProviderTest.java @@ -9,21 +9,26 @@ import java.util.Locale; import java.util.ResourceBundle; public class TimeFormatProviderTest { + @Test public void test() { + Locale defaultLocale = Locale.getDefault(); Locale locale = new Locale("xx"); Locale.setDefault(locale); ResourceBundle bundle = ResourceBundle.getBundle(Resources.class.getName(), locale); Assert.assertTrue(bundle instanceof TimeFormatProvider); + Locale.setDefault(defaultLocale); } @Test public void testFormatFromDirectFormatOverride() throws Exception { + Locale defaultLocale = Locale.getDefault(); Locale locale = new Locale("xx"); Locale.setDefault(locale); PrettyTime prettyTime = new PrettyTime(locale); String result = prettyTime.format(System.currentTimeMillis() + 1000 * 60 * 6); Assert.assertEquals("6 minutes from now", result); + Locale.setDefault(defaultLocale); } } diff --git a/src/test/java/org/xbib/time/schedule/CompareBehaviorToQuartzTest.java b/src/test/java/org/xbib/time/schedule/CompareBehaviorToQuartzTest.java new file mode 100644 index 0000000..e295cbc --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/CompareBehaviorToQuartzTest.java @@ -0,0 +1,261 @@ +package org.xbib.time.schedule; + +import static org.junit.Assert.assertTrue; +import static org.xbib.time.schedule.DateTimes.toDates; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.DayOfWeek; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAdjusters; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class CompareBehaviorToQuartzTest { + + static final CronExpression.Parser quartzLike = CronExpression.parser() + .withSecondsField(true) + .withOneBasedDayOfWeek(true) + .allowBothDayFields(false); + private static final String timeFormatString = "s m H d M E yyyy"; + private static final DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern(timeFormatString); + private final DateFormat dateFormat = new SimpleDateFormat(timeFormatString); + + protected String string; + private Times expected; + + @Before + public void before() { + expected = new Times(); + } + + @Test + public void complex() throws Exception { + string = "0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2010"; + expected.seconds.with(0); + expected.minutes.with(52).withRange(3, 39); + expected.months.with(1, 3, 9); + expected.daysOfWeek.withRange(1, 5); + expected.years.withRange(2002, 2010); + check(); + } + + @Test + public void at_noon_every_day() throws Exception { + string = "0 0 12 * * ?"; + expected.seconds.with(0); + expected.minutes.with(0); + expected.hours.with(12); + check(); + } + + @Test + public void at_10_15am_every_day1() throws Exception { + string = "0 15 10 ? * *"; + expected.seconds.with(0); + expected.minutes.with(15); + expected.hours.with(10); + check(); + } + + @Test + public void at_10_15am_every_day2() throws Exception { + string = "0 15 10 * * ?"; + expected.seconds.with(0); + expected.minutes.with(15); + expected.hours.with(10); + check(); + } + + @Test + public void at_10_15am_every_day3() throws Exception { + string = "0 15 10 * * ? *"; + expected.seconds.with(0); + expected.minutes.with(15); + expected.hours.with(10); + check(); + } + + + @Test + public void at_10_15am_every_day_in_2005() throws Exception { + string = "0 15 10 * * ? 2005"; + expected.seconds.with(0); + expected.minutes.with(15); + expected.hours.with(10); + expected.years.with(2005); + check(); + } + + @Test + public void every_minute_of_2pm() throws Exception { + string = "0 * 14 * * ?"; + expected.seconds.with(0); + expected.hours.with(14); + check(); + } + + @Test + public void every_5_minutes_of_2pm() throws Exception { + string = "0 0/5 14 * * ?"; + expected.seconds.with(0); + expected.minutes.withRange(0, 59, 5); + expected.hours.with(14); + check(); + } + + @Test + public void every_5_minutes_of_2pm_and_6pm() throws Exception { + string = "0 0/5 14,18 * * ?"; + expected.seconds.with(0); + expected.minutes.withRange(0, 59, 5); + expected.hours.with(14, 18); + check(); + } + + @Test + public void first_5_minutes_of_2pm() throws Exception { + string = "0 0-5 14 * * ?"; + expected.seconds.with(0); + expected.minutes.withRange(0, 5); + expected.hours.with(14); + check(); + } + + @Test + public void at_2_10pm_and_2_44pm_every_wednesday_in_march() throws Exception { + string = "0 10,44 14 ? 3 WED"; + expected.seconds.with(0); + expected.minutes.with(10, 44); + expected.hours.with(14); + expected.months.with(3); + expected.daysOfWeek.with(3); + check(); + } + + @Test + public void at_10_15am_every_weekday() throws Exception { + string = "0 15 10 ? * MON-FRI"; + expected.seconds.with(0); + expected.minutes.with(15); + expected.hours.with(10); + expected.daysOfWeek.withRange(1, 5); + check(); + } + + @Test + public void at_10_15am_on_the_15th_of_every_month() throws Exception { + string = "0 15 10 15 * ?"; + expected.seconds.with(0); + expected.minutes.with(15); + expected.hours.with(10); + expected.daysOfMonth.with(15); + check(); + } + + @Test + public void at_10_15am_on_the_last_day_of_every_month() throws Exception { + string = "0 15 10 L * ?"; + List times = new ArrayList<>(); + ZonedDateTime t = ZonedDateTime.now().withDayOfYear(1).truncatedTo(ChronoUnit.DAYS).plusHours(10).plusMinutes(15); + int year = t.getYear(); + while (t.getYear() == year) { + times.add(t.with(TemporalAdjusters.lastDayOfMonth())); + t = t.plusMonths(1); + } + check(times); + } + + @Test + public void at_10_15am_on_the_last_friday_of_every_month() throws Exception { + string = "0 15 10 ? * 6L"; + List times = new ArrayList<>(); + ZonedDateTime t = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).plusHours(10).plusMinutes(15); + int year = t.getYear(); + while (t.getYear() == year) { + times.add(DateTimes.lastOfMonth(t, DayOfWeek.FRIDAY)); + t = t.plusMonths(1); + } + check(times); + } + + @Test + public void at_10_15am_on_the_last_friday_of_every_month_during_2002_through_2005() throws Exception { + string = "0 15 10 ? * 6L 2002-2005"; + List times = new ArrayList<>(); + for (int year = 2002; year <= 2005; year++) { + ZonedDateTime t = ZonedDateTime.now().withYear(year).truncatedTo(ChronoUnit.DAYS).plusHours(10).plusMinutes(15); + while (t.getYear() == year) { + times.add(DateTimes.lastOfMonth(t, DayOfWeek.FRIDAY)); + t = t.plusMonths(1); + } + } + check(times); + } + + + @Test + @Ignore + // TODO let's see if we can make this more reliably faster than the respective quartz run + public void at_10_15am_on_the_third_friday_of_every_month() throws Exception { + string = "0 15 10 ? * 6#3"; + List times = new ArrayList<>(); + ZonedDateTime t = ZonedDateTime.now().withDayOfYear(1).truncatedTo(ChronoUnit.DAYS).plusHours(10).plusMinutes(15); + int year = t.getYear(); + while (t.getYear() == year) { + times.add(DateTimes.nthOfMonth(t, DayOfWeek.FRIDAY, 3)); + t = t.plusMonths(1); + } + check(times); + } + + @Test + public void at_noon_every_5_days_every_month_starting_on_the_first_day_of_the_month() throws Exception { + string = "0 0 12 1/5 * ?"; + expected.seconds.with(0); + expected.minutes.with(0); + expected.hours.with(12); + expected.daysOfMonth.withRange(1, 31, 5); + check(); + } + + @Test + public void november_11th_at_11_11am() throws Exception { + string = "0 11 11 11 11 ?"; + expected.seconds.with(0); + expected.minutes.with(11); + expected.hours.with(11); + expected.daysOfMonth.with(11); + expected.months.with(11); + check(); + } + + private void check() throws ParseException { + check(expected.dateTimes()); + } + + protected void check(Iterable times) throws ParseException { + checkLocalImplementation(times); + checkQuartzImplementation(toDates(times)); + } + + private void checkQuartzImplementation(Iterable times) throws ParseException { + org.quartz.CronExpression quartz = new org.quartz.CronExpression(string); + for (Date time : times) { + assertTrue(dateFormat.format(time).toUpperCase() + " doesn't match expression: " + string, quartz.isSatisfiedBy(time)); + } + } + + private void checkLocalImplementation(Iterable times) { + CronExpression expr = quartzLike.parse(string); + for (ZonedDateTime time : times) { + assertTrue(time.format(dateTimeFormat).toUpperCase() + " doesn't match expression: " + string, expr.matches(time)); + } + } +} diff --git a/src/test/java/org/xbib/time/schedule/CompareSizeToQuartzTest.java b/src/test/java/org/xbib/time/schedule/CompareSizeToQuartzTest.java new file mode 100644 index 0000000..9e63ab3 --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/CompareSizeToQuartzTest.java @@ -0,0 +1,123 @@ +package org.xbib.time.schedule; + +import static org.junit.Assert.assertTrue; +import com.google.caliper.memory.ObjectGraphMeasurer; +import org.junit.Test; + +public class CompareSizeToQuartzTest { + + private static final CronExpression.Parser quartzLike = CronExpression.parser() + .withSecondsField(true) + .withOneBasedDayOfWeek(true) + .allowBothDayFields(false); + + @Test + public void complex() throws Exception { + check("0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2010"); + } + + @Test + public void at_noon_every_day() throws Exception { + check("0 0 12 * * ?"); + } + + @Test + public void at_10_15am_every_day1() throws Exception { + check("0 15 10 ? * *"); + } + + @Test + public void at_10_15am_every_day2() throws Exception { + check("0 15 10 * * ?"); + } + + @Test + public void at_10_15am_every_day3() throws Exception { + check("0 15 10 * * ? *"); + } + + @Test + public void at_10_15am_every_day_in_2005() throws Exception { + check("0 15 10 * * ? 2005"); + } + + @Test + public void every_minute_of_2pm() throws Exception { + check("0 * 14 * * ?"); + } + + @Test + public void every_5_minutes_of_2pm() throws Exception { + check("0 0/5 14 * * ?"); + } + + @Test + public void every_5_minutes_of_2pm_and_6pm() throws Exception { + check("0 0/5 14,18 * * ?"); + } + + @Test + public void first_5_minutes_of_2pm() throws Exception { + check("0 0-5 14 * * ?"); + } + + @Test + public void at_2_10pm_and_2_44pm_every_wednesday_in_march() throws Exception { + check("0 10,44 14 ? 3 WED"); + } + + @Test + public void at_10_15am_every_weekday() throws Exception { + check("0 15 10 ? * MON-FRI"); + } + + @Test + public void at_10_15am_on_the_15th_of_every_month() throws Exception { + check("0 15 10 15 * ?"); + } + + @Test + public void at_10_15am_on_the_last_day_of_every_month() throws Exception { + check("0 15 10 L * ?"); + } + + @Test + public void at_10_15am_on_the_last_friday_of_every_month() throws Exception { + check("0 15 10 ? * 6L"); + } + + @Test + public void at_10_15am_on_the_last_friday_of_every_month_during_2002_through_2005() throws Exception { + check("0 15 10 ? * 6L 2002-2005"); + } + + @Test + public void at_10_15am_on_the_third_friday_of_every_month() throws Exception { + check("0 15 10 ? * 6#3"); + } + + @Test + public void at_noon_every_5_days_every_month_starting_on_the_first_day_of_the_month() throws Exception { + check("0 0 12 1/5 * ?"); + } + + @Test + public void november_11th_at_11_11am() throws Exception { + check("0 11 11 11 11 ?"); + } + + private void check(String expression) throws Exception { + CronExpression local = quartzLike.parse(expression); + org.quartz.CronExpression quartz = new org.quartz.CronExpression(expression); + long localSize = ObjectSizeCalculator.getObjectSize(local); + long quartzSize = ObjectSizeCalculator.getObjectSize(quartz); + assertTrue("We have more bytes", localSize < quartzSize); + ObjectGraphMeasurer.Footprint localFoot = ObjectGraphMeasurer.measure(local); + ObjectGraphMeasurer.Footprint quartzFoot = ObjectGraphMeasurer.measure(quartz); + assertTrue("We have more references", localFoot.getAllReferences() < quartzFoot.getAllReferences()); + assertTrue("We have more non-null references", localFoot.getNonNullReferences() < quartzFoot.getNonNullReferences()); + //assertTrue("We have more null references", localFoot.getNullReferences() < quartzFoot.getNullReferences()); + assertTrue("We have more objects", localFoot.getObjects() < quartzFoot.getObjects()); + assertTrue("We have more primitives", localFoot.getPrimitives().size() < quartzFoot.getPrimitives().size()); + } +} diff --git a/src/test/java/org/xbib/time/schedule/CompareSpeedToQuartzTest.java b/src/test/java/org/xbib/time/schedule/CompareSpeedToQuartzTest.java new file mode 100644 index 0000000..abc5a94 --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/CompareSpeedToQuartzTest.java @@ -0,0 +1,66 @@ +package org.xbib.time.schedule; + +import static org.junit.Assert.assertTrue; +import com.google.common.base.Stopwatch; +import java.text.ParseException; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.concurrent.TimeUnit; + +public class CompareSpeedToQuartzTest extends CompareBehaviorToQuartzTest { + @Override + protected void check(final Iterable times) throws ParseException { + final Iterable dates = DateTimes.toDates(times); + final CronExpression local = quartzLike.parse(string); + final org.quartz.CronExpression quartz = new org.quartz.CronExpression(string); + final int trials = 25; + final Stopwatch clock = Stopwatch.createStarted(); + for (int i = 0; i < trials; i++) { + for (ZonedDateTime time : times) { + local.matches(time); + } + } + final long localNano = clock.elapsed(TimeUnit.NANOSECONDS); + clock.reset().start(); + for (int i = 0; i < trials; i++) { + for (Date date : dates) { + quartz.isSatisfiedBy(date); + } + } + final long quartzNano = clock.elapsed(TimeUnit.NANOSECONDS); + final boolean lessThanOrEqual = localNano <= quartzNano; + System.out.printf( + "%-80s %-60s local %8.2fms %6s Quartz %8.2fms\n", + nameOfTestMethod(), + string, + localNano / 1000000d, + (lessThanOrEqual ? "<=" : ">"), + quartzNano / 1000000d + ); + assertTrue( + "We took longer for expression '" + string + "'; " + localNano + " > " + quartzNano, + lessThanOrEqual + ); + } + + private String nameOfTestMethod() { + try { + throw new Exception(); + } catch (Exception e) { + String method = null; + Iterator trace = Arrays.asList(e.getStackTrace()).iterator(); + StackTraceElement element = trace.next(); + while (getClass().getName().equals(element.getClassName())) { + element = trace.next(); + } + String parentClassName = getClass().getSuperclass().getName(); + while (element.getClassName().equals(parentClassName)) { + method = element.getMethodName(); + element = trace.next(); + } + return method; + } + } +} diff --git a/src/test/java/org/xbib/time/schedule/CronExpressionTest.java b/src/test/java/org/xbib/time/schedule/CronExpressionTest.java new file mode 100644 index 0000000..a05c1e2 --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/CronExpressionTest.java @@ -0,0 +1,344 @@ +package org.xbib.time.schedule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.xbib.time.schedule.DateTimes.midnight; +import static org.xbib.time.schedule.DateTimes.nearestWeekday; +import static org.xbib.time.schedule.DateTimes.now; +import static org.xbib.time.schedule.DateTimes.nthOfMonth; +import static org.xbib.time.schedule.DateTimes.startOfHour; +import org.junit.Test; +import java.time.DayOfWeek; +import java.time.Month; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; + +public class CronExpressionTest { + + private static final CronExpression.Parser withSecondsField = CronExpression.parser().withSecondsField(true); + + private CronExpression expression; + + @Test + public void testHashCode() { + assertEquals(CronExpression.daily().hashCode(), CronExpression.parse("@daily").hashCode()); + assertEquals(CronExpression.daily().hashCode(), CronExpression.parse("@midnight").hashCode()); + assertEquals(CronExpression.hourly().hashCode(), CronExpression.parse("@hourly").hashCode()); + assertEquals(CronExpression.monthly().hashCode(), CronExpression.parse("@monthly").hashCode()); + assertEquals(CronExpression.weekly().hashCode(), CronExpression.parse("@weekly").hashCode()); + assertEquals(CronExpression.yearly().hashCode(), CronExpression.parse("@annually").hashCode()); + assertEquals(CronExpression.yearly().hashCode(), CronExpression.parse("@yearly").hashCode()); + assertEquals( + CronExpression.parse("0 0 ? * 5#3,2#2").hashCode(), + CronExpression.parse("0 0 ? * 5#3,2#2").hashCode()); + assertEquals( + withSecondsField.parse("0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2010").hashCode(), + withSecondsField.parse("0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2010").hashCode()); + } + + @Test + public void testEquals() { + assertEquals(CronExpression.daily(), CronExpression.parse("@daily")); + assertEquals(CronExpression.daily(), CronExpression.parse("@midnight")); + assertEquals(CronExpression.hourly(), CronExpression.parse("@hourly")); + assertEquals(CronExpression.monthly(), CronExpression.parse("@monthly")); + assertEquals(CronExpression.weekly(), CronExpression.parse("@weekly")); + assertEquals(CronExpression.yearly(), CronExpression.parse("@annually")); + assertEquals(CronExpression.yearly(), CronExpression.parse("@yearly")); + assertEquals( + CronExpression.parse("0 0 ? * 5#3,2#2"), + CronExpression.parse("0 0 ? * 5#3,2#2")); + assertEquals( + withSecondsField.parse("0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2010"), + withSecondsField.parse("0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2010")); + } + + @Test + public void illegalCharacter() { + try { + expression = CronExpression.parse("0 0 4X * *"); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + assertEquals("Bad character 'X' at position 5 in string: 0 0 4X * *", e.getMessage()); + } + } + + @Test + public void disallowBothDayFields() { + try { + expression = CronExpression.parser().allowBothDayFields(false).parse("0 0 1 * 5L"); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + assertEquals("Day of month and day of week may not both be specified", e.getMessage()); + } + } + + @Test + public void nearestWeekdayWithoutNumber() { + try { + expression = CronExpression.parse("0 0 W * *"); + } catch (IllegalArgumentException e) { + assertEquals("Bad character 'W' in day of month field: W", e.getMessage()); + } + } + + @Test + public void nearestWeekdayOfMonth() { + expression = CronExpression.parse("0 0 5W * *"); + List times = new ArrayList<>(); + ZonedDateTime t = DateTimes.startOfYear(); + int year = t.getYear(); + do { + times.add(nearestWeekday(t.withDayOfMonth(5))); + t = t.plusMonths(1); + } while (year == t.getYear()); + assertMatchesAll(times); + } + + @Test + public void nearestFriday() { + ZonedDateTime t = now().truncatedTo(ChronoUnit.DAYS).with(DayOfWeek.SATURDAY); + expression = CronExpression.parse("0 0 " + t.getDayOfMonth() + "W * *"); + assertMatches(t.minusDays(1)); + } + + @Test + public void nearestMonday() { + ZonedDateTime t = now().truncatedTo(ChronoUnit.DAYS).with(DayOfWeek.SUNDAY); + expression = CronExpression.parse("0 0 " + t.getDayOfMonth() + "W * *"); + assertMatches(t.plusDays(1)); + } + + @Test + public void nonMatchingNth() { + expression = CronExpression.parse("0 0 ? * 2#2"); + List times = new ArrayList<>(); + ZonedDateTime t = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).withDayOfYear(1); + int year = t.getYear(); + while (t.getYear() == year) { + times.add(nthOfMonth(t, DayOfWeek.TUESDAY, 1)); + t = t.plusMonths(1); + } + for (ZonedDateTime time : times) { + assertFalse(expression.matches(time)); + } + } + + @Test + public void multipleNth() { + expression = CronExpression.parse("0 0 ? * 5#3,2#2"); + List times = new ArrayList<>(); + ZonedDateTime t = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).withDayOfYear(1); + int year = t.getYear(); + while (t.getYear() == year) { + times.add(nthOfMonth(t, DayOfWeek.FRIDAY, 3)); + times.add(nthOfMonth(t, DayOfWeek.TUESDAY, 2)); + t = t.plusMonths(1); + } + assertMatchesAll(times); + } + + @Test + public void thirdFriday() { + String string = "0 0 ? * 5#3"; + List times = new ArrayList<>(); + ZonedDateTime t = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).withDayOfYear(1); + int year = t.getYear(); + while (t.getYear() == year) { + times.add(nthOfMonth(t, DayOfWeek.FRIDAY, 3)); + t = t.plusMonths(1); + } + expression = CronExpression.parse(string); + assertMatchesAll(times); + } + + @Test + public void reboot() { + expression = CronExpression.parse("@reboot"); + ZonedDateTime now = now(); + assertTrue(expression.matches(now)); + assertFalse(expression.matches(now)); + } + + @Test + public void minuteFullRangeExplicit() { + expression = CronExpression.parse("0-59 * * * * *"); + ZonedDateTime time = startOfHour(); + int hour = time.getHour(); + do { + assertMatches(time); + time = time.plusMinutes(1); + } while (time.getHour() == hour); + } + + @Test + public void minuteRestrictedRange() { + expression = CronExpression.parse("10-20 * * * * *"); + int first = 10, last = 20; + ZonedDateTime time = startOfHour(); + int hour = time.getHour(); + do { + int minute = time.getMinute(); + assertEquals(first <= minute && minute <= last, expression.matches(time)); + time = time.plusMinutes(1); + } while (time.getHour() == hour); + } + + @Test + public void minuteFullRangeMod() { + expression = CronExpression.parse("*/5 * * * * *"); + ZonedDateTime time = startOfHour(); + int hour = time.getHour(); + do { + int minute = time.getMinute(); + assertEquals(minute % 5 == 0, expression.matches(time)); + time = time.plusMinutes(1); + } while (time.getHour() == hour); + } + + @Test + public void minuteRestrictedRangeMod() { + expression = CronExpression.parse("10-20/5 * * * * *"); + int first = 10, last = 20; + ZonedDateTime time = startOfHour(); + int hour = time.getHour(); + do { + int minute = time.getMinute(); + assertEquals(first <= minute && minute <= last && minute % 5 == 0, expression.matches(time)); + time = time.plusMinutes(1); + } while (time.getHour() == hour); + } + + @Test + public void yearly() { + expression = CronExpression.yearly(); + assertYearly(); + } + + @Test + public void monthly() { + expression = CronExpression.monthly(); + assertMonthly(); + } + + @Test + public void weekly() { + expression = CronExpression.weekly(); + assertWeekly(); + } + + @Test + public void daily() { + expression = CronExpression.daily(); + assertDaily(); + } + + @Test + public void hourly() { + expression = CronExpression.hourly(); + assertHourly(); + } + + @Test + public void yearlyKeyword() { + expression = CronExpression.parse("@yearly"); + assertYearly(); + } + + @Test + public void annualKeyword() { + expression = CronExpression.parse("@annually"); + assertYearly(); + } + + @Test + public void monthlyKeyword() { + expression = CronExpression.parse("@monthly"); + assertMonthly(); + } + + @Test + public void weeklyKeyword() { + expression = CronExpression.parse("@weekly"); + assertWeekly(); + } + + @Test + public void dailyKeyword() { + expression = CronExpression.parse("@daily"); + assertDaily(); + } + + @Test + public void hourlyKeyword() { + expression = CronExpression.parse("@hourly"); + assertHourly(); + } + + @Test + public void invalid() { + assertFalse(CronExpression.isValid(null)); + assertFalse(CronExpression.isValid("")); + assertFalse(CronExpression.isValid("a")); + assertFalse(CronExpression.isValid("0 0 1 * X")); + assertFalse(CronExpression.isValid("0 0 1 * 1X")); + } + + @Test + public void invalidDueToSecondsField() { + assertTrue(CronExpression.isValid("0 0 1 * 1")); + assertFalse(CronExpression.parser().allowBothDayFields(false).isValid("0 0 1 * 1")); + } + + private void assertWeekly() { + for (int week = 1; week <= 52; week++) { + assertMatches(midnight().withDayOfYear(7 * week).with(DayOfWeek.SUNDAY)); + } + } + + private void assertDaily() { + for (int day = 1; day <= 365; day++) { + assertMatches(midnight().withDayOfYear(day)); + } + } + + private void assertMonthly() { + for (Month month : Month.values()) { + assertMatches(midnight().with(month).withDayOfMonth(1)); + } + } + + private void assertHourly() { + for (int day = 1; day <= 365; day++) { + for (int hour = 0; hour <= 23; hour++) { + assertMatches(midnight().withDayOfYear(day).withHour(hour)); + } + } + } + + private void assertYearly() { + assertMatches(midnight().withDayOfYear(1)); + } + + private static final String formatString = "m H d M E yyyy"; + + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatString); + + private void assertMatchesAll(List times) { + for (ZonedDateTime time : times) { + assertMatches(time); + } + } + + private void assertMatches(ZonedDateTime time) { + assertTrue( + time.format(formatter).toUpperCase() + " doesn't match expression: " + expression, + expression.matches(time) + ); + } +} diff --git a/src/test/java/org/xbib/time/schedule/CronScheduleTest.java b/src/test/java/org/xbib/time/schedule/CronScheduleTest.java new file mode 100644 index 0000000..82ecfcc --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/CronScheduleTest.java @@ -0,0 +1,126 @@ +package org.xbib.time.schedule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import com.google.common.collect.HashMultiset; +import com.google.common.collect.Multiset; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class CronScheduleTest { + + private static final Logger logger = Logger.getLogger(CronScheduleTest.class.getName()); + + private CronSchedule schedule; + + private ScheduledExecutorService executor; + + @Before + public void before() { + executor = Executors.newScheduledThreadPool(1); + } + + @Test + public void runMinutes() throws InterruptedException { + schedule = new CronSchedule<>(executor, 60000); + final AtomicBoolean run = new AtomicBoolean(false); + schedule.add("test", CronExpression.parser() + .parse("* * * * * *"), + () -> { + run.set(true); + return null; + }); + assertFalse(run.get()); + schedule.start(); + Thread.sleep(TimeUnit.MINUTES.toMillis(2)); + assertTrue(run.get()); + logger.log(Level.INFO, schedule.toString()); + } + + @Test + public void runSeconds() throws Exception { + schedule = new CronSchedule<>(executor, 1000); + final AtomicBoolean run = new AtomicBoolean(false); + schedule.add("test", CronExpression.parser() + .withSecondsField(true).parse("* * * * * *"), + () -> { + run.set(true); + return null; + }); + assertFalse(run.get()); + schedule.start(); + Thread.sleep(TimeUnit.SECONDS.toMillis(2)); + assertTrue(run.get()); + } + + @Test + public void removeOne() throws Exception { + schedule = new CronSchedule<>(executor, 1000); + final Multiset counts = HashMultiset.create(); + Callable a = () -> { + counts.add("a"); + return null; + }; + Callable b = () -> { + counts.add("b"); + return null; + }; + CronExpression expression = CronExpression.parse("* * * * *"); + schedule.add("1", expression, a); + schedule.add("2", expression, b); + runAndWait(); + assertEquals(1, counts.count("a")); + assertEquals(1, counts.count("b")); + schedule.remove("1"); + runAndWait(); + assertEquals(1, counts.count("a")); + assertEquals(2, counts.count("b")); + } + + @Test + public void removeAllForExpression() throws Exception { + schedule = new CronSchedule<>(executor, 1000); + final Multiset counts = HashMultiset.create(); + Callable a = () -> { + counts.add("a"); + return null; + }; + Callable b = () -> { + counts.add("b"); + return null; + }; + CronExpression expression = CronExpression.parse("* * * * *"); + schedule.add("a", expression, a); + schedule.add("b", expression, b); + runAndWait(); + assertEquals(1, counts.count("a")); + assertEquals(1, counts.count("b")); + schedule.remove("a"); + schedule.remove("b"); + runAndWait(); + assertEquals(1, counts.count("a")); + assertEquals(1, counts.count("b")); + } + + @After + public void after() throws IOException { + if (schedule != null) { + schedule.close(); + } + } + + private void runAndWait() throws InterruptedException { + schedule.run(); + Thread.sleep(10); + } +} diff --git a/src/test/java/org/xbib/time/schedule/DateTimes.java b/src/test/java/org/xbib/time/schedule/DateTimes.java new file mode 100644 index 0000000..3c52c05 --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/DateTimes.java @@ -0,0 +1,65 @@ +package org.xbib.time.schedule; + +import java.time.DayOfWeek; +import java.time.Month; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAdjusters; +import java.util.Date; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class DateTimes { + + static Iterable toDates(Iterable times) { + return StreamSupport.stream(times.spliterator(), false) + .map(input -> Date.from(input.toInstant())).collect(Collectors.toList()); + } + + static ZonedDateTime midnight() { + return now().truncatedTo(ChronoUnit.DAYS); + } + + static ZonedDateTime startOfHour() { + return now().truncatedTo(ChronoUnit.HOURS); + } + + public static ZonedDateTime now() { + return ZonedDateTime.now(); + } + + static ZonedDateTime lastOfMonth(ZonedDateTime t, DayOfWeek dayOfWeek) { + ZonedDateTime day = t.with(TemporalAdjusters.lastDayOfMonth()).with(dayOfWeek); + if (day.getMonth() != t.getMonth()) { + day = day.minusWeeks(1); + } + return day; + } + + static ZonedDateTime nthOfMonth(ZonedDateTime t, DayOfWeek dayOfWeek, int desiredNumber) { + Month month = t.getMonth(); + t = t.withDayOfMonth(1).with(dayOfWeek); + if (t.getMonth() != month) { + t = t.plusWeeks(1); + } + int number = 1; + while (number < desiredNumber && t.getMonth() == month) { + number++; + t = t.plusWeeks(1); + } + return t; + } + + static ZonedDateTime nearestWeekday(ZonedDateTime t) { + if (t.getDayOfWeek() == DayOfWeek.SATURDAY) { + return t.minusDays(1); + } else if (t.getDayOfWeek() == DayOfWeek.SUNDAY) { + return t.plusDays(1); + } + return t; + } + + static ZonedDateTime startOfYear() { + return midnight().withDayOfYear(1); + } +} diff --git a/src/test/java/org/xbib/time/schedule/DayOfMonthFieldTest.java b/src/test/java/org/xbib/time/schedule/DayOfMonthFieldTest.java new file mode 100644 index 0000000..dc57632 --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/DayOfMonthFieldTest.java @@ -0,0 +1,36 @@ +package org.xbib.time.schedule; + +import static org.junit.Assert.assertTrue; +import org.junit.Test; +import java.time.DayOfWeek; +import java.time.ZonedDateTime; +import java.time.temporal.TemporalAdjusters; + +public class DayOfMonthFieldTest { + + @Test + public void last() { + DayOfMonthField field = parse("L"); + assertTrue(field.matches(ZonedDateTime.now().with(TemporalAdjusters.lastDayOfMonth()))); + } + + @Test + public void nearestFriday() { + ZonedDateTime saturday = ZonedDateTime.now().with(DayOfWeek.SATURDAY); + ZonedDateTime friday = saturday.minusDays(1); + DayOfMonthField field = parse(saturday.getDayOfMonth() + "W"); + assertTrue(field.matches(friday)); + } + + @Test + public void nearestMonday() { + ZonedDateTime sunday = ZonedDateTime.now().with(DayOfWeek.SUNDAY); + ZonedDateTime monday = sunday.plusDays(1); + DayOfMonthField field = parse(sunday.getDayOfMonth() + "W"); + assertTrue(field.matches(monday)); + } + + private DayOfMonthField parse(String s) { + return DayOfMonthField.parse(new Tokens(s)); + } +} diff --git a/src/test/java/org/xbib/time/schedule/DayOfWeekFieldTest.java b/src/test/java/org/xbib/time/schedule/DayOfWeekFieldTest.java new file mode 100644 index 0000000..f862844 --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/DayOfWeekFieldTest.java @@ -0,0 +1,28 @@ +package org.xbib.time.schedule; + +import static org.junit.Assert.assertTrue; +import org.junit.Test; + +public class DayOfWeekFieldTest { + + @Test + public void keywords() { + assertTrue(parse("MON", false).contains(1)); + assertTrue(parse("TUE", false).contains(2)); + assertTrue(parse("WED", false).contains(3)); + assertTrue(parse("THU", false).contains(4)); + assertTrue(parse("FRI", false).contains(5)); + assertTrue(parse("SAT", false).contains(6)); + assertTrue(parse("SUN", false).contains(7)); + } + + @Test + public void oneBased() { + assertTrue(parse("1", true).contains(0)); + assertTrue(parse("2", true).contains(1)); + } + + private DayOfWeekField parse(String s, boolean oneBased) { + return DayOfWeekField.parse(new Tokens(s), oneBased); + } +} diff --git a/src/test/java/org/xbib/time/schedule/DefaultFieldTest.java b/src/test/java/org/xbib/time/schedule/DefaultFieldTest.java new file mode 100644 index 0000000..fdbb3a9 --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/DefaultFieldTest.java @@ -0,0 +1,125 @@ +package org.xbib.time.schedule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.junit.Test; + +public class DefaultFieldTest { + + private DefaultField field; + + @Test + public void emptyMonthField() { + parse("", 1, 12); + for (int month = 1; month <= 12; month++) { + assertFalse(field.contains(month)); + } + } + + @Test + public void backwardsRange() { + parse("2-1", 1, 2); + assertFalse(field.contains(0)); + assertFalse(field.contains(1)); + assertFalse(field.contains(2)); + assertFalse(field.contains(3)); + } + + @Test + public void finalWildcardWins() { + parse("1-2,2,3,*", 1, 10); + assertContainsRange(1, 10); + } + + @Test + public void initialWildcardWins() { + parse("*,1-2,2,3", 1, 10); + assertContainsRange(1, 10); + } + + @Test + public void multipleRanges() { + parse("1-2,3-4", 1, 5); + assertContains(1, 2, 3, 4); + } + + @Test + public void multipleNumbers() { + parse("1,2,3", 1, 5); + assertContains(1, 2, 3); + } + + @Test + public void danglingRange() { + try { + parse("1-", 0, 0); + fail("Expected exception"); + } catch (IllegalStateException e) { + assertEquals("Expected number", e.getMessage()); + } + } + + @Test + public void danglingSkip() { + try { + parse("1-2/", 0, 0); + fail("Expected exception"); + } catch (IllegalStateException e) { + assertEquals("Expected number", e.getMessage()); + } + } + + @Test + public void range() { + parse("1-12", 1, 12); + assertContainsRange(1, 12); + } + + @Test + public void wildcard() { + parse("*", 1, 12); + assertContainsRange(1, 12); + } + + @Test + public void skipRangeWithImplicitEnd() { + parse("1/5", 1, 31); + assertContains(1, 6, 11, 16, 21, 26, 31); + } + + @Test + public void oneBasedSkipRange() { + parse("1-31/5", 1, 31); + assertContains(1, 6, 11, 16, 21, 26, 31); + } + + @Test + public void zeroBasedSkipRange() { + parse("0-20/5", 0, 59); + assertContains(0, 5, 10, 15, 20); + } + + @Test + public void wildcardSkipRange() { + parse("*/5", 0, 20); + assertContains(0, 5, 10, 15, 20); + } + + private void assertContains(int... numbers) { + for (int number : numbers) { + assertTrue(field.contains(number)); + } + } + + private void assertContainsRange(int first, int last) { + for (int number = first; number <= last; number++) { + assertContains(number); + } + } + + private DefaultField parse(String s, int min, int max) { + return field = DefaultField.parse(new Tokens(s), min, max); + } +} diff --git a/src/test/java/org/xbib/time/schedule/Integers.java b/src/test/java/org/xbib/time/schedule/Integers.java new file mode 100644 index 0000000..677e43e --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/Integers.java @@ -0,0 +1,42 @@ +package org.xbib.time.schedule; + +import com.google.common.collect.ForwardingSet; +import java.util.HashSet; +import java.util.Set; + +public class Integers extends ForwardingSet { + private final Set delegate; + + public Integers(int... integers) { + delegate = new HashSet<>(); + with(integers); + } + + @Override + protected Set delegate() { + return delegate; + } + + public Integers with(int... integers) { + for (int integer : integers) { + add(integer); + } + return this; + } + + public Integers withRange(int start, int end) { + for (int i = start; i <= end; i++) { + add(i); + } + return this; + } + + public Integers withRange(int start, int end, int mod) { + for (int i = start; i <= end; i++) { + if ((i - start) % mod == 0) { + add(i); + } + } + return this; + } +} diff --git a/src/test/java/org/xbib/time/schedule/KeywordsTest.java b/src/test/java/org/xbib/time/schedule/KeywordsTest.java new file mode 100644 index 0000000..8767ca2 --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/KeywordsTest.java @@ -0,0 +1,79 @@ +package org.xbib.time.schedule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import org.junit.Before; +import org.junit.Test; + +public class KeywordsTest { + private Keywords keywords; + + @Before + public void before() { + keywords = new Keywords(); + } + + @Test + public void normalUse() { + keywords.put("AAA", 1); + keywords.put("BBB", 2); + assertEquals(1, keywords.get("AAABBB", 0, 3)); + assertEquals(2, keywords.get("AAABBB", 3, 6)); + } + + @Test + public void getNotPresent() { + try { + assertEquals(-1, keywords.get("CCC", 0, 3)); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + } + } + + @Test + public void getWrongAlphabet() { + try { + keywords.get("aaa", 0, 3); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + } + } + + @Test + public void getWrongLength() { + try { + keywords.get("aaa", 0, 1); + } catch (IllegalArgumentException e) { + } + } + + @Test + public void putEmpty() { + try { + keywords.put("", 0); + fail("Expected exception"); + } catch (StringIndexOutOfBoundsException e) { + assertEquals("String index out of range: 0", e.getMessage()); + } + } + + @Test + public void putWrongLength() { + try { + keywords.put("A", 0); + fail("Expected exception"); + } catch (StringIndexOutOfBoundsException e) { + assertEquals("String index out of range: 1", e.getMessage()); + } + } + + @Test + public void putWrongAlphabet() { + try { + keywords.put("a", 0); + fail("Expected exception"); + } catch (ArrayIndexOutOfBoundsException e) { + assertEquals("Index 32 out of bounds for length 26", e.getMessage()); + } + } +} diff --git a/src/test/java/org/xbib/time/schedule/MonthFieldTest.java b/src/test/java/org/xbib/time/schedule/MonthFieldTest.java new file mode 100644 index 0000000..83d161b --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/MonthFieldTest.java @@ -0,0 +1,27 @@ +package org.xbib.time.schedule; + +import static org.junit.Assert.assertTrue; +import org.junit.Test; + +public class MonthFieldTest { + + @Test + public void keywords() { + assertTrue(parse("JAN").contains(1)); + assertTrue(parse("FEB").contains(2)); + assertTrue(parse("MAR").contains(3)); + assertTrue(parse("APR").contains(4)); + assertTrue(parse("MAY").contains(5)); + assertTrue(parse("JUN").contains(6)); + assertTrue(parse("JUL").contains(7)); + assertTrue(parse("AUG").contains(8)); + assertTrue(parse("SEP").contains(9)); + assertTrue(parse("OCT").contains(10)); + assertTrue(parse("NOV").contains(11)); + assertTrue(parse("DEC").contains(12)); + } + + private DefaultField parse(String s) { + return MonthField.parse(new Tokens(s)); + } +} diff --git a/src/test/java/org/xbib/time/schedule/NextExecutionTest.java b/src/test/java/org/xbib/time/schedule/NextExecutionTest.java new file mode 100644 index 0000000..e931a13 --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/NextExecutionTest.java @@ -0,0 +1,79 @@ +package org.xbib.time.schedule; + +import org.junit.Test; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class NextExecutionTest { + + private static final Logger logger = Logger.getLogger(NextExecutionTest.class.getName()); + + @Test + public void nextSecond() { + CronExpression expression = CronExpression.parser().withSecondsField(true) + .parse("0-59 * * * * *"); + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime next = expression.nextExecution(now, now.plusMinutes(1)); + logger.log(Level.INFO, now.toString()); + logger.log(Level.INFO, next.toString()); + } + + @Test + public void nextHour() { + CronExpression expression = CronExpression.hourly(); + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime next = expression.nextExecution(now, now.plusHours(2)); + logger.log(Level.INFO, now.toString()); + logger.log(Level.INFO, next.toString()); + } + + @Test + public void nextDay() { + CronExpression expression = CronExpression.daily(); + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime next = expression.nextExecution(now, now.plusDays(2)); + logger.log(Level.INFO, now.toString()); + logger.log(Level.INFO, next.toString()); + } + + @Test + public void nextWeek() { + CronExpression expression = CronExpression.weekly(); + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime next = expression.nextExecution(now, now.plusWeeks(2)); + logger.log(Level.INFO, now.toString()); + logger.log(Level.INFO, next.toString()); + } + + @Test + public void nextMonth() { + CronExpression expression = CronExpression.monthly(); + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime next = expression.nextExecution(now, now.plusMonths(2)); + logger.log(Level.INFO, now.toString()); + logger.log(Level.INFO, next.toString()); + } + + @Test + public void nextFractionMinute() { + CronExpression expression = CronExpression.parse("10-20/5 * * * * *"); + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime next = expression.nextExecution(now, now.plusHours(2)); + logger.log(Level.INFO, now.toString()); + logger.log(Level.INFO, next.toString()); + } + + @Test + public void nextMultipleNth() { + CronExpression expression = CronExpression.parser() + .withSecondsField(true) + .parse("0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2010"); + ZonedDateTime begin = ZonedDateTime.of(2002, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + logger.log(Level.INFO, expression.toString()); + logger.log(Level.INFO, begin.toString()); + ZonedDateTime next = expression.nextExecution(begin, begin.plusYears(1)); + logger.log(Level.INFO, next.toString()); + } +} diff --git a/src/test/java/org/xbib/time/schedule/ObjectSizeCalculator.java b/src/test/java/org/xbib/time/schedule/ObjectSizeCalculator.java new file mode 100644 index 0000000..4435833 --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/ObjectSizeCalculator.java @@ -0,0 +1,436 @@ +package org.xbib.time.schedule; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.Sets; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryPoolMXBean; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * Contains utility methods for calculating the memory usage of objects. It + * only works on the HotSpot JVM, and infers the actual memory layout (32 bit + * vs. 64 bit word size, compressed object pointers vs. uncompressed) from + * best available indicators. It can reliably detect a 32 bit vs. 64 bit JVM. + * It can only make an educated guess at whether compressed OOPs are used, + * though; specifically, it knows what the JVM's default choice of OOP + * compression would be based on HotSpot version and maximum heap sizes, but if + * the choice is explicitly overridden with the -XX:{+|-}UseCompressedOops command line + * switch, it can not detect + * this fact and will report incorrect sizes, as it will presume the default JVM + * behavior. + */ +public class ObjectSizeCalculator { + + /** + * Describes constant memory overheads for various constructs in a JVM implementation. + */ + public interface MemoryLayoutSpecification { + + /** + * Returns the fixed overhead of an array of any type or length in this JVM. + * + * @return the fixed overhead of an array. + */ + int getArrayHeaderSize(); + + /** + * Returns the fixed overhead of for any {@link Object} subclass in this JVM. + * + * @return the fixed overhead of any object. + */ + int getObjectHeaderSize(); + + /** + * Returns the quantum field size for a field owned by an object in this JVM. + * + * @return the quantum field size for an object. + */ + int getObjectPadding(); + + /** + * Returns the fixed size of an object reference in this JVM. + * + * @return the size of all object references. + */ + int getReferenceSize(); + + /** + * Returns the quantum field size for a field owned by one of an object's ancestor superclasses + * in this JVM. + * + * @return the quantum field size for a superclass field. + */ + int getSuperclassFieldPadding(); + } + + private static class CurrentLayout { + private static final MemoryLayoutSpecification SPEC = + getEffectiveMemoryLayoutSpecification(); + } + + /** + * Given an object, returns the total allocated size, in bytes, of the object + * and all other objects reachable from it. Attempts to to detect the current JVM memory layout, + * but may fail with {@link UnsupportedOperationException}; + * + * @param obj the object; can be null. Passing in a {@link Class} object doesn't do + * anything special, it measures the size of all objects + * reachable through it (which will include its class loader, and by + * extension, all other Class objects loaded by + * the same loader, and all the parent class loaders). It doesn't provide the + * size of the static fields in the JVM class that the Class object + * represents. + * @return the total allocated size of the object and all other objects it + * retains. + * @throws UnsupportedOperationException if the current vm memory layout cannot be detected. + */ + public static long getObjectSize(Object obj) throws UnsupportedOperationException { + return obj == null ? 0 : new ObjectSizeCalculator(CurrentLayout.SPEC).calculateObjectSize(obj); + } + + // Fixed object header size for arrays. + private final int arrayHeaderSize; + // Fixed object header size for non-array objects. + private final int objectHeaderSize; + // Padding for the object size - if the object size is not an exact multiple + // of this, it is padded to the next multiple. + private final int objectPadding; + // Size of reference (pointer) fields. + private final int referenceSize; + // Padding for the fields of superclass before fields of subclasses are + // added. + private final int superclassFieldPadding; + + private final LoadingCache, ClassSizeInfo> classSizeInfos = + CacheBuilder.newBuilder().build(new CacheLoader, ClassSizeInfo>() { + public ClassSizeInfo load(Class clazz) { + return new ClassSizeInfo(clazz); + } + }); + + + private final Set alreadyVisited = Sets.newIdentityHashSet(); + private final Deque pending = new ArrayDeque(16 * 1024); + private long size; + + /** + * Creates an object size calculator that can calculate object sizes for a given + * {@code memoryLayoutSpecification}. + * + * @param memoryLayoutSpecification a description of the JVM memory layout. + */ + public ObjectSizeCalculator(MemoryLayoutSpecification memoryLayoutSpecification) { + Preconditions.checkNotNull(memoryLayoutSpecification); + arrayHeaderSize = memoryLayoutSpecification.getArrayHeaderSize(); + objectHeaderSize = memoryLayoutSpecification.getObjectHeaderSize(); + objectPadding = memoryLayoutSpecification.getObjectPadding(); + referenceSize = memoryLayoutSpecification.getReferenceSize(); + superclassFieldPadding = memoryLayoutSpecification.getSuperclassFieldPadding(); + } + + /** + * Given an object, returns the total allocated size, in bytes, of the object + * and all other objects reachable from it. + * + * @param obj the object; can be null. Passing in a {@link Class} object doesn't do + * anything special, it measures the size of all objects + * reachable through it (which will include its class loader, and by + * extension, all other Class objects loaded by + * the same loader, and all the parent class loaders). It doesn't provide the + * size of the static fields in the JVM class that the Class object + * represents. + * @return the total allocated size of the object and all other objects it + * retains. + */ + public synchronized long calculateObjectSize(Object obj) { + // Breadth-first traversal instead of naive depth-first with recursive + // implementation, so we don't blow the stack traversing long linked lists. + try { + for (; ; ) { + visit(obj); + if (pending.isEmpty()) { + return size; + } + obj = pending.removeFirst(); + } + } finally { + alreadyVisited.clear(); + pending.clear(); + size = 0; + } + } + + private void visit(Object obj) { + if (alreadyVisited.contains(obj)) { + return; + } + final Class clazz = obj.getClass(); + if (clazz == ArrayElementsVisitor.class) { + ((ArrayElementsVisitor) obj).visit(this); + } else { + alreadyVisited.add(obj); + if (clazz.isArray()) { + visitArray(obj); + } else { + classSizeInfos.getUnchecked(clazz).visit(obj, this); + } + } + } + + private void visitArray(Object array) { + final Class componentType = array.getClass().getComponentType(); + final int length = Array.getLength(array); + if (componentType.isPrimitive()) { + increaseByArraySize(length, getPrimitiveFieldSize(componentType)); + } else { + increaseByArraySize(length, referenceSize); + // If we didn't use an ArrayElementsVisitor, we would be enqueueing every + // element of the array here instead. For large arrays, it would + // tremendously enlarge the queue. In essence, we're compressing it into + // a small command object instead. This is different than immediately + // visiting the elements, as their visiting is scheduled for the end of + // the current queue. + switch (length) { + case 0: { + break; + } + case 1: { + enqueue(Array.get(array, 0)); + break; + } + default: { + enqueue(new ArrayElementsVisitor((Object[]) array)); + } + } + } + } + + private void increaseByArraySize(int length, long elementSize) { + increaseSize(roundTo(arrayHeaderSize + length * elementSize, objectPadding)); + } + + private static class ArrayElementsVisitor { + private final Object[] array; + + ArrayElementsVisitor(Object[] array) { + this.array = array; + } + + public void visit(ObjectSizeCalculator calc) { + for (Object elem : array) { + if (elem != null) { + calc.visit(elem); + } + } + } + } + + void enqueue(Object obj) { + if (obj != null) { + pending.addLast(obj); + } + } + + void increaseSize(long objectSize) { + size += objectSize; + } + + @VisibleForTesting + static long roundTo(long x, int multiple) { + return ((x + multiple - 1) / multiple) * multiple; + } + + private class ClassSizeInfo { + // Padded fields + header size + private final long objectSize; + // Only the fields size - used to calculate the subclasses' memory + // footprint. + private final long fieldsSize; + private final Field[] referenceFields; + + public ClassSizeInfo(Class clazz) { + long fieldsSize = 0; + final List referenceFields = new LinkedList(); + for (Field f : clazz.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers())) { + continue; + } + final Class type = f.getType(); + if (type.isPrimitive()) { + fieldsSize += getPrimitiveFieldSize(type); + } else { + f.setAccessible(true); + referenceFields.add(f); + fieldsSize += referenceSize; + } + } + final Class superClass = clazz.getSuperclass(); + if (superClass != null) { + final ClassSizeInfo superClassInfo = classSizeInfos.getUnchecked(superClass); + fieldsSize += roundTo(superClassInfo.fieldsSize, superclassFieldPadding); + referenceFields.addAll(Arrays.asList(superClassInfo.referenceFields)); + } + this.fieldsSize = fieldsSize; + this.objectSize = roundTo(objectHeaderSize + fieldsSize, objectPadding); + this.referenceFields = referenceFields.toArray( + new Field[referenceFields.size()]); + } + + void visit(Object obj, ObjectSizeCalculator calc) { + calc.increaseSize(objectSize); + enqueueReferencedObjects(obj, calc); + } + + public void enqueueReferencedObjects(Object obj, ObjectSizeCalculator calc) { + for (Field f : referenceFields) { + try { + calc.enqueue(f.get(obj)); + } catch (IllegalAccessException e) { + final AssertionError ae = new AssertionError( + "Unexpected denial of access to " + f, e); + throw ae; + } + } + } + } + + private static long getPrimitiveFieldSize(Class type) { + if (type == boolean.class || type == byte.class) { + return 1; + } + if (type == char.class || type == short.class) { + return 2; + } + if (type == int.class || type == float.class) { + return 4; + } + if (type == long.class || type == double.class) { + return 8; + } + throw new AssertionError("Encountered unexpected primitive type " + + type.getName()); + } + + @VisibleForTesting + static MemoryLayoutSpecification getEffectiveMemoryLayoutSpecification() { + final String vmName = System.getProperty("java.vm.name"); + if (vmName == null || !(vmName.startsWith("Java HotSpot(TM) ") || vmName.startsWith("OpenJDK"))) { + throw new UnsupportedOperationException( + "ObjectSizeCalculator only supported on HotSpot VM"); + } + + final String dataModel = System.getProperty("sun.arch.data.model"); + if ("32".equals(dataModel)) { + // Running with 32-bit data model + return new MemoryLayoutSpecification() { + @Override + public int getArrayHeaderSize() { + return 12; + } + + @Override + public int getObjectHeaderSize() { + return 8; + } + + @Override + public int getObjectPadding() { + return 8; + } + + @Override + public int getReferenceSize() { + return 4; + } + + @Override + public int getSuperclassFieldPadding() { + return 4; + } + }; + } else if (!"64".equals(dataModel)) { + throw new UnsupportedOperationException("Unrecognized value '" + + dataModel + "' of sun.arch.data.model system property"); + } + + final String strVmVersion = System.getProperty("java.vm.version"); + final int vmVersion = Integer.parseInt(strVmVersion.substring(0, + strVmVersion.indexOf('.'))); + if (vmVersion >= 17) { + long maxMemory = 0; + for (MemoryPoolMXBean mp : ManagementFactory.getMemoryPoolMXBeans()) { + maxMemory += mp.getUsage().getMax(); + } + if (maxMemory < 30L * 1024 * 1024 * 1024) { + // HotSpot 17.0 and above use compressed OOPs below 30GB of RAM total + // for all memory pools (yes, including code cache). + return new MemoryLayoutSpecification() { + @Override + public int getArrayHeaderSize() { + return 16; + } + + @Override + public int getObjectHeaderSize() { + return 12; + } + + @Override + public int getObjectPadding() { + return 8; + } + + @Override + public int getReferenceSize() { + return 4; + } + + @Override + public int getSuperclassFieldPadding() { + return 4; + } + }; + } + } + + // In other cases, it's a 64-bit uncompressed OOPs object model + return new MemoryLayoutSpecification() { + @Override + public int getArrayHeaderSize() { + return 24; + } + + @Override + public int getObjectHeaderSize() { + return 16; + } + + @Override + public int getObjectPadding() { + return 8; + } + + @Override + public int getReferenceSize() { + return 8; + } + + @Override + public int getSuperclassFieldPadding() { + return 8; + } + }; + } +} \ No newline at end of file diff --git a/src/test/java/org/xbib/time/schedule/ReadmeTest.java b/src/test/java/org/xbib/time/schedule/ReadmeTest.java new file mode 100644 index 0000000..369d54b --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/ReadmeTest.java @@ -0,0 +1,38 @@ +package org.xbib.time.schedule; + +import static org.junit.Assert.assertTrue; +import org.junit.Test; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +/** + * Examples used in the Readme file. + */ +public class ReadmeTest { + + @Test + public void normal() { + ZonedDateTime time = ZonedDateTime.now().withDayOfYear(1).truncatedTo(ChronoUnit.DAYS); + assertTrue(CronExpression.parse("0 0 1 1 *").matches(time)); + assertTrue(CronExpression.parse("@yearly").matches(time)); + assertTrue(CronExpression.parse("@annually").matches(time)); + assertTrue(CronExpression.yearly().matches(time)); + } + + @Test + public void quartzLike() { + CronExpression expression = CronExpression.parser() + .withSecondsField(true) + .withOneBasedDayOfWeek(true) + .allowBothDayFields(false) + .parse("0 15 10 L * ?"); + ZonedDateTime time = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS) + .withYear(2013) + .withMonth(1) + .withDayOfMonth(31) + .withHour(10) + .withMinute(15); + assertTrue(expression.matches(time)); + } + +} diff --git a/src/test/java/org/xbib/time/schedule/Times.java b/src/test/java/org/xbib/time/schedule/Times.java new file mode 100644 index 0000000..c3c13e4 --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/Times.java @@ -0,0 +1,102 @@ +package org.xbib.time.schedule; + +import com.google.common.collect.ImmutableSortedSet; +import java.time.DayOfWeek; +import java.time.Month; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAdjusters; +import java.util.Iterator; +import java.util.NavigableSet; + +public class Times { + + final Integers + seconds, + minutes, + hours, + months, + daysOfWeek, + years, + daysOfMonth; + + Times() { + seconds = new Integers(); + minutes = new Integers(); + hours = new Integers(); + months = new Integers(); + daysOfWeek = new Integers(); + years = new Integers(); + daysOfMonth = new Integers(); + } + + NavigableSet dateTimes() { + if (seconds.isEmpty()) { + seconds.withRange(0, 1); + } + if (minutes.isEmpty()) { + minutes.withRange(0, 1); + } + if (hours.isEmpty()) { + hours.withRange(0, 1); + } + if (months.isEmpty()) { + months.withRange(1, 2); + } + if (years.isEmpty()) { + int thisYear = ZonedDateTime.now().getYear(); + years.withRange(thisYear, thisYear + 1); + } + ImmutableSortedSet.Builder builder = ImmutableSortedSet.naturalOrder(); + for (int second : seconds) { + for (int minute : minutes) { + for (int hour : hours) { + for (int month : months) { + for (int year : years) { + ZonedDateTime base = ZonedDateTime.now() + .truncatedTo(ChronoUnit.DAYS) + .withSecond(second) + .withMinute(minute) + .withHour(hour) + .withMonth(month) + .withDayOfMonth(1) + .withYear(year); + if (!daysOfWeek.isEmpty() && !daysOfMonth.isEmpty()) { + addDaysOfWeek(builder, base); + addDaysOfMonth(builder, base); + } else if (!daysOfWeek.isEmpty()) { + addDaysOfWeek(builder, base); + } else if (!daysOfMonth.isEmpty()) { + addDaysOfMonth(builder, base); + } else { + builder.add(base); + } + } + } + } + } + } + return builder.build(); + } + + private void addDaysOfWeek(ImmutableSortedSet.Builder builder, ZonedDateTime base) { + Month month = base.getMonth(); + Iterator iterator = daysOfWeek.iterator(); + base = base.with(DayOfWeek.of(iterator.next())); + if (base.getMonth() != month) { + base = base.plusWeeks(1); + } + do { + builder.add(base); + base = base.plusWeeks(1); + } while (base.getMonth() == month); + } + + private void addDaysOfMonth(ImmutableSortedSet.Builder builder, ZonedDateTime base) { + for (int day : daysOfMonth) { + if (day <= base.with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth()) { + builder.add(base.withDayOfMonth(day)); + } + } + } +} diff --git a/src/test/java/org/xbib/time/schedule/TokensTest.java b/src/test/java/org/xbib/time/schedule/TokensTest.java new file mode 100644 index 0000000..86493db --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/TokensTest.java @@ -0,0 +1,204 @@ +package org.xbib.time.schedule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; +import org.junit.Test; + +public class TokensTest { + private Tokens tokens; + + @Test + public void empty() { + tokenize(""); + assertFalse(tokens.hasNext()); + assertEndOfInput(); + } + + @Test + public void end() { + tokenize("1"); + assertNextIsNumber(1); + assertFalse(tokens.hasNext()); + assertEndOfInput(); + } + + @Test + public void offset() { + tokenize("5,6"); + tokens.offset(5); + assertNextIsNumber(0); + assertNextIs(Token.VALUE_SEPARATOR); + assertNextIsNumber(1); + assertEndOfInput(); + } + + @Test + public void resetClearsOffset() { + tokenize("2,2"); + tokens.offset(1); + assertNextIsNumber(1); + assertNextIs(Token.VALUE_SEPARATOR); + tokens.reset(); + assertNextIsNumber(2); + } + + @Test + public void resetClearsKeywords() { + tokenize("FRI,FRI"); + tokens.keywords(DayOfWeekField.Builder.KEYWORDS); + assertNextIsNumber(5); + assertNextIs(Token.VALUE_SEPARATOR); + tokens.reset(); + try { + tokens.next(); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + assertEquals("Bad keyword 'FRI' at position 4 in string: FRI,FRI", e.getMessage()); + } + } + + @Test + public void matchOne() { + tokenize("?"); + assertNextIs(Token.MATCH_ONE); + assertEndOfInput(); + } + + @Test + public void matchAll() { + tokenize("*"); + assertNextIs(Token.MATCH_ALL); + assertEndOfInput(); + } + + @Test + public void skip() { + tokenize("/"); + assertNextIs(Token.SKIP); + assertEndOfInput(); + } + + @Test + public void range() { + tokenize("-"); + assertNextIs(Token.RANGE); + assertEndOfInput(); + } + + @Test + public void last() { + tokenize("1L"); + assertNextIsNumber(1); + assertNextIs(Token.LAST); + assertEndOfInput(); + } + + @Test + public void lastAlone() { + tokenize("L"); + assertNextIs(Token.LAST); + assertEndOfInput(); + } + + @Test + public void weekday() { + tokenize("1W"); + assertNextIsNumber(1); + assertNextIs(Token.WEEKDAY); + assertEndOfInput(); + } + + @Test + public void nth() { + tokenize("1#2"); + assertNextIsNumber(1); + assertNextIs(Token.NTH); + assertNextIsNumber(2); + assertEndOfInput(); + } + + @Test + public void multipleWhitespaceCharacters() { + tokenize(" \t \t \t \t "); + assertEquals(Token.FIELD_SEPARATOR, tokens.next()); + assertEndOfInput(); + } + + @Test + public void keywordRange() { + tokenize("MON-FRI"); + tokens.keywords(DayOfWeekField.Builder.KEYWORDS); + assertEquals(Token.NUMBER, tokens.next()); + assertEquals(1, tokens.number()); + assertEquals(Token.RANGE, tokens.next()); + assertEquals(Token.NUMBER, tokens.next()); + assertEquals(5, tokens.number()); + assertEndOfInput(); + } + + @Test + public void badCharacter() { + tokenize("5%"); + assertEquals(Token.NUMBER, tokens.next()); + try { + tokens.next(); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + assertEquals("Bad character '%' at position 1 in string: 5%", e.getMessage()); + } + } + + @Test + public void badLetter() { + tokenize("1F"); + assertEquals(Token.NUMBER, tokens.next()); + try { + tokens.next(); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + assertEquals("Bad character 'F' at position 1 in string: 1F", e.getMessage()); + } + } + + @Test + public void badKeywordOfValidLength() { + tokenize("ABC"); + tokens.keywords(DayOfWeekField.Builder.KEYWORDS); + try { + tokens.next(); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + assertEquals("Bad keyword 'ABC' at position 0 in string: ABC", e.getMessage()); + } + } + + @Test + public void badKeywordOfInvalidLength() { + tokenize("AB"); + tokens.keywords(DayOfWeekField.Builder.KEYWORDS); + try { + tokens.next(); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + assertEquals("Bad keyword 'AB' at position 0 in string: AB", e.getMessage()); + } + } + + private void assertEndOfInput() { + assertNextIs(Token.END_OF_INPUT); + } + + private void assertNextIsNumber(int expected) { + assertNextIs(Token.NUMBER); + assertEquals(expected, tokens.number()); + } + + private void assertNextIs(Token expected) { + assertEquals(expected, tokens.next()); + } + + private void tokenize(String s) { + tokens = new Tokens(s); + } +} diff --git a/src/test/java/org/xbib/time/schedule/WhatQuartzDoesNotSupport.java b/src/test/java/org/xbib/time/schedule/WhatQuartzDoesNotSupport.java new file mode 100644 index 0000000..af33881 --- /dev/null +++ b/src/test/java/org/xbib/time/schedule/WhatQuartzDoesNotSupport.java @@ -0,0 +1,53 @@ +package org.xbib.time.schedule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.xbib.time.schedule.DateTimes.nthOfMonth; +import org.junit.Test; +import java.text.ParseException; +import java.time.DayOfWeek; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class WhatQuartzDoesNotSupport { + @Test + public void multipleNthDayOfWeek() { + try { + org.quartz.CronExpression quartz = new org.quartz.CronExpression("0 0 0 ? * 6#3,4#1,3#2"); + List times = new ArrayList<>(); + ZonedDateTime t = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).withDayOfYear(1); + int year = t.getYear(); + while (t.getYear() == year) { + times.add(nthOfMonth(t, DayOfWeek.FRIDAY, 3)); + times.add(nthOfMonth(t, DayOfWeek.TUESDAY, 2)); + t = t.plusMonths(1); + } + for (ZonedDateTime time : times) { + boolean satisfied = quartz.isSatisfiedBy(Date.from(time.toInstant())); + if (time.getDayOfWeek() == DayOfWeek.TUESDAY) { + // Earlier versions of Quartz only picked up the last one + assertTrue(satisfied); + } else { + assertFalse(satisfied); + } + } + } catch (ParseException e) { + assertEquals("Support for specifying multiple \"nth\" days is not implemented.", e.getMessage()); + } + } + + @Test + public void multipleLastDayOfWeek() throws Exception { + try { + new org.quartz.CronExpression("0 0 0 ? * 6L,4L,3L"); + fail("Expected exception"); + } catch (ParseException e) { + assertEquals("Support for specifying 'L' with other days of the week is not implemented", e.getMessage()); + } + } +} diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml deleted file mode 100644 index f71aced..0000000 --- a/src/test/resources/log4j2.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file