├── .github └── workflows │ ├── main.yml │ └── pr.yml ├── .gitignore ├── LICENSE ├── NOTICE ├── README.md ├── benchmark ├── build.gradle ├── results │ ├── results-0.11.6-1-g4ccdb6d.json │ └── results-0.16.0.json └── src │ └── jmh │ └── java │ └── org │ └── dmfs │ └── rfc5545 │ └── recur │ ├── RecurrenceRuleExpansion.java │ ├── RecurrenceSetExpansion.java │ └── RecurrenceSetIterableExpansion.java ├── build.gradle ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jacoco.gradle ├── lib-recur-confidence ├── build.gradle └── src │ ├── main │ └── java │ │ └── org │ │ └── dmfs │ │ └── rfc5545 │ │ └── confidence │ │ └── quality │ │ ├── EmptyRecurrenceSet.java │ │ ├── Finite.java │ │ ├── Infinite.java │ │ └── StartsWith.java │ └── test │ └── java │ └── org │ └── dmfs │ └── rfc5545 │ └── confidence │ └── quality │ ├── EmptyRecurrenceSetTest.java │ ├── FiniteTest.java │ ├── InfiniteTest.java │ └── StartsWithTest.java ├── lib-recur-hamcrest ├── build.gradle └── src │ ├── main │ └── java │ │ └── org │ │ └── dmfs │ │ └── rfc5545 │ │ └── hamcrest │ │ ├── GeneratorMatcher.java │ │ ├── IncreasingMatcher.java │ │ ├── InstancesMatcher.java │ │ ├── RecurrenceRuleMatcher.java │ │ ├── ResultsMatcher.java │ │ ├── WalkingStartMatcher.java │ │ └── datetime │ │ ├── AfterMatcher.java │ │ ├── BeforeMatcher.java │ │ ├── DayOfMonthMatcher.java │ │ ├── DayOfYearMatcher.java │ │ ├── MonthMatcher.java │ │ ├── WeekDayMatcher.java │ │ ├── WeekOfYearMatcher.java │ │ └── YearMatcher.java │ └── test │ └── java │ └── org │ └── dmfs │ └── rfc5545 │ └── hamcrest │ ├── ResultsMatcherTest.java │ └── datetime │ ├── AfterMatcherTest.java │ ├── BeforeMatcherTest.java │ ├── DayOfMonthMatcherTest.java │ ├── DayOfYearMatcherTest.java │ ├── MonthMatcherTest.java │ ├── WeekDayMatcherTest.java │ ├── WeekOfYearMatcherTest.java │ └── YearMatcherTest.java ├── publish.gradle ├── settings.gradle └── src ├── main └── java │ └── org │ └── dmfs │ └── rfc5545 │ ├── InstanceIterator.java │ ├── RecurrenceSet.java │ ├── instanceiterator │ ├── CountLimitedRecurrenceRuleIterator.java │ ├── EffectiveInstancesIterator.java │ ├── EmptyIterator.java │ ├── FastForwardable.java │ ├── Merged.java │ ├── PeekableInstanceIterator.java │ └── package-info.java │ ├── iterable │ ├── InstanceIterable.java │ ├── InstanceIterator.java │ ├── ParsedDates.java │ ├── RecurrenceSet.java │ ├── instanceiterable │ │ ├── Composite.java │ │ ├── EmptyIterable.java │ │ ├── FastForwarded.java │ │ ├── FirstAndRuleInstances.java │ │ ├── InstanceList.java │ │ └── RuleInstances.java │ └── instanceiterator │ │ ├── Composite.java │ │ ├── CountLimitedRecurrenceRuleIterator.java │ │ ├── EffectiveInstancesIterator.java │ │ └── EmptyIterator.java │ ├── optional │ ├── Last.java │ ├── LastInstance.java │ └── package-info.java │ ├── package-info.java │ ├── recur │ ├── ByDayFilter.java │ ├── ByDayMonthlyExpander.java │ ├── ByDayPrefixedFilter.java │ ├── ByDayWeeklyAndMonthlyExpander.java │ ├── ByDayWeeklyExpander.java │ ├── ByDayYearlyExpander.java │ ├── ByExpander.java │ ├── ByFilter.java │ ├── ByHourExpander.java │ ├── ByHourFilter.java │ ├── ByMinuteExpander.java │ ├── ByMinuteFilter.java │ ├── ByMonthDayFilter.java │ ├── ByMonthDayMonthlyExpander.java │ ├── ByMonthDaySkipFilter.java │ ├── ByMonthDayWeeklyAndMonthlyExpander.java │ ├── ByMonthDayWeeklyExpander.java │ ├── ByMonthExpander.java │ ├── ByMonthFilter.java │ ├── ByMonthSkipFilter.java │ ├── BySecondExpander.java │ ├── BySecondFilter.java │ ├── BySetPosFilter.java │ ├── ByWeekNoFilter.java │ ├── ByWeekNoMonthlyExpander.java │ ├── ByWeekNoMonthlyOverlapExpander.java │ ├── ByWeekNoYearlyExpander.java │ ├── ByYearDayFilter.java │ ├── ByYearDayMonthlyExpander.java │ ├── ByYearDayWeeklyAndMonthlyExpander.java │ ├── ByYearDayWeeklyExpander.java │ ├── ByYearDayYearlyExpander.java │ ├── CountLimiter.java │ ├── Freq.java │ ├── FreqIterator.java │ ├── InvalidRecurrenceRuleException.java │ ├── Limiter.java │ ├── LongArray.java │ ├── RecurrenceRule.java │ ├── RecurrenceRuleIterator.java │ ├── RuleIterator.java │ ├── SanityFilter.java │ ├── SkipBuffer.java │ ├── StaticUtils.java │ ├── TrivialByMonthFilter.java │ ├── UnicodeCalendarScales.java │ ├── UntilDateLimiter.java │ └── UntilLimiter.java │ └── recurrenceset │ ├── Difference.java │ ├── FastForwarded.java │ ├── Merged.java │ ├── OfList.java │ ├── OfRule.java │ ├── OfRuleAndFirst.java │ ├── Preceding.java │ ├── Within.java │ └── package-info.java └── test └── java └── org └── dmfs └── rfc5545 ├── instanceiterator └── PeekableInstanceIteratorTest.java ├── iterable ├── ParsedDatesTest.java └── RecurrenceSetTest.java ├── optional └── LastInstanceTest.java ├── recur ├── InvalidRuleTest.java ├── LongArrayTest.java ├── RScaleTest.java ├── RecurrenceEquivalenceTest.java ├── RecurrenceIteratorTest.java ├── RecurrenceParserTest.java ├── RecurrenceRuleTest.java ├── RecurrenceRuleYearlyTest.java ├── TestDate.java └── TestRule.java └── recurrenceset ├── DifferenceTest.java ├── FastForwardedTest.java ├── MergedTest.java ├── OfListTest.java ├── OfRuleAndFirstTest.java ├── OfRuleTest.java ├── PrecedingTest.java └── WithinTest.java /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build main 2 | on: 3 | push: 4 | branches: 5 | - 'main' 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | main: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Set up JDK 11 15 | uses: actions/setup-java@v3 16 | with: 17 | java-version: '11' 18 | distribution: 'temurin' 19 | 20 | - name: Checkout Repo 21 | uses: actions/checkout@v3 22 | with: 23 | fetch-depth: 0 24 | 25 | - name: Build Project 26 | run: ./gradlew gitVersion check javadoc -P GITHUB_API_TOKEN=${{ secrets.GITHUB_TOKEN }} 27 | 28 | - name: Tag Release 29 | run: ./gradlew gitTagRelease -P GITHUB_API_TOKEN=${{ secrets.GITHUB_TOKEN }} 30 | 31 | - name: Push Tags 32 | run: git push --tags 33 | 34 | - name: Upload Test Results 35 | uses: codecov/codecov-action@v4 36 | with: 37 | fail_ci_if_error: true # optional (default = false) 38 | verbose: true # optional (default = false) 39 | token: ${{ secrets.CODECOV_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Build PR 2 | on: 3 | pull_request: 4 | branches: 5 | - 'main' 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Set up JDK 11 11 | uses: actions/setup-java@v3 12 | with: 13 | java-version: '11' 14 | distribution: 'temurin' 15 | 16 | - name: Checkout Repo 17 | uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Build Project 22 | run: ./gradlew gitVersion check javadoc -P GITHUB_API_TOKEN=${{ secrets.GITHUB_TOKEN }} 23 | 24 | - name: Upload Test Results 25 | uses: codecov/codecov-action@v4 26 | with: 27 | fail_ci_if_error: true # optional (default = false) 28 | verbose: true # optional (default = false) 29 | token: ${{ secrets.CODECOV_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/gradle,intellij 3 | 4 | ### Intellij ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff: 9 | .idea 10 | 11 | ## File-based project format: 12 | *.iws 13 | 14 | ## Plugin-specific files: 15 | 16 | # IntelliJ 17 | /out/ 18 | /*/out/ 19 | 20 | # mpeltonen/sbt-idea plugin 21 | .idea_modules/ 22 | 23 | # JIRA plugin 24 | atlassian-ide-plugin.xml 25 | 26 | # Crashlytics plugin (for Android Studio and IntelliJ) 27 | com_crashlytics_export_strings.xml 28 | crashlytics.properties 29 | crashlytics-build.properties 30 | fabric.properties 31 | 32 | ### Intellij Patch ### 33 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 34 | 35 | *.iml 36 | modules.xml 37 | .idea/misc.xml 38 | *.ipr 39 | 40 | 41 | ### Gradle ### 42 | .gradle 43 | /build/ 44 | /*/build 45 | 46 | # Ignore Gradle GUI config 47 | gradle-app.setting 48 | 49 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 50 | !gradle-wrapper.jar 51 | 52 | # Cache of project 53 | .gradletasknamecache 54 | 55 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 56 | # gradle/wrapper/gradle-wrapper.properties 57 | 58 | build/ 59 | 60 | *.class 61 | pom.xml* 62 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | lib-recur - A recurrence processor for Java 2 | Copyright 2013 Marten Gajda - http://dmfs.org -------------------------------------------------------------------------------- /benchmark/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id "me.champeau.jmh" version "0.6.8" 4 | } 5 | 6 | sourceCompatibility = JavaVersion.VERSION_1_8 7 | targetCompatibility = JavaVersion.VERSION_1_8 8 | 9 | dependencies { 10 | jmh libs.jems2 11 | jmh rootProject 12 | } 13 | 14 | jmh { 15 | resultFormat = 'JSON' 16 | resultsFile = project.file("${project.buildDir}/reports/jmh/results-${version}.json") 17 | } 18 | -------------------------------------------------------------------------------- /benchmark/src/jmh/java/org/dmfs/rfc5545/recur/RecurrenceRuleExpansion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.openjdk.jmh.annotations.*; 22 | 23 | import java.util.TimeZone; 24 | 25 | 26 | /** 27 | * @author marten 28 | */ 29 | @Warmup(iterations = 5, time = 5) 30 | @Measurement(iterations = 5, time = 5) 31 | @Fork(5) 32 | @BenchmarkMode(Mode.Throughput) 33 | public class RecurrenceRuleExpansion 34 | { 35 | @State(Scope.Benchmark) 36 | public static class BenchmarkState 37 | { 38 | 39 | @Param({ "1", "1000" }) 40 | int iterations; 41 | 42 | @Param({ 43 | "FREQ=YEARLY;BYMONTH=12;BYMONTHDAY=24", 44 | "FREQ=MONTHLY;BYMONTH=12;BYMONTHDAY=24", 45 | "FREQ=YEARLY;BYDAY=-2SU,-3SU,-4SU,-5SU", 46 | "FREQ=MONTHLY;INTERVAL=3;BYDAY=2WE", 47 | "FREQ=YEARLY;INTERVAL=1;BYDAY=WE;BYMONTHDAY=25,26,27,21,22,23,24;BYMONTH=4", 48 | "FREQ=MONTHLY;INTERVAL=1;BYDAY=WE;BYMONTHDAY=25,26,27,21,22,23,24;BYMONTH=4", 49 | "FREQ=YEARLY;BYDAY=MO", 50 | "FREQ=MONTHLY;BYDAY=MO", 51 | "FREQ=WEEKLY;BYDAY=MO", 52 | "FREQ=DAILY;BYDAY=MO", 53 | "FREQ=YEARLY;BYDAY=MO,TU,WE,TH,FR,SA,SU", 54 | "FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR,SA,SU", 55 | "FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR,SA,SU", 56 | "FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR,SA,SU", 57 | }) 58 | String rule; 59 | 60 | TimeZone tz = TimeZone.getTimeZone("Europe/Berlin"); 61 | 62 | DateTime start = new DateTime(tz, 2020, 3, 1, 10, 30, 0); 63 | 64 | RecurrenceRule recurrenceRule; 65 | 66 | 67 | @Setup 68 | public void setup() throws InvalidRecurrenceRuleException 69 | { 70 | recurrenceRule = new RecurrenceRule(rule); 71 | } 72 | } 73 | 74 | 75 | @Benchmark 76 | public long benchmarkExpansion(BenchmarkState state) 77 | { 78 | RecurrenceRuleIterator iterator = state.recurrenceRule.iterator(state.start); 79 | long last = 0L; 80 | int count = state.iterations; 81 | while (iterator.hasNext() && count-- > 0) 82 | { 83 | last = iterator.nextMillis(); 84 | } 85 | return last; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | id 'org.dmfs.gver' version '0.18.0' 4 | id 'io.github.gradle-nexus.publish-plugin' version '1.1.0' apply false 5 | } 6 | 7 | sourceCompatibility = JavaVersion.VERSION_1_8 8 | targetCompatibility = JavaVersion.VERSION_1_8 9 | 10 | gver { 11 | issueTracker GitHub { 12 | repo = "dmfs/lib-recur" 13 | if (project.hasProperty("GITHUB_API_TOKEN")) { 14 | accessToken = GITHUB_API_TOKEN 15 | } 16 | } 17 | changes { 18 | are none when { 19 | affects only(matches(~/.*\.md/)) 20 | } 21 | are major when { 22 | commitMessage contains(~/(?i)#(major|break(ing)?)\b/) 23 | } 24 | are minor when { 25 | commitMessage contains(~/(?i)#(?\d+)\b/) { 26 | where("issue") { isIssue { labeled "enhancement" } } 27 | } 28 | } 29 | are patch when { 30 | commitMessage contains(~/(?i)#(?\d+)\b/) { 31 | where("issue") { isIssue { labeled "bug" } } 32 | } 33 | } 34 | are minor when { 35 | commitMessage contains("#feature\\b") 36 | } 37 | otherwise patch 38 | } 39 | preReleases { 40 | on ~/main|master/ use { "beta" } 41 | on ~/(.*\/)?(?.*)/ use { "alpha-${group('name')}.1" } 42 | } 43 | releaseBranchPattern ~/main|master$/ 44 | } 45 | 46 | configurations { 47 | pom 48 | } 49 | 50 | apply from: 'publish.gradle' 51 | apply from: 'jacoco.gradle' 52 | 53 | allprojects { 54 | group 'org.dmfs' 55 | repositories { 56 | mavenCentral() 57 | } 58 | } 59 | 60 | if (project.hasProperty('SONATYPE_USERNAME') && project.hasProperty('SONATYPE_PASSWORD')) { 61 | apply plugin: 'io.github.gradle-nexus.publish-plugin' 62 | 63 | nexusPublishing { 64 | repositories { 65 | sonatype { 66 | username = SONATYPE_USERNAME 67 | password = SONATYPE_PASSWORD 68 | } 69 | } 70 | } 71 | } 72 | 73 | dependencies { 74 | compileOnly libs.srcless.annotations 75 | annotationProcessor libs.srcless.processors 76 | compileOnly 'org.eclipse.jdt:org.eclipse.jdt.annotation:2.2.600' 77 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0' 78 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0' 79 | api 'org.dmfs:rfc5545-datetime:0.3' 80 | api libs.jems2 81 | testImplementation project("lib-recur-hamcrest") 82 | testImplementation project("lib-recur-confidence") 83 | testImplementation libs.jems2.testing 84 | testImplementation libs.jems2.confidence 85 | testImplementation libs.confidence.core 86 | testImplementation libs.confidence.engine 87 | } 88 | 89 | 90 | test { 91 | useJUnitPlatform() 92 | } 93 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | RELEASE_REPOSITORY=https://oss.sonatype.org/service/local/staging/deploy/maven2 2 | POM_DEVELOPER_ID=dmfs 3 | POM_DEVELOPER_NAME=Marten Gajda 4 | POM_DEVELOPER_EMAIL=marten@dmfs.org 5 | POM_DEVELOPER_ORGANIZATION=dmfs GmbH 6 | POM_DEVELOPER_ORGANIZATION_URL=https://dmfs.org 7 | POM_LICENSE=The Apache Software License, Version 2.0 8 | POM_LICENSE_URL=http://www.apache.org/license/LICENSE-2.0.txt 9 | POM_PROJECT_URL=https://github.com/dmfs/lib-recur 10 | POM_SCM_URL=git@github.com:dmfs/lib-recur.git 11 | POM_SCM_CONNECTION=scm:git:git@github.com:dmfs/lib-recur.git 12 | POM_SCM_DEVELOPER_CONNECTION=scm:git:git@github.com:dmfs/lib-recur.git 13 | POM_DESCRIPTION A recurrence processor for Java 14 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | eclipse-jdt = "2.2.600" 3 | hamcrest = "2.2" 4 | jems2 = "2.23.1" 5 | junit = "5.8.2" 6 | junit-testkit = "1.9.2" 7 | srcless = "0.3.0" 8 | confidence = "0.42.0" 9 | 10 | [libraries] 11 | srcless-annotations = { module = "org.dmfs:srcless-annotations", version.ref = "srcless" } 12 | srcless-processors = { module = "org.dmfs:srcless-processors", version.ref = "srcless" } 13 | eclipse-jdt-anntation = { module = 'org.eclipse.jdt:org.eclipse.jdt.annotation', version.ref = "eclipse-jdt" } 14 | nullless-processors = { module = "org.dmfs:nullless-processors", version.ref = "srcless" } 15 | 16 | junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } 17 | junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } 18 | 19 | jems2 = { module = "org.dmfs:jems2", version.ref = "jems2" } 20 | jems2-testing = { module = "org.dmfs:jems2-testing", version.ref = "jems2" } 21 | jems2-confidence = { module = "org.dmfs:jems2-confidence", version.ref = "jems2" } 22 | 23 | confidence-core = { module = "org.saynotobugs:confidence-core", version.ref = "confidence" } 24 | confidence-test = { module = "org.saynotobugs:confidence-test", version.ref = "confidence" } 25 | confidence-engine = { module = "org.saynotobugs:confidence-incubator", version.ref = "confidence" } 26 | 27 | [bundles] 28 | srcless-processors = ["srcless-processors", "nullless-processors"] 29 | 30 | [plugins] 31 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmfs/lib-recur/5ba0c83529d47e8020fa3b6411689841edca6242/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jan 18 02:56:17 CET 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /jacoco.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "jacoco" 2 | 3 | jacocoTestReport { 4 | reports { 5 | xml.enabled = true 6 | html.enabled = true 7 | } 8 | } 9 | 10 | check.dependsOn jacocoTestReport -------------------------------------------------------------------------------- /lib-recur-confidence/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | } 4 | 5 | sourceCompatibility = JavaVersion.VERSION_1_8 6 | targetCompatibility = JavaVersion.VERSION_1_8 7 | 8 | dependencies { 9 | compileOnly libs.eclipse.jdt.anntation 10 | compileOnly libs.srcless.annotations 11 | annotationProcessor libs.bundles.srcless.processors 12 | api libs.confidence.core 13 | implementation libs.jems2 14 | implementation libs.jems2.confidence 15 | api rootProject 16 | 17 | testImplementation libs.confidence.test 18 | testImplementation libs.jems2.testing 19 | testImplementation libs.junit.jupiter.api 20 | testRuntimeOnly libs.junit.jupiter.engine 21 | } 22 | 23 | test { 24 | useJUnitPlatform() 25 | } -------------------------------------------------------------------------------- /lib-recur-confidence/src/main/java/org/dmfs/rfc5545/confidence/quality/EmptyRecurrenceSet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.confidence.quality; 19 | 20 | import org.dmfs.rfc5545.RecurrenceSet; 21 | import org.dmfs.srcless.annotations.staticfactory.StaticFactories; 22 | import org.saynotobugs.confidence.quality.composite.AllOf; 23 | import org.saynotobugs.confidence.quality.composite.Not; 24 | import org.saynotobugs.confidence.quality.composite.QualityComposition; 25 | import org.saynotobugs.confidence.quality.grammar.Is; 26 | import org.saynotobugs.confidence.quality.iterable.EmptyIterable; 27 | 28 | @StaticFactories(value = "Recur", packageName = "org.dmfs.rfc5545.confidence") 29 | public final class EmptyRecurrenceSet extends QualityComposition 30 | { 31 | public EmptyRecurrenceSet() 32 | { 33 | super(new AllOf<>( 34 | new Is<>(new EmptyIterable()), 35 | new Is<>(new Finite()), 36 | new Is<>(new Not<>(new Infinite())))); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib-recur-confidence/src/main/java/org/dmfs/rfc5545/confidence/quality/Finite.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.confidence.quality; 19 | 20 | import org.dmfs.rfc5545.RecurrenceSet; 21 | import org.dmfs.srcless.annotations.staticfactory.StaticFactories; 22 | import org.saynotobugs.confidence.description.Text; 23 | import org.saynotobugs.confidence.quality.composite.AllOf; 24 | import org.saynotobugs.confidence.quality.composite.Not; 25 | import org.saynotobugs.confidence.quality.composite.QualityComposition; 26 | import org.saynotobugs.confidence.quality.object.Satisfies; 27 | 28 | @StaticFactories(value = "Recur", packageName = "org.dmfs.rfc5545.confidence") 29 | public final class Finite extends QualityComposition 30 | { 31 | public Finite() 32 | { 33 | super(new AllOf<>( 34 | new Not<>(new Satisfies<>(RecurrenceSet::isInfinite, new Text("infinite"))), 35 | new Satisfies<>(RecurrenceSet::isFinite, new Text("finite")))); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib-recur-confidence/src/main/java/org/dmfs/rfc5545/confidence/quality/Infinite.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.confidence.quality; 19 | 20 | import org.dmfs.rfc5545.RecurrenceSet; 21 | import org.dmfs.srcless.annotations.staticfactory.StaticFactories; 22 | import org.saynotobugs.confidence.description.Text; 23 | import org.saynotobugs.confidence.quality.composite.AllOf; 24 | import org.saynotobugs.confidence.quality.composite.Not; 25 | import org.saynotobugs.confidence.quality.composite.QualityComposition; 26 | import org.saynotobugs.confidence.quality.object.Satisfies; 27 | 28 | @StaticFactories(value = "Recur", packageName = "org.dmfs.rfc5545.confidence") 29 | public final class Infinite extends QualityComposition 30 | { 31 | public Infinite() 32 | { 33 | super(new AllOf<>( 34 | new Not<>(new Satisfies<>(RecurrenceSet::isFinite, new Text("finite"))), 35 | new Satisfies<>(RecurrenceSet::isInfinite, new Text("infinite")))); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib-recur-confidence/src/main/java/org/dmfs/rfc5545/confidence/quality/StartsWith.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.confidence.quality; 19 | 20 | import org.dmfs.jems2.iterable.First; 21 | import org.dmfs.rfc5545.DateTime; 22 | import org.dmfs.rfc5545.RecurrenceSet; 23 | import org.dmfs.srcless.annotations.staticfactory.StaticFactories; 24 | import org.saynotobugs.confidence.quality.composite.Has; 25 | import org.saynotobugs.confidence.quality.composite.QualityComposition; 26 | import org.saynotobugs.confidence.quality.iterable.Iterates; 27 | 28 | @StaticFactories(value = "Recur", packageName = "org.dmfs.rfc5545.confidence") 29 | public final class StartsWith extends QualityComposition 30 | { 31 | public StartsWith(DateTime... instances) 32 | { 33 | super(new Has<>("first " + instances.length + " instances", 34 | candidate -> new First<>(instances.length, candidate), new Iterates<>(instances))); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib-recur-confidence/src/test/java/org/dmfs/rfc5545/confidence/quality/EmptyRecurrenceSetTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.confidence.quality; 19 | 20 | import org.dmfs.jems2.iterator.Seq; 21 | import org.dmfs.rfc5545.DateTime; 22 | import org.dmfs.rfc5545.RecurrenceSet; 23 | import org.dmfs.rfc5545.instanceiterator.EmptyIterator; 24 | import org.dmfs.rfc5545.instanceiterator.FastForwardable; 25 | import org.junit.jupiter.api.Test; 26 | 27 | import static org.dmfs.jems2.mockito.Mock.*; 28 | import static org.saynotobugs.confidence.Assertion.assertThat; 29 | import static org.saynotobugs.confidence.quality.Core.allOf; 30 | import static org.saynotobugs.confidence.test.quality.Test.fails; 31 | 32 | class EmptyRecurrenceSetTest 33 | { 34 | @Test 35 | void test() 36 | { 37 | assertThat(new EmptyRecurrenceSet(), 38 | allOf( 39 | org.saynotobugs.confidence.test.quality.Test.passes(mock(RecurrenceSet.class, 40 | with(RecurrenceSet::isInfinite, returning(false)), 41 | with(RecurrenceSet::isFinite, returning(true)), 42 | with(RecurrenceSet::iterator, returning(new EmptyIterator())))), 43 | 44 | fails(mock(RecurrenceSet.class, 45 | with(RecurrenceSet::isInfinite, returning(true)), 46 | with(RecurrenceSet::isFinite, returning(false)), 47 | with(RecurrenceSet::iterator, returning(new EmptyIterator())))), 48 | 49 | fails(mock(RecurrenceSet.class, 50 | with(RecurrenceSet::isInfinite, returning(false)), 51 | with(RecurrenceSet::isFinite, returning(true)), 52 | with(RecurrenceSet::iterator, returning( 53 | new FastForwardable( 54 | DateTime.parse("20240101"), 55 | new Seq<>(DateTime.parse("20240102"), DateTime.parse("20240103"))))))), 56 | 57 | fails(mock(RecurrenceSet.class, 58 | with(RecurrenceSet::isInfinite, returning(true)), 59 | with(RecurrenceSet::isFinite, returning(false)), 60 | with(RecurrenceSet::iterator, returning( 61 | new FastForwardable( 62 | DateTime.parse("20240101"), 63 | new Seq<>(DateTime.parse("20240102"), DateTime.parse("20240103"))))))))); 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /lib-recur-confidence/src/test/java/org/dmfs/rfc5545/confidence/quality/FiniteTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.confidence.quality; 19 | 20 | import org.dmfs.rfc5545.InstanceIterator; 21 | import org.dmfs.rfc5545.RecurrenceSet; 22 | import org.junit.jupiter.api.Test; 23 | 24 | import static org.dmfs.jems2.mockito.Mock.*; 25 | import static org.saynotobugs.confidence.Assertion.assertThat; 26 | import static org.saynotobugs.confidence.quality.Core.allOf; 27 | import static org.saynotobugs.confidence.test.quality.Test.fails; 28 | 29 | class FiniteTest 30 | { 31 | @Test 32 | void test() 33 | { 34 | assertThat(new Finite(), 35 | allOf( 36 | org.saynotobugs.confidence.test.quality.Test.passes(mock(RecurrenceSet.class, 37 | with(RecurrenceSet::isInfinite, returning(false)), 38 | with(RecurrenceSet::isFinite, returning(true)), 39 | with(RecurrenceSet::iterator, returning(mock(InstanceIterator.class))))), 40 | 41 | fails(mock(RecurrenceSet.class, 42 | with(RecurrenceSet::isInfinite, returning(true)), 43 | with(RecurrenceSet::isFinite, returning(false)), 44 | with(RecurrenceSet::iterator, returning(mock(InstanceIterator.class))))))); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /lib-recur-confidence/src/test/java/org/dmfs/rfc5545/confidence/quality/InfiniteTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.confidence.quality; 19 | 20 | import org.dmfs.rfc5545.InstanceIterator; 21 | import org.dmfs.rfc5545.RecurrenceSet; 22 | import org.junit.jupiter.api.Test; 23 | 24 | import static org.dmfs.jems2.mockito.Mock.*; 25 | import static org.saynotobugs.confidence.Assertion.assertThat; 26 | import static org.saynotobugs.confidence.quality.Core.allOf; 27 | import static org.saynotobugs.confidence.test.quality.Test.fails; 28 | 29 | class InfiniteTest 30 | { 31 | @Test 32 | void test() 33 | { 34 | assertThat(new Infinite(), 35 | allOf( 36 | org.saynotobugs.confidence.test.quality.Test.passes(mock(RecurrenceSet.class, 37 | with(RecurrenceSet::isInfinite, returning(true)), 38 | with(RecurrenceSet::isFinite, returning(false)), 39 | with(RecurrenceSet::iterator, returning(mock(InstanceIterator.class))))), 40 | 41 | fails(mock(RecurrenceSet.class, 42 | with(RecurrenceSet::isInfinite, returning(false)), 43 | with(RecurrenceSet::isFinite, returning(true)), 44 | with(RecurrenceSet::iterator, returning(mock(InstanceIterator.class))))))); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /lib-recur-confidence/src/test/java/org/dmfs/rfc5545/confidence/quality/StartsWithTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.confidence.quality; 19 | 20 | import org.dmfs.jems2.iterator.Seq; 21 | import org.dmfs.rfc5545.DateTime; 22 | import org.dmfs.rfc5545.RecurrenceSet; 23 | import org.dmfs.rfc5545.instanceiterator.EmptyIterator; 24 | import org.dmfs.rfc5545.instanceiterator.FastForwardable; 25 | import org.junit.jupiter.api.Test; 26 | 27 | import static org.dmfs.jems2.mockito.Mock.*; 28 | import static org.saynotobugs.confidence.Assertion.assertThat; 29 | import static org.saynotobugs.confidence.quality.Core.allOf; 30 | import static org.saynotobugs.confidence.test.quality.Test.fails; 31 | 32 | class StartsWithTest 33 | { 34 | @Test 35 | void test() 36 | { 37 | assertThat(new StartsWith(DateTime.parse("20240101"), DateTime.parse("20240102")), 38 | allOf( 39 | org.saynotobugs.confidence.test.quality.Test.passes(mock(RecurrenceSet.class, 40 | with(RecurrenceSet::iterator, returning(new FastForwardable( 41 | DateTime.parse("20240101"), 42 | new Seq<>(DateTime.parse("20240102"))))))), 43 | 44 | org.saynotobugs.confidence.test.quality.Test.passes(mock(RecurrenceSet.class, 45 | with(RecurrenceSet::iterator, returning(new FastForwardable( 46 | DateTime.parse("20240101"), 47 | new Seq<>( 48 | DateTime.parse("20240102"), 49 | DateTime.parse("20240103"), 50 | DateTime.parse("20240104"))))))), 51 | 52 | fails(mock(RecurrenceSet.class, 53 | with(RecurrenceSet::iterator, returning(new EmptyIterator())))), 54 | 55 | fails(mock(RecurrenceSet.class, 56 | with(RecurrenceSet::iterator, returning( 57 | new FastForwardable( 58 | DateTime.parse("20240101"), 59 | new EmptyIterator()))))), 60 | 61 | fails(mock(RecurrenceSet.class, 62 | with(RecurrenceSet::iterator, returning( 63 | new FastForwardable( 64 | DateTime.parse("20240101"), 65 | new Seq<>(DateTime.parse("20240103"), DateTime.parse("20240104"))))))), 66 | 67 | fails(mock(RecurrenceSet.class, 68 | with(RecurrenceSet::iterator, returning( 69 | new FastForwardable( 70 | DateTime.parse("20240102"), 71 | new Seq<>(DateTime.parse("20240103"), DateTime.parse("20240104"))))))))); 72 | 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /lib-recur-hamcrest/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | } 4 | 5 | sourceCompatibility = JavaVersion.VERSION_1_8 6 | targetCompatibility = JavaVersion.VERSION_1_8 7 | 8 | dependencies { 9 | testImplementation group: 'junit', name: 'junit', version: '4.12' 10 | api 'org.hamcrest:hamcrest:2.2' 11 | api 'org.dmfs:jems2-testing:2.11.1' 12 | implementation 'org.dmfs:jems2:2.11.1' 13 | api rootProject 14 | } 15 | -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/main/java/org/dmfs/rfc5545/hamcrest/GeneratorMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest; 19 | 20 | import org.dmfs.jems2.Generatable; 21 | import org.dmfs.jems2.hamcrest.matchers.generatable.GeneratableMatcher; 22 | import org.dmfs.jems2.iterable.Mapped; 23 | import org.dmfs.jems2.iterable.Seq; 24 | import org.dmfs.rfc5545.DateTime; 25 | import org.dmfs.rfc5545.recur.RecurrenceRule; 26 | import org.hamcrest.Description; 27 | import org.hamcrest.Matcher; 28 | import org.hamcrest.Matchers; 29 | import org.hamcrest.TypeSafeDiagnosingMatcher; 30 | 31 | 32 | /** 33 | * A {@link Matcher} which checks the first instances generated by a {@link RecurrenceRule}. 34 | * 35 | * @author Marten Gajda 36 | */ 37 | public final class GeneratorMatcher extends TypeSafeDiagnosingMatcher 38 | { 39 | private final DateTime mStart; 40 | private final Matcher> mInstanceMatcher; 41 | 42 | 43 | public GeneratorMatcher(DateTime start, Matcher> instancematcher) 44 | { 45 | mStart = start; 46 | mInstanceMatcher = instancematcher; 47 | } 48 | 49 | 50 | public static Matcher generates(String start, String... instances) 51 | { 52 | return generates(DateTime.parse(start), new Mapped<>(DateTime::parse, new Seq<>(instances))); 53 | } 54 | 55 | 56 | public static Matcher generates(DateTime start, Iterable instances) 57 | { 58 | return new GeneratorMatcher(start, GeneratableMatcher.startsWith(new Mapped<>(Matchers::is, instances))); 59 | } 60 | 61 | 62 | public static Matcher generates(DateTime start, Matcher> instancematcher) 63 | { 64 | return new GeneratorMatcher(start, instancematcher); 65 | } 66 | 67 | 68 | @Override 69 | protected boolean matchesSafely(RecurrenceRule item, Description mismatchDescription) 70 | { 71 | // a Generatable of the recurrence instances 72 | Generatable generatable = () -> item.iterator(mStart)::nextDateTime; 73 | 74 | if (!mInstanceMatcher.matches(generatable)) 75 | { 76 | mInstanceMatcher.describeMismatch(generatable, mismatchDescription); 77 | return false; 78 | } 79 | return true; 80 | } 81 | 82 | 83 | @Override 84 | public void describeTo(Description description) 85 | { 86 | description.appendText("generated events "); 87 | mInstanceMatcher.describeTo(description); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/main/java/org/dmfs/rfc5545/hamcrest/IncreasingMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.Duration; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule; 23 | import org.dmfs.rfc5545.recur.RecurrenceRuleIterator; 24 | import org.hamcrest.Description; 25 | import org.hamcrest.Matcher; 26 | import org.hamcrest.TypeSafeDiagnosingMatcher; 27 | 28 | import java.util.Locale; 29 | 30 | 31 | /** 32 | * A {@link Matcher} which verifies the iterated instances of a {@link RecurrenceRule} are strictly increasing. 33 | * 34 | * @author Marten Gajda 35 | */ 36 | public final class IncreasingMatcher extends TypeSafeDiagnosingMatcher 37 | { 38 | private final static int MAX_ITERATIONS = 10000; 39 | private final DateTime mStart; 40 | 41 | 42 | public IncreasingMatcher(DateTime start) 43 | { 44 | mStart = start; 45 | } 46 | 47 | 48 | public static Matcher increasing(DateTime start) 49 | { 50 | return new IncreasingMatcher(start); 51 | } 52 | 53 | 54 | @Override 55 | protected boolean matchesSafely(RecurrenceRule recurrenceRule, Description mismatchDescription) 56 | { 57 | // no instance should be before start 58 | DateTime lastInstance = mStart.addDuration(mStart.isAllDay() ? new Duration(-1, 1, 0) : new Duration(-1, 0, 1)); 59 | 60 | int count = 0; 61 | RecurrenceRuleIterator it = recurrenceRule.iterator(mStart); 62 | while (it.hasNext()) 63 | { 64 | count++; 65 | DateTime instance = it.nextDateTime(); 66 | if (!lastInstance.before(instance)) 67 | { 68 | mismatchDescription.appendText( 69 | String.format(Locale.ENGLISH, "instance number %d (%s) of rule %s was before the previous instance (%s)", 70 | count, 71 | instance.toString(), 72 | recurrenceRule.toString(), 73 | lastInstance.toString())); 74 | return false; 75 | } 76 | lastInstance = instance; 77 | 78 | if (count == MAX_ITERATIONS || instance.getYear() > 9000) 79 | { 80 | break; 81 | } 82 | } 83 | return true; 84 | } 85 | 86 | 87 | @Override 88 | public void describeTo(Description description) 89 | { 90 | description.appendText("recurrence iteration is strictly increasing"); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/main/java/org/dmfs/rfc5545/hamcrest/InstancesMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.recur.RecurrenceRule; 22 | import org.dmfs.rfc5545.recur.RecurrenceRuleIterator; 23 | import org.hamcrest.Description; 24 | import org.hamcrest.Matcher; 25 | import org.hamcrest.TypeSafeDiagnosingMatcher; 26 | 27 | 28 | /** 29 | * A {@link Matcher} which checks the instances generated by a {@link RecurrenceRule}. 30 | * 31 | * @author Marten Gajda 32 | */ 33 | public final class InstancesMatcher extends TypeSafeDiagnosingMatcher 34 | { 35 | private final static int MAX_ITERATIONS = 10000; 36 | private final DateTime mStart; 37 | private final Matcher mInstancesMatcher; 38 | 39 | 40 | public InstancesMatcher(DateTime start, Matcher instancesMatcher) 41 | { 42 | mStart = start; 43 | mInstancesMatcher = instancesMatcher; 44 | } 45 | 46 | 47 | public static Matcher instances(DateTime start, Matcher instancesMatcher) 48 | { 49 | return new InstancesMatcher(start, instancesMatcher); 50 | } 51 | 52 | 53 | @Override 54 | protected boolean matchesSafely(RecurrenceRule recurrenceRule, Description mismatchDescription) 55 | { 56 | int count = 0; 57 | RecurrenceRuleIterator it = recurrenceRule.iterator(mStart); 58 | while (it.hasNext()) 59 | { 60 | count++; 61 | DateTime instance = it.nextDateTime(); 62 | if (!mInstancesMatcher.matches(instance)) 63 | { 64 | mismatchDescription.appendText(String.format("instance %s ", instance.toString())); 65 | mInstancesMatcher.describeMismatch(instance, mismatchDescription); 66 | return false; 67 | } 68 | 69 | if (count == MAX_ITERATIONS || instance.getYear() > 9000) 70 | { 71 | break; 72 | } 73 | } 74 | return true; 75 | } 76 | 77 | 78 | @Override 79 | public void describeTo(Description description) 80 | { 81 | description.appendText("instances "); 82 | mInstancesMatcher.describeTo(description); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/main/java/org/dmfs/rfc5545/hamcrest/ResultsMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.recur.RecurrenceRule; 22 | import org.dmfs.rfc5545.recur.RecurrenceRuleIterator; 23 | import org.hamcrest.Description; 24 | import org.hamcrest.Matcher; 25 | import org.hamcrest.TypeSafeDiagnosingMatcher; 26 | 27 | import static org.hamcrest.Matchers.is; 28 | 29 | 30 | /** 31 | * A {@link Matcher} which checks the number of instances generated by a {@link RecurrenceRule}. 32 | *

33 | * Not to be used with infinite rules! 34 | * 35 | * @author Marten Gajda 36 | */ 37 | public final class ResultsMatcher extends TypeSafeDiagnosingMatcher 38 | { 39 | private final DateTime mStart; 40 | private final Matcher mCountMatcher; 41 | 42 | 43 | public ResultsMatcher(DateTime start, Matcher countMatcher) 44 | { 45 | mStart = start; 46 | mCountMatcher = countMatcher; 47 | } 48 | 49 | 50 | public static Matcher results(DateTime start, Matcher count) 51 | { 52 | return new ResultsMatcher(start, is(count)); 53 | } 54 | 55 | 56 | public static Matcher results(DateTime start, int count) 57 | { 58 | return new ResultsMatcher(start, is(count)); 59 | } 60 | 61 | 62 | @Override 63 | protected boolean matchesSafely(RecurrenceRule recurrenceRule, Description mismatchDescription) 64 | { 65 | int count = 0; 66 | RecurrenceRuleIterator it = recurrenceRule.iterator(mStart); 67 | while (it.hasNext()) 68 | { 69 | it.nextDateTime(); 70 | count++; 71 | } 72 | if (!mCountMatcher.matches(count)) 73 | { 74 | mismatchDescription.appendText("number of instances "); 75 | mCountMatcher.describeMismatch(count, mismatchDescription); 76 | return false; 77 | } 78 | return true; 79 | } 80 | 81 | 82 | @Override 83 | public void describeTo(Description description) 84 | { 85 | description.appendText("number of instances "); 86 | mCountMatcher.describeTo(description); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/main/java/org/dmfs/rfc5545/hamcrest/datetime/AfterMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest.datetime; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.hamcrest.Description; 22 | import org.hamcrest.Matcher; 23 | import org.hamcrest.TypeSafeDiagnosingMatcher; 24 | 25 | 26 | /** 27 | * Matches if a {@link DateTime} is after a given date. 28 | * 29 | * @author Marten Gajda 30 | */ 31 | public final class AfterMatcher extends TypeSafeDiagnosingMatcher 32 | { 33 | private final DateTime mDateTime; 34 | 35 | 36 | public AfterMatcher(DateTime referenceDate) 37 | { 38 | mDateTime = referenceDate; 39 | } 40 | 41 | 42 | public static Matcher after(String dateTime) 43 | { 44 | return after(DateTime.parse(dateTime)); 45 | } 46 | 47 | 48 | public static Matcher after(DateTime dateTime) 49 | { 50 | return new AfterMatcher(dateTime); 51 | } 52 | 53 | 54 | @Override 55 | protected boolean matchesSafely(DateTime item, Description mismatchDescription) 56 | { 57 | if (!item.after(mDateTime)) 58 | { 59 | mismatchDescription.appendText(String.format("not after %s", mDateTime)); 60 | return false; 61 | } 62 | return true; 63 | } 64 | 65 | 66 | @Override 67 | public void describeTo(Description description) 68 | { 69 | description.appendText(String.format("after %s", mDateTime)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/main/java/org/dmfs/rfc5545/hamcrest/datetime/BeforeMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest.datetime; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.hamcrest.Description; 22 | import org.hamcrest.Matcher; 23 | import org.hamcrest.TypeSafeDiagnosingMatcher; 24 | 25 | 26 | /** 27 | * Matches if a {@link DateTime} is before a given date. 28 | * 29 | * @author Marten Gajda 30 | */ 31 | public final class BeforeMatcher extends TypeSafeDiagnosingMatcher 32 | { 33 | private final DateTime mDateTime; 34 | 35 | 36 | public BeforeMatcher(DateTime referenceDate) 37 | { 38 | mDateTime = referenceDate; 39 | } 40 | 41 | 42 | public static Matcher before(String dateTime) 43 | { 44 | return before(DateTime.parse(dateTime)); 45 | } 46 | 47 | 48 | public static Matcher before(DateTime dateTime) 49 | { 50 | return new BeforeMatcher(dateTime); 51 | } 52 | 53 | 54 | @Override 55 | protected boolean matchesSafely(DateTime item, Description mismatchDescription) 56 | { 57 | if (!item.before(mDateTime)) 58 | { 59 | mismatchDescription.appendText(String.format("not before %s", mDateTime)); 60 | return false; 61 | } 62 | return true; 63 | } 64 | 65 | 66 | @Override 67 | public void describeTo(Description description) 68 | { 69 | description.appendText(String.format("before %s", mDateTime)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/main/java/org/dmfs/rfc5545/hamcrest/datetime/DayOfMonthMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest.datetime; 19 | 20 | import org.dmfs.jems2.iterable.Mapped; 21 | import org.dmfs.jems2.iterable.Seq; 22 | import org.dmfs.rfc5545.DateTime; 23 | import org.hamcrest.FeatureMatcher; 24 | import org.hamcrest.Matcher; 25 | import org.hamcrest.Matchers; 26 | import org.hamcrest.core.AnyOf; 27 | 28 | 29 | /** 30 | * Matches the day of month of a {@link DateTime}. 31 | * 32 | * @author Marten Gajda 33 | */ 34 | public final class DayOfMonthMatcher extends FeatureMatcher 35 | { 36 | 37 | /** 38 | * Constructor 39 | * 40 | * @param subMatcher 41 | * The matcher to apply to the feature 42 | */ 43 | public DayOfMonthMatcher(Matcher subMatcher) 44 | { 45 | super(subMatcher, "day of month", "day of month"); 46 | } 47 | 48 | 49 | public static Matcher onDayOfMonth(Matcher dayMatcher) 50 | { 51 | return new DayOfMonthMatcher(dayMatcher); 52 | } 53 | 54 | 55 | public static Matcher onDayOfMonth(Integer... days) 56 | { 57 | return new DayOfMonthMatcher(new AnyOf<>(new Mapped<>(Matchers::equalTo, new Seq<>(days)))); 58 | } 59 | 60 | 61 | @Override 62 | protected Integer featureValueOf(DateTime actual) 63 | { 64 | return actual.getDayOfMonth(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/main/java/org/dmfs/rfc5545/hamcrest/datetime/DayOfYearMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest.datetime; 19 | 20 | import org.dmfs.jems2.iterable.Mapped; 21 | import org.dmfs.jems2.iterable.Seq; 22 | import org.dmfs.rfc5545.DateTime; 23 | import org.hamcrest.FeatureMatcher; 24 | import org.hamcrest.Matcher; 25 | import org.hamcrest.Matchers; 26 | import org.hamcrest.core.AnyOf; 27 | 28 | 29 | /** 30 | * @author Marten Gajda 31 | */ 32 | public final class DayOfYearMatcher extends FeatureMatcher 33 | { 34 | /** 35 | * Constructor 36 | * 37 | * @param subMatcher 38 | * The matcher to apply to the feature 39 | */ 40 | public DayOfYearMatcher(Matcher subMatcher) 41 | { 42 | super(subMatcher, "day of year", "day of year"); 43 | } 44 | 45 | 46 | public static Matcher onDayOfYear(Matcher dayMatcher) 47 | { 48 | return new DayOfYearMatcher(dayMatcher); 49 | } 50 | 51 | 52 | public static Matcher onDayOfYear(Integer... days) 53 | { 54 | return new DayOfYearMatcher(new AnyOf<>(new Mapped<>(Matchers::equalTo, new Seq<>(days)))); 55 | } 56 | 57 | 58 | @Override 59 | protected Integer featureValueOf(DateTime actual) 60 | { 61 | return actual.getCalendarMetrics().getDayOfYear(actual.getYear(), actual.getMonth(), actual.getDayOfMonth()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/main/java/org/dmfs/rfc5545/hamcrest/datetime/MonthMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest.datetime; 19 | 20 | import org.dmfs.jems2.iterable.Mapped; 21 | import org.dmfs.jems2.iterable.Seq; 22 | import org.dmfs.rfc5545.DateTime; 23 | import org.hamcrest.FeatureMatcher; 24 | import org.hamcrest.Matcher; 25 | import org.hamcrest.Matchers; 26 | import org.hamcrest.core.AnyOf; 27 | 28 | 29 | /** 30 | * @author Marten Gajda 31 | */ 32 | public final class MonthMatcher extends FeatureMatcher 33 | { 34 | /** 35 | * Constructor 36 | * 37 | * @param subMatcher 38 | * The matcher to apply to the feature 39 | */ 40 | public MonthMatcher(Matcher subMatcher) 41 | { 42 | super(subMatcher, "month", "month"); 43 | } 44 | 45 | 46 | public static Matcher inMonth(Matcher monthMatcher) 47 | { 48 | return new MonthMatcher(monthMatcher); 49 | } 50 | 51 | 52 | public static Matcher inMonth(Integer... months) 53 | { 54 | return new MonthMatcher(new AnyOf<>(new Mapped<>(Matchers::equalTo, new Seq<>(months)))); 55 | } 56 | 57 | 58 | @Override 59 | protected Integer featureValueOf(DateTime actual) 60 | { 61 | return actual.getMonth() + 1; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/main/java/org/dmfs/rfc5545/hamcrest/datetime/WeekDayMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest.datetime; 19 | 20 | import org.dmfs.jems2.iterable.Mapped; 21 | import org.dmfs.jems2.iterable.Seq; 22 | import org.dmfs.rfc5545.DateTime; 23 | import org.dmfs.rfc5545.Weekday; 24 | import org.hamcrest.FeatureMatcher; 25 | import org.hamcrest.Matcher; 26 | import org.hamcrest.Matchers; 27 | import org.hamcrest.core.AnyOf; 28 | 29 | 30 | /** 31 | * @author Marten Gajda 32 | */ 33 | public final class WeekDayMatcher extends FeatureMatcher 34 | { 35 | /** 36 | * Constructor 37 | * 38 | * @param subMatcher 39 | * The matcher to apply to the feature 40 | */ 41 | public WeekDayMatcher(Matcher subMatcher) 42 | { 43 | super(subMatcher, "weekday", "weekday"); 44 | } 45 | 46 | 47 | public static Matcher onWeekDay(Matcher weekdayMatcher) 48 | { 49 | return new WeekDayMatcher(weekdayMatcher); 50 | } 51 | 52 | 53 | public static Matcher onWeekDay(Weekday... weekdays) 54 | { 55 | return new WeekDayMatcher(new AnyOf<>(new Mapped<>(Matchers::equalTo, new Seq<>(weekdays)))); 56 | } 57 | 58 | 59 | @Override 60 | protected Weekday featureValueOf(DateTime actual) 61 | { 62 | return Weekday.values()[actual.getCalendarMetrics().getDayOfWeek(actual.getYear(), actual.getMonth(), actual.getDayOfMonth())]; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/main/java/org/dmfs/rfc5545/hamcrest/datetime/WeekOfYearMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest.datetime; 19 | 20 | import org.dmfs.jems2.iterable.Mapped; 21 | import org.dmfs.jems2.iterable.Seq; 22 | import org.dmfs.rfc5545.DateTime; 23 | import org.hamcrest.FeatureMatcher; 24 | import org.hamcrest.Matcher; 25 | import org.hamcrest.Matchers; 26 | import org.hamcrest.core.AnyOf; 27 | 28 | 29 | /** 30 | * Matches the week of year of a {@link DateTime}. 31 | * 32 | * @author Marten Gajda 33 | */ 34 | public final class WeekOfYearMatcher extends FeatureMatcher 35 | { 36 | 37 | /** 38 | * Constructor 39 | * 40 | * @param subMatcher 41 | * The matcher to apply to the feature 42 | */ 43 | public WeekOfYearMatcher(Matcher subMatcher) 44 | { 45 | super(subMatcher, "week of year", "week of year"); 46 | } 47 | 48 | 49 | public static Matcher inWeekOfYear(Matcher weekOfYearMatcher) 50 | { 51 | return new WeekOfYearMatcher(weekOfYearMatcher); 52 | } 53 | 54 | 55 | public static Matcher inWeekOfYear(Integer... weekOfYear) 56 | { 57 | return new WeekOfYearMatcher(new AnyOf<>(new Mapped<>(Matchers::equalTo, new Seq<>(weekOfYear)))); 58 | } 59 | 60 | 61 | @Override 62 | protected Integer featureValueOf(DateTime actual) 63 | { 64 | return actual.getWeekOfYear(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/main/java/org/dmfs/rfc5545/hamcrest/datetime/YearMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest.datetime; 19 | 20 | import org.dmfs.jems2.iterable.Mapped; 21 | import org.dmfs.jems2.iterable.Seq; 22 | import org.dmfs.rfc5545.DateTime; 23 | import org.hamcrest.FeatureMatcher; 24 | import org.hamcrest.Matcher; 25 | import org.hamcrest.Matchers; 26 | import org.hamcrest.core.AnyOf; 27 | 28 | 29 | /** 30 | * Matches the year of a {@link DateTime}. 31 | * 32 | * @author Marten Gajda 33 | */ 34 | public final class YearMatcher extends FeatureMatcher 35 | { 36 | 37 | /** 38 | * Constructor 39 | * 40 | * @param subMatcher 41 | * The matcher to apply to the feature 42 | */ 43 | public YearMatcher(Matcher subMatcher) 44 | { 45 | super(subMatcher, "year", "year"); 46 | } 47 | 48 | 49 | public static Matcher inYear(Matcher yearMatcher) 50 | { 51 | return new YearMatcher(yearMatcher); 52 | } 53 | 54 | 55 | public static Matcher inYear(Integer... years) 56 | { 57 | return new YearMatcher(new AnyOf<>(new Mapped<>(Matchers::equalTo, new Seq<>(years)))); 58 | } 59 | 60 | 61 | @Override 62 | protected Integer featureValueOf(DateTime actual) 63 | { 64 | return actual.getYear(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/test/java/org/dmfs/rfc5545/hamcrest/ResultsMatcherTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.recur.RecurrenceRule; 22 | import org.hamcrest.Matcher; 23 | import org.hamcrest.core.AllOf; 24 | import org.junit.Test; 25 | 26 | import static org.dmfs.jems2.hamcrest.matchers.matcher.MatcherMatcher.*; 27 | import static org.dmfs.rfc5545.hamcrest.ResultsMatcher.results; 28 | import static org.hamcrest.Matchers.lessThan; 29 | import static org.junit.Assert.assertThat; 30 | 31 | 32 | /** 33 | * Unit test for {@link ResultsMatcher}. 34 | *

35 | * FIXME: this is circular reasoning, fix when RecurrenceRule and RecurrenceRuleIterator are mockable 36 | * 37 | * @author Marten Gajda 38 | */ 39 | public class ResultsMatcherTest 40 | { 41 | @Test 42 | public void test() throws Exception 43 | { 44 | assertThat(results(DateTime.parse("20180101"), 10), 45 | AllOf.>allOf( 46 | matches(new RecurrenceRule("FREQ=DAILY;COUNT=10")), 47 | mismatches(new RecurrenceRule("FREQ=DAILY;COUNT=9"), "number of instances was <9>"), 48 | mismatches(new RecurrenceRule("FREQ=DAILY;COUNT=12"), "number of instances was <12>"), 49 | describesAs("number of instances is <10>") 50 | )); 51 | 52 | assertThat(results(DateTime.parse("20180101"), lessThan(20)), 53 | AllOf.>allOf( 54 | matches(new RecurrenceRule("FREQ=DAILY;COUNT=1")), 55 | matches(new RecurrenceRule("FREQ=DAILY;COUNT=19")), 56 | mismatches(new RecurrenceRule("FREQ=DAILY;COUNT=20"), "number of instances <20> was equal to <20>"), 57 | mismatches(new RecurrenceRule("FREQ=DAILY;COUNT=21"), "number of instances <21> was greater than <20>"), 58 | describesAs("number of instances is a value less than <20>") 59 | )); 60 | } 61 | } -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/test/java/org/dmfs/rfc5545/hamcrest/datetime/AfterMatcherTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest.datetime; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.hamcrest.Matcher; 22 | import org.hamcrest.core.AllOf; 23 | import org.junit.Test; 24 | 25 | import static org.dmfs.jems2.hamcrest.matchers.matcher.MatcherMatcher.*; 26 | import static org.dmfs.rfc5545.hamcrest.datetime.AfterMatcher.after; 27 | import static org.junit.Assert.assertThat; 28 | 29 | 30 | /** 31 | * Unit test for {@link org.dmfs.rfc5545.hamcrest.datetime.AfterMatcher}. 32 | * 33 | * @author Marten Gajda 34 | */ 35 | public class AfterMatcherTest 36 | { 37 | 38 | @Test 39 | public void test() throws Exception 40 | { 41 | assertThat(after(DateTime.parse("20180101T010000Z")), 42 | AllOf.>allOf( 43 | matches(DateTime.parse("20180101T010001Z")), 44 | mismatches(DateTime.parse("20180101T005959Z"), "not after 20180101T010000Z"), 45 | mismatches(DateTime.parse("20180101T010000Z"), "not after 20180101T010000Z"), 46 | describesAs("after 20180101T010000Z") 47 | )); 48 | 49 | assertThat(after("20180101T010000Z"), 50 | AllOf.>allOf( 51 | matches(DateTime.parse("20180101T010001Z")), 52 | mismatches(DateTime.parse("20180101T005959Z"), "not after 20180101T010000Z"), 53 | mismatches(DateTime.parse("20180101T010000Z"), "not after 20180101T010000Z"), 54 | describesAs("after 20180101T010000Z") 55 | )); 56 | } 57 | } -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/test/java/org/dmfs/rfc5545/hamcrest/datetime/BeforeMatcherTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest.datetime; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.hamcrest.Matcher; 22 | import org.hamcrest.core.AllOf; 23 | import org.junit.Test; 24 | 25 | import static org.dmfs.jems2.hamcrest.matchers.matcher.MatcherMatcher.*; 26 | import static org.dmfs.rfc5545.hamcrest.datetime.BeforeMatcher.before; 27 | import static org.junit.Assert.assertThat; 28 | 29 | 30 | /** 31 | * Unit test for {@link BeforeMatcher}. 32 | * 33 | * @author Marten Gajda 34 | */ 35 | public class BeforeMatcherTest 36 | { 37 | 38 | @Test 39 | public void test() throws Exception 40 | { 41 | assertThat(before(DateTime.parse("20180101T010000Z")), 42 | AllOf.>allOf( 43 | matches(DateTime.parse("20180101T000059Z")), 44 | mismatches(DateTime.parse("20180101T010001Z"), "not before 20180101T010000Z"), 45 | mismatches(DateTime.parse("20180101T010000Z"), "not before 20180101T010000Z"), 46 | describesAs("before 20180101T010000Z") 47 | )); 48 | 49 | assertThat(before("20180101T010000Z"), 50 | AllOf.>allOf( 51 | matches(DateTime.parse("20180101T000059Z")), 52 | mismatches(DateTime.parse("20180101T010001Z"), "not before 20180101T010000Z"), 53 | mismatches(DateTime.parse("20180101T010000Z"), "not before 20180101T010000Z"), 54 | describesAs("before 20180101T010000Z") 55 | )); 56 | } 57 | } -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/test/java/org/dmfs/rfc5545/hamcrest/datetime/DayOfMonthMatcherTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest.datetime; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.hamcrest.Matcher; 22 | import org.hamcrest.core.AllOf; 23 | import org.junit.Test; 24 | 25 | import static org.dmfs.jems2.hamcrest.matchers.matcher.MatcherMatcher.*; 26 | import static org.dmfs.rfc5545.hamcrest.datetime.DayOfMonthMatcher.onDayOfMonth; 27 | import static org.junit.Assert.assertThat; 28 | 29 | 30 | /** 31 | * Unit test for {@link DayOfMonthMatcher}. 32 | * 33 | * @author Marten Gajda 34 | */ 35 | public class DayOfMonthMatcherTest 36 | { 37 | 38 | @Test 39 | public void test() throws Exception 40 | { 41 | assertThat(onDayOfMonth(10), 42 | AllOf.>allOf( 43 | matches(DateTime.parse("20180710T010001Z")), 44 | mismatches(DateTime.parse("20181001T005959Z"), "day of month was <1>"), 45 | mismatches(DateTime.parse("20181011T010000Z"), "day of month was <11>"), 46 | describesAs("day of month (<10>)") 47 | )); 48 | assertThat(onDayOfMonth(6, 8, 10), 49 | AllOf.>allOf( 50 | matches(DateTime.parse("20180706T010001Z")), 51 | matches(DateTime.parse("20180708T010002Z")), 52 | matches(DateTime.parse("20180710T010003Z")), 53 | mismatches(DateTime.parse("20180605T005959Z"), "day of month was <5>"), 54 | mismatches(DateTime.parse("20180811T005958Z"), "day of month was <11>"), 55 | mismatches(DateTime.parse("20181009T005957Z"), "day of month was <9>"), 56 | mismatches(DateTime.parse("20180907T010000Z"), "day of month was <7>"), 57 | describesAs("day of month (<6> or <8> or <10>)") 58 | )); 59 | } 60 | } -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/test/java/org/dmfs/rfc5545/hamcrest/datetime/DayOfYearMatcherTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest.datetime; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.hamcrest.Matcher; 22 | import org.hamcrest.core.AllOf; 23 | import org.junit.Test; 24 | 25 | import static org.dmfs.jems2.hamcrest.matchers.matcher.MatcherMatcher.*; 26 | import static org.dmfs.rfc5545.hamcrest.datetime.DayOfYearMatcher.onDayOfYear; 27 | import static org.junit.Assert.assertThat; 28 | 29 | 30 | /** 31 | * Unit test for {@link DayOfYearMatcher}. 32 | * 33 | * @author Marten Gajda 34 | */ 35 | public class DayOfYearMatcherTest 36 | { 37 | 38 | @Test 39 | public void test() throws Exception 40 | { 41 | assertThat(onDayOfYear(10), 42 | AllOf.>allOf( 43 | matches(DateTime.parse("20180110T010001Z")), 44 | mismatches(DateTime.parse("20180111T005959Z"), "day of year was <11>"), 45 | mismatches(DateTime.parse("20180109T010000Z"), "day of year was <9>"), 46 | describesAs("day of year (<10>)") 47 | )); 48 | assertThat(onDayOfYear(1, 8, 365), 49 | AllOf.>allOf( 50 | matches(DateTime.parse("20180101T010001Z")), 51 | matches(DateTime.parse("20180108T010002Z")), 52 | matches(DateTime.parse("20181231T010003Z")), 53 | mismatches(DateTime.parse("20180102T005959Z"), "day of year was <2>"), 54 | mismatches(DateTime.parse("20180107T005959Z"), "day of year was <7>"), 55 | mismatches(DateTime.parse("20180109T005959Z"), "day of year was <9>"), 56 | mismatches(DateTime.parse("20180228T005958Z"), "day of year was <59>"), 57 | mismatches(DateTime.parse("20181230T005957Z"), "day of year was <364>"), 58 | describesAs("day of year (<1> or <8> or <365>)") 59 | )); 60 | } 61 | } -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/test/java/org/dmfs/rfc5545/hamcrest/datetime/MonthMatcherTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest.datetime; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.hamcrest.Matcher; 22 | import org.hamcrest.core.AllOf; 23 | import org.junit.Test; 24 | 25 | import static org.dmfs.jems2.hamcrest.matchers.matcher.MatcherMatcher.*; 26 | import static org.dmfs.rfc5545.hamcrest.datetime.MonthMatcher.inMonth; 27 | import static org.junit.Assert.assertThat; 28 | 29 | 30 | /** 31 | * Unit test for {@link MonthMatcher}. 32 | * 33 | * @author Marten Gajda 34 | */ 35 | public class MonthMatcherTest 36 | { 37 | 38 | @Test 39 | public void test() throws Exception 40 | { 41 | assertThat(inMonth(10), 42 | AllOf.>allOf( 43 | matches(DateTime.parse("20181001T010001Z")), 44 | mismatches(DateTime.parse("20180901T005959Z"), "month was <9>"), 45 | mismatches(DateTime.parse("20181101T010000Z"), "month was <11>"), 46 | describesAs("month (<10>)") 47 | )); 48 | assertThat(inMonth(6, 8, 10), 49 | AllOf.>allOf( 50 | matches(DateTime.parse("20181001T010001Z")), 51 | matches(DateTime.parse("20180801T010002Z")), 52 | matches(DateTime.parse("20180601T010003Z")), 53 | mismatches(DateTime.parse("20180501T005959Z"), "month was <5>"), 54 | mismatches(DateTime.parse("20180701T005958Z"), "month was <7>"), 55 | mismatches(DateTime.parse("20180901T005957Z"), "month was <9>"), 56 | mismatches(DateTime.parse("20181101T010000Z"), "month was <11>"), 57 | describesAs("month (<6> or <8> or <10>)") 58 | )); 59 | } 60 | } -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/test/java/org/dmfs/rfc5545/hamcrest/datetime/WeekDayMatcherTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest.datetime; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.Weekday; 22 | import org.hamcrest.Matcher; 23 | import org.hamcrest.core.AllOf; 24 | import org.junit.Test; 25 | 26 | import static org.dmfs.jems2.hamcrest.matchers.matcher.MatcherMatcher.*; 27 | import static org.dmfs.rfc5545.hamcrest.datetime.WeekDayMatcher.onWeekDay; 28 | import static org.junit.Assert.assertThat; 29 | 30 | 31 | /** 32 | * Unit test for {@link WeekDayMatcher}. 33 | * 34 | * @author Marten Gajda 35 | */ 36 | public class WeekDayMatcherTest 37 | { 38 | @Test 39 | public void test() throws Exception 40 | { 41 | assertThat(onWeekDay(Weekday.WE), 42 | AllOf.>allOf( 43 | matches(DateTime.parse("20180808T010001Z")), 44 | mismatches(DateTime.parse("20180809T005959Z"), "weekday was "), 45 | mismatches(DateTime.parse("20180807T010000Z"), "weekday was "), 46 | describesAs("weekday ()") 47 | )); 48 | assertThat(onWeekDay(Weekday.MO, Weekday.WE, Weekday.FR), 49 | AllOf.>allOf( 50 | matches(DateTime.parse("20180808T010001Z")), 51 | matches(DateTime.parse("20180806T010002Z")), 52 | matches(DateTime.parse("20180810T010003Z")), 53 | mismatches(DateTime.parse("20180805T005959Z"), "weekday was "), 54 | mismatches(DateTime.parse("20180804T005958Z"), "weekday was "), 55 | mismatches(DateTime.parse("20180814T005957Z"), "weekday was "), 56 | mismatches(DateTime.parse("20180816T010000Z"), "weekday was "), 57 | describesAs("weekday ( or or )") 58 | )); 59 | 60 | } 61 | } -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/test/java/org/dmfs/rfc5545/hamcrest/datetime/WeekOfYearMatcherTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest.datetime; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.hamcrest.Matcher; 22 | import org.hamcrest.core.AllOf; 23 | import org.junit.Test; 24 | 25 | import static org.dmfs.jems2.hamcrest.matchers.matcher.MatcherMatcher.*; 26 | import static org.dmfs.rfc5545.hamcrest.datetime.WeekOfYearMatcher.inWeekOfYear; 27 | import static org.junit.Assert.assertThat; 28 | 29 | 30 | /** 31 | * Unit test for {@link WeekOfYearMatcher}. 32 | * 33 | * @author Marten Gajda 34 | */ 35 | public class WeekOfYearMatcherTest 36 | { 37 | @Test 38 | public void test() throws Exception 39 | { 40 | assertThat(inWeekOfYear(16), 41 | AllOf.>allOf( 42 | matches(DateTime.parse("20180418T010001Z")), 43 | mismatches(DateTime.parse("20180415T005959Z"), "week of year was <15>"), 44 | mismatches(DateTime.parse("20180423T010000Z"), "week of year was <17>"), 45 | describesAs("week of year (<16>)") 46 | )); 47 | assertThat(inWeekOfYear(1, 16, 52), 48 | AllOf.>allOf( 49 | matches(DateTime.parse("20180101T010001Z")), 50 | matches(DateTime.parse("20180418T010001Z")), 51 | matches(DateTime.parse("20181230T010001Z")), 52 | matches(DateTime.parse("20181231T010001Z")), 53 | matches(DateTime.parse("20190101T010001Z")), 54 | mismatches(DateTime.parse("20180108T010001Z"), "week of year was <2>"), 55 | mismatches(DateTime.parse("20180415T005959Z"), "week of year was <15>"), 56 | mismatches(DateTime.parse("20180423T010000Z"), "week of year was <17>"), 57 | mismatches(DateTime.parse("20181223T010001Z"), "week of year was <51>"), 58 | mismatches(DateTime.parse("20181223T010001Z"), "week of year was <51>"), 59 | mismatches(DateTime.parse("20190107T010001Z"), "week of year was <2>"), 60 | describesAs("week of year (<1> or <16> or <52>)") 61 | )); 62 | 63 | } 64 | } -------------------------------------------------------------------------------- /lib-recur-hamcrest/src/test/java/org/dmfs/rfc5545/hamcrest/datetime/YearMatcherTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.hamcrest.datetime; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.hamcrest.Matcher; 22 | import org.hamcrest.core.AllOf; 23 | import org.junit.Test; 24 | 25 | import static org.dmfs.jems2.hamcrest.matchers.matcher.MatcherMatcher.*; 26 | import static org.dmfs.rfc5545.hamcrest.datetime.YearMatcher.inYear; 27 | import static org.junit.Assert.assertThat; 28 | 29 | 30 | /** 31 | * Unit test for {@link YearMatcher}. 32 | * 33 | * @author Marten Gajda 34 | */ 35 | public class YearMatcherTest 36 | { 37 | @Test 38 | public void test() throws Exception 39 | { 40 | assertThat(inYear(2018), 41 | AllOf.>allOf( 42 | matches(DateTime.parse("20181001T010001Z")), 43 | mismatches(DateTime.parse("20210901T005959Z"), "year was <2021>"), 44 | mismatches(DateTime.parse("20171101T010000Z"), "year was <2017>"), 45 | describesAs("year (<2018>)") 46 | )); 47 | assertThat(inYear(2018, 2019, 2020), 48 | AllOf.>allOf( 49 | matches(DateTime.parse("20181001T010001Z")), 50 | matches(DateTime.parse("20190801T010002Z")), 51 | matches(DateTime.parse("20200601T010003Z")), 52 | mismatches(DateTime.parse("20110501T005959Z"), "year was <2011>"), 53 | mismatches(DateTime.parse("20170701T005958Z"), "year was <2017>"), 54 | mismatches(DateTime.parse("20210901T005957Z"), "year was <2021>"), 55 | mismatches(DateTime.parse("20301101T010000Z"), "year was <2030>"), 56 | describesAs("year (<2018> or <2019> or <2020>)") 57 | )); 58 | 59 | } 60 | } -------------------------------------------------------------------------------- /publish.gradle: -------------------------------------------------------------------------------- 1 | if (project.hasProperty('SONATYPE_USERNAME') && project.hasProperty('SONATYPE_PASSWORD')) { 2 | 3 | apply plugin: 'maven-publish' 4 | apply plugin: 'signing' 5 | 6 | task javadocJar(type: Jar, dependsOn: javadoc) { 7 | classifier = 'javadoc' 8 | from javadoc.destinationDir 9 | } 10 | 11 | task sourceJar(type: Jar) { 12 | classifier "sources" 13 | from sourceSets.main.allJava 14 | } 15 | 16 | publishing { 17 | 18 | publications { 19 | jar(MavenPublication) { 20 | from components.java 21 | artifact sourceJar 22 | artifact javadocJar 23 | 24 | pom { 25 | name = project.name 26 | description = POM_DESCRIPTION 27 | url = POM_PROJECT_URL 28 | scm { 29 | url = POM_SCM_URL 30 | connection = POM_SCM_CONNECTION 31 | developerConnection = POM_SCM_DEVELOPER_CONNECTION 32 | } 33 | licenses { 34 | license { 35 | name = POM_LICENSE 36 | url = POM_LICENSE_URL 37 | } 38 | } 39 | developers { 40 | developer { 41 | id = POM_DEVELOPER_ID 42 | name = POM_DEVELOPER_NAME 43 | email = POM_DEVELOPER_EMAIL 44 | organization = POM_DEVELOPER_ORGANIZATION 45 | organizationUrl = POM_DEVELOPER_ORGANIZATION_URL 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | signing { 54 | useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) 55 | sign publishing.publications 56 | } 57 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'lib-recur' 2 | include 'lib-recur-confidence' 3 | include 'lib-recur-hamcrest' 4 | include 'benchmark' 5 | 6 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/InstanceIterator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545; 19 | 20 | import java.util.Iterator; 21 | 22 | public interface InstanceIterator extends Iterator 23 | { 24 | /** 25 | * Skip all occurrences until {@code until}. If {@code until} is an occurrence itself it will be the next iterated occurrence. 26 | * If the rule doesn't recur till that date the next call to {@link #hasNext()} will return {@code false}. 27 | */ 28 | void fastForward(DateTime until); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/RecurrenceSet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545; 19 | 20 | 21 | import org.dmfs.srcless.annotations.composable.Composable; 22 | 23 | /** 24 | * A set of instances. 25 | */ 26 | @Composable 27 | public interface RecurrenceSet extends Iterable 28 | { 29 | /** 30 | * Returns an {@link InstanceIterator} for this {@link RecurrenceSet}. 31 | */ 32 | InstanceIterator iterator(); 33 | 34 | /** 35 | * Returns whether this {@link RecurrenceSet} is infinite or not. 36 | * 37 | * @deprecated in favour of {@link #isFinite()} 38 | */ 39 | @Deprecated(forRemoval = true) 40 | boolean isInfinite(); 41 | 42 | /** 43 | * Returns whether this {@link RecurrenceSet} is finite. 44 | */ 45 | default boolean isFinite() 46 | { 47 | return !isInfinite(); 48 | } 49 | } -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/instanceiterator/CountLimitedRecurrenceRuleIterator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.instanceiterator; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.InstanceIterator; 22 | import org.dmfs.rfc5545.recur.RecurrenceRuleIterator; 23 | 24 | import java.util.NoSuchElementException; 25 | 26 | 27 | /** 28 | * An {@link InstanceIterator} which limits the number of iterated instances. 29 | */ 30 | public final class CountLimitedRecurrenceRuleIterator implements InstanceIterator 31 | { 32 | private final RecurrenceRuleIterator mDelegate; 33 | private int mRemaining; 34 | 35 | 36 | public CountLimitedRecurrenceRuleIterator(RecurrenceRuleIterator delegate, int remaining) 37 | { 38 | mDelegate = delegate; 39 | mRemaining = remaining; 40 | } 41 | 42 | 43 | @Override 44 | public boolean hasNext() 45 | { 46 | return mRemaining > 0 && mDelegate.hasNext(); 47 | } 48 | 49 | 50 | @Override 51 | public DateTime next() 52 | { 53 | if (!hasNext()) 54 | { 55 | throw new NoSuchElementException("No further elements to iterate"); 56 | } 57 | mRemaining--; 58 | return mDelegate.nextDateTime(); 59 | } 60 | 61 | 62 | @Override 63 | public void fastForward(DateTime until) 64 | { 65 | while (hasNext() && mDelegate.peekMillis() < until.getTimestamp()) 66 | { 67 | next(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/instanceiterator/EmptyIterator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.instanceiterator; 19 | 20 | 21 | import org.dmfs.rfc5545.DateTime; 22 | import org.dmfs.rfc5545.InstanceIterator; 23 | 24 | import java.util.NoSuchElementException; 25 | 26 | 27 | /** 28 | * An {@link InstanceIterator} without any instances. 29 | */ 30 | public final class EmptyIterator implements InstanceIterator 31 | { 32 | public static final InstanceIterator INSTANCE = new EmptyIterator(); 33 | 34 | 35 | @Override 36 | public boolean hasNext() 37 | { 38 | return false; 39 | } 40 | 41 | 42 | @Override 43 | public DateTime next() 44 | { 45 | throw new NoSuchElementException("No elements to iterate"); 46 | } 47 | 48 | 49 | @Override 50 | public void fastForward(DateTime until) 51 | { 52 | /* nothing to do */ 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/instanceiterator/FastForwardable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.instanceiterator; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.InstanceIterator; 22 | 23 | import java.util.Iterator; 24 | import java.util.NoSuchElementException; 25 | 26 | public final class FastForwardable implements InstanceIterator 27 | { 28 | private DateTime mNextInstance; 29 | private final Iterator mDelegate; 30 | private boolean mHasNext = true; 31 | 32 | public FastForwardable( 33 | DateTime firstInstance, 34 | Iterator delegate) 35 | { 36 | mNextInstance = firstInstance; 37 | mDelegate = delegate; 38 | } 39 | 40 | @Override 41 | public void fastForward(DateTime until) 42 | { 43 | while (mHasNext && until.after(mNextInstance)) 44 | { 45 | moveToNext(); 46 | } 47 | } 48 | 49 | @Override 50 | public boolean hasNext() 51 | { 52 | return mHasNext; 53 | } 54 | 55 | @Override 56 | public DateTime next() 57 | { 58 | if (!mHasNext) 59 | { 60 | throw new NoSuchElementException("No more elements to iterate"); 61 | } 62 | DateTime next = mNextInstance; 63 | moveToNext(); 64 | return next; 65 | } 66 | 67 | private void moveToNext() 68 | { 69 | if (mDelegate.hasNext()) 70 | { 71 | mNextInstance = mDelegate.next(); 72 | } 73 | else 74 | { 75 | mHasNext = false; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/instanceiterator/PeekableInstanceIterator.java: -------------------------------------------------------------------------------- 1 | package org.dmfs.rfc5545.instanceiterator; 2 | 3 | import org.dmfs.rfc5545.DateTime; 4 | import org.dmfs.rfc5545.InstanceIterator; 5 | 6 | import java.util.NoSuchElementException; 7 | 8 | public final class PeekableInstanceIterator implements InstanceIterator 9 | { 10 | private final InstanceIterator mDelegate; 11 | private DateTime mNext; 12 | private boolean mHasNext; 13 | 14 | public PeekableInstanceIterator(InstanceIterator delegate) 15 | { 16 | mDelegate = delegate; 17 | pullNext(); 18 | } 19 | 20 | @Override 21 | public void fastForward(DateTime until) 22 | { 23 | if (mHasNext && mNext.before(until)) 24 | { 25 | mDelegate.fastForward(until); 26 | pullNext(); 27 | } 28 | } 29 | 30 | @Override 31 | public boolean hasNext() 32 | { 33 | return mHasNext; 34 | } 35 | 36 | @Override 37 | public DateTime next() 38 | { 39 | if (!mHasNext) 40 | { 41 | throw new NoSuchElementException("no further elements to return"); 42 | } 43 | DateTime result = mNext; 44 | pullNext(); 45 | return result; 46 | } 47 | 48 | public DateTime peek() 49 | { 50 | if (!mHasNext) 51 | { 52 | throw new NoSuchElementException("no further elements to peek at"); 53 | } 54 | return mNext; 55 | } 56 | 57 | private void pullNext() 58 | { 59 | mHasNext = mDelegate.hasNext(); 60 | if (mHasNext) 61 | { 62 | mNext = mDelegate.next(); 63 | } 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/instanceiterator/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | @NonNullByDefault 19 | package org.dmfs.rfc5545.instanceiterator; 20 | 21 | import org.eclipse.jdt.annotation.NonNullByDefault; -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/iterable/InstanceIterable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.iterable; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | 22 | 23 | /** 24 | * An {@link Iterable} of recurring instances. 25 | * 26 | * @deprecated in favour of {@link org.dmfs.rfc5545.RecurrenceSet} 27 | */ 28 | @Deprecated 29 | public interface InstanceIterable 30 | { 31 | /** 32 | * Return an {@link InstanceIterator} for the given first instance. 33 | */ 34 | InstanceIterator iterator(DateTime firstInstance); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/iterable/InstanceIterator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.iterable; 19 | 20 | /** 21 | * @deprecated in favour of {@link org.dmfs.rfc5545.InstanceIterator} 22 | */ 23 | @Deprecated 24 | public interface InstanceIterator 25 | { 26 | /** 27 | * Returns {@code true} if there is at least one more instance to iterate and {@code false} otherwise. 28 | */ 29 | boolean hasNext(); 30 | 31 | /** 32 | * Get the next instance (in milliseconds since the epoch) of this set. Do not call this if {@link #hasNext()} returns {@code false}. 33 | */ 34 | long next(); 35 | 36 | /** 37 | * Skip all instances till {@code until}. If {@code until} is an instance itself it will be the next iterated instance. If the rule doesn't 38 | * recur till that date the next call to {@link #hasNext()} will return {@code false}. 39 | */ 40 | void fastForward(long until); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/iterable/ParsedDates.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.iterable; 19 | 20 | import org.dmfs.jems2.iterable.DelegatingIterable; 21 | import org.dmfs.jems2.iterable.Mapped; 22 | import org.dmfs.jems2.iterable.Seq; 23 | import org.dmfs.jems2.iterable.Sieved; 24 | import org.dmfs.jems2.predicate.Not; 25 | import org.dmfs.rfc5545.DateTime; 26 | 27 | import java.util.TimeZone; 28 | 29 | /** 30 | * An {@link Iterable} of {@link DateTime}s parsed from a {@link String} of comma separated RFC 5545 date or 31 | * datetime values. 32 | */ 33 | public final class ParsedDates extends DelegatingIterable 34 | { 35 | public ParsedDates(TimeZone timeZone, String datesList) 36 | { 37 | super(new Mapped<>(dateString -> DateTime.parse(timeZone, dateString), 38 | new Sieved<>(new Not<>(String::isEmpty), 39 | new Seq<>(datesList.split(","))))); 40 | } 41 | 42 | 43 | public ParsedDates(String datesList) 44 | { 45 | super(new Mapped<>(DateTime::parse, 46 | new Sieved<>(new Not<>(String::isEmpty), 47 | new Seq<>(datesList.split(","))))); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/Composite.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.iterable.instanceiterable; 19 | 20 | import org.dmfs.jems2.iterable.Mapped; 21 | import org.dmfs.jems2.iterable.Seq; 22 | import org.dmfs.rfc5545.DateTime; 23 | import org.dmfs.rfc5545.iterable.InstanceIterable; 24 | import org.dmfs.rfc5545.iterable.InstanceIterator; 25 | 26 | 27 | /** 28 | * A composite {@link InstanceIterable} composed of other {@link InstanceIterable}s. This {@link InstanceIterator} 29 | * returned by this class returns the instances of all given {@link InstanceIterable}s in chronological order. 30 | * 31 | * @deprecated in favour of {@link org.dmfs.rfc5545.recurrenceset.Merged} 32 | */ 33 | @Deprecated 34 | public final class Composite implements InstanceIterable 35 | { 36 | private final InstanceIterable mDelegate; 37 | 38 | 39 | public Composite(InstanceIterable... delegate) 40 | { 41 | this(new Seq<>(delegate)); 42 | } 43 | 44 | 45 | public Composite(Iterable delegate) 46 | { 47 | this(firstInstance -> new org.dmfs.rfc5545.iterable.instanceiterator.Composite(new Mapped<>(d -> d.iterator(firstInstance), delegate))); 48 | } 49 | 50 | 51 | private Composite(InstanceIterable delegate) 52 | { 53 | mDelegate = delegate; 54 | } 55 | 56 | 57 | @Override 58 | public InstanceIterator iterator(DateTime firstInstance) 59 | { 60 | return mDelegate.iterator(firstInstance); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/EmptyIterable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.iterable.instanceiterable; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.iterable.InstanceIterable; 22 | import org.dmfs.rfc5545.iterable.InstanceIterator; 23 | import org.dmfs.rfc5545.iterable.instanceiterator.EmptyIterator; 24 | 25 | 26 | /** 27 | * An {@link InstanceIterable} that doesn't have any instances. 28 | * 29 | * @deprecated without replacement 30 | */ 31 | @Deprecated 32 | public final class EmptyIterable implements InstanceIterable 33 | { 34 | public static final InstanceIterable INSTANCE = new EmptyIterable(); 35 | 36 | 37 | @Override 38 | public InstanceIterator iterator(DateTime firstInstance) 39 | { 40 | return EmptyIterator.INSTANCE; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/FastForwarded.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.iterable.instanceiterable; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.iterable.InstanceIterable; 22 | import org.dmfs.rfc5545.iterable.InstanceIterator; 23 | 24 | 25 | /** 26 | * An {@link InstanceIterable} that fast forwards the iteration to a given instant. All instances prior to that instant will be skipped. 27 | * 28 | * @deprecated in favour of {@link org.dmfs.rfc5545.recurrenceset.FastForwarded} 29 | */ 30 | @Deprecated 31 | public final class FastForwarded implements InstanceIterable 32 | { 33 | private final long mTimeStamp; 34 | private final InstanceIterable mDelegate; 35 | 36 | 37 | public FastForwarded(DateTime fastForwardTo, InstanceIterable delegate) 38 | { 39 | this(fastForwardTo.getTimestamp(), delegate); 40 | } 41 | 42 | 43 | public FastForwarded(DateTime fastForwardTo, InstanceIterable... delegate) 44 | { 45 | this(fastForwardTo.getTimestamp(), new Composite(delegate)); 46 | } 47 | 48 | 49 | public FastForwarded(DateTime fastForwardTo, Iterable delegate) 50 | { 51 | this(fastForwardTo.getTimestamp(), new Composite(delegate)); 52 | } 53 | 54 | 55 | public FastForwarded(long timeStamp, InstanceIterable delegate) 56 | { 57 | mTimeStamp = timeStamp; 58 | mDelegate = delegate; 59 | } 60 | 61 | 62 | @Override 63 | public InstanceIterator iterator(DateTime firstInstance) 64 | { 65 | InstanceIterator iterator = mDelegate.iterator(firstInstance); 66 | iterator.fastForward(mTimeStamp); 67 | return iterator; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/FirstAndRuleInstances.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.iterable.instanceiterable; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.iterable.InstanceIterable; 22 | import org.dmfs.rfc5545.iterable.InstanceIterator; 23 | import org.dmfs.rfc5545.iterable.instanceiterator.Composite; 24 | import org.dmfs.rfc5545.iterable.instanceiterator.CountLimitedRecurrenceRuleIterator; 25 | import org.dmfs.rfc5545.recur.RecurrenceRule; 26 | import org.dmfs.rfc5545.recur.RecurrenceRuleIterator; 27 | import org.dmfs.rfc5545.recurrenceset.OfRuleAndFirst; 28 | 29 | 30 | /** 31 | * Implements {@link InstanceIterable} for a {@link RecurrenceRule} that also returns any non-synchronized first instance. 32 | * 33 | * @deprecated in favour of {@link OfRuleAndFirst} 34 | */ 35 | @Deprecated 36 | public final class FirstAndRuleInstances implements InstanceIterable 37 | { 38 | /** 39 | * The recurrence rule. 40 | */ 41 | private final RecurrenceRule mRrule; 42 | 43 | 44 | /** 45 | * Create a new adapter for the given rule and start. 46 | * 47 | * @param rule The recurrence rule to adapt to. 48 | */ 49 | public FirstAndRuleInstances(RecurrenceRule rule) 50 | { 51 | mRrule = rule; 52 | } 53 | 54 | 55 | @Override 56 | public InstanceIterator iterator(DateTime firstInstance) 57 | { 58 | RecurrenceRuleIterator ruleIterator = mRrule.iterator(firstInstance); 59 | if (mRrule.getCount() != null && ruleIterator.peekMillis() != firstInstance.getTimestamp()) 60 | { 61 | // we have a count limited rule and an unsynched start date 62 | // since the start date counts as the first element, the RRULE iterator should return one less element. 63 | return new Composite( 64 | new InstanceList(new long[] { firstInstance.getTimestamp() }).iterator(firstInstance), 65 | new CountLimitedRecurrenceRuleIterator(ruleIterator, mRrule.getCount() - 1)); 66 | } 67 | return new InstanceIterator() 68 | { 69 | @Override 70 | public boolean hasNext() 71 | { 72 | return ruleIterator.hasNext(); 73 | } 74 | 75 | 76 | @Override 77 | public long next() 78 | { 79 | return ruleIterator.nextMillis(); 80 | } 81 | 82 | 83 | @Override 84 | public void fastForward(long until) 85 | { 86 | ruleIterator.fastForward(until); 87 | } 88 | }; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/RuleInstances.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.iterable.instanceiterable; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.iterable.InstanceIterable; 22 | import org.dmfs.rfc5545.iterable.InstanceIterator; 23 | import org.dmfs.rfc5545.recur.RecurrenceRule; 24 | import org.dmfs.rfc5545.recur.RecurrenceRuleIterator; 25 | import org.dmfs.rfc5545.recurrenceset.OfRule; 26 | 27 | 28 | /** 29 | * Implements {@link InstanceIterable} for a {@link RecurrenceRule}. That only iterates instances that match the {@link RecurrenceRule}. 30 | * Any non-synchronized first instance is not returned. 31 | * 32 | * @deprecated in favour of {@link OfRule} 33 | */ 34 | @Deprecated 35 | public final class RuleInstances implements InstanceIterable 36 | { 37 | 38 | /** 39 | * The recurrence rule. 40 | */ 41 | private final RecurrenceRule mRrule; 42 | 43 | 44 | /** 45 | * Create a new adapter for the given rule and start. 46 | * 47 | * @param rule The recurrence rule to adapt to. 48 | */ 49 | public RuleInstances(RecurrenceRule rule) 50 | { 51 | mRrule = rule; 52 | } 53 | 54 | 55 | @Override 56 | public InstanceIterator iterator(DateTime firstInstance) 57 | { 58 | RecurrenceRuleIterator iterator = mRrule.iterator(firstInstance); 59 | return new InstanceIterator() 60 | { 61 | private final RecurrenceRuleIterator mIterator = iterator; 62 | 63 | 64 | @Override 65 | public boolean hasNext() 66 | { 67 | return mIterator.hasNext(); 68 | } 69 | 70 | 71 | @Override 72 | public long next() 73 | { 74 | return mIterator.nextMillis(); 75 | } 76 | 77 | 78 | @Override 79 | public void fastForward(long until) 80 | { 81 | mIterator.fastForward(until); 82 | } 83 | }; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/CountLimitedRecurrenceRuleIterator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.iterable.instanceiterator; 19 | 20 | import org.dmfs.rfc5545.iterable.InstanceIterator; 21 | import org.dmfs.rfc5545.recur.RecurrenceRuleIterator; 22 | 23 | import java.util.NoSuchElementException; 24 | 25 | 26 | /** 27 | * An {@link InstanceIterator} which limits the number of iterated instances. 28 | * 29 | * @deprecated in favour of {@link org.dmfs.rfc5545.instanceiterator.CountLimitedRecurrenceRuleIterator} 30 | */ 31 | @Deprecated 32 | public final class CountLimitedRecurrenceRuleIterator implements InstanceIterator 33 | { 34 | private final RecurrenceRuleIterator mDelegate; 35 | private int mRemaining; 36 | 37 | 38 | public CountLimitedRecurrenceRuleIterator(RecurrenceRuleIterator delegate, int remaining) 39 | { 40 | mDelegate = delegate; 41 | mRemaining = remaining; 42 | } 43 | 44 | 45 | @Override 46 | public boolean hasNext() 47 | { 48 | return mRemaining > 0 && mDelegate.hasNext(); 49 | } 50 | 51 | 52 | @Override 53 | public long next() 54 | { 55 | if (!hasNext()) 56 | { 57 | throw new NoSuchElementException("No further elements to iterate"); 58 | } 59 | mRemaining--; 60 | return mDelegate.nextMillis(); 61 | } 62 | 63 | 64 | @Override 65 | public void fastForward(long until) 66 | { 67 | while (hasNext() && mDelegate.peekMillis() < until) 68 | { 69 | next(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/EmptyIterator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.iterable.instanceiterator; 19 | 20 | import org.dmfs.rfc5545.iterable.InstanceIterator; 21 | 22 | import java.util.NoSuchElementException; 23 | 24 | 25 | /** 26 | * An {@link InstanceIterator} without any instances. 27 | * 28 | * @deprecated in favour of {@link org.dmfs.rfc5545.instanceiterator.EmptyIterator} 29 | */ 30 | @Deprecated 31 | public final class EmptyIterator implements InstanceIterator 32 | { 33 | public static final InstanceIterator INSTANCE = new EmptyIterator(); 34 | 35 | 36 | @Override 37 | public boolean hasNext() 38 | { 39 | return false; 40 | } 41 | 42 | 43 | @Override 44 | public long next() 45 | { 46 | throw new NoSuchElementException("No elements to iterate"); 47 | } 48 | 49 | 50 | @Override 51 | public void fastForward(long until) 52 | { 53 | /* nothing to do */ 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/optional/Last.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.optional; 19 | 20 | import org.dmfs.jems2.Optional; 21 | 22 | import java.util.Iterator; 23 | 24 | /** 25 | * TODO: move to jems2 26 | */ 27 | final class Last implements Optional 28 | { 29 | private final Iterable mIterable; 30 | 31 | public Last(Iterable iterable) 32 | { 33 | this.mIterable = iterable; 34 | } 35 | 36 | @Override 37 | public boolean isPresent() 38 | { 39 | return mIterable.iterator().hasNext(); 40 | } 41 | 42 | @Override 43 | public T value() 44 | { 45 | Iterator iterator = mIterable.iterator(); 46 | T result = iterator.next(); 47 | while (iterator.hasNext()) 48 | { 49 | result = iterator.next(); 50 | } 51 | return result; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/optional/LastInstance.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.optional; 19 | 20 | import org.dmfs.jems2.Optional; 21 | import org.dmfs.jems2.optional.DelegatingOptional; 22 | import org.dmfs.jems2.optional.If; 23 | import org.dmfs.rfc5545.DateTime; 24 | import org.dmfs.rfc5545.RecurrenceSet; 25 | 26 | /** 27 | * The {@link Optional} last {@link DateTime} of a {@link RecurrenceSet}. 28 | * The value is absent when the {@link RecurrenceSet} is empty or infinite. 29 | */ 30 | public final class LastInstance extends DelegatingOptional 31 | { 32 | public LastInstance(RecurrenceSet recurrenceSet) 33 | { 34 | super(new If<>(recurrenceSet::isFinite, new Last<>(recurrenceSet))); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/optional/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | @NonNullByDefault 19 | package org.dmfs.rfc5545.optional; 20 | 21 | import org.eclipse.jdt.annotation.NonNullByDefault; -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | @NonNullByDefault 19 | package org.dmfs.rfc5545; 20 | 21 | import org.eclipse.jdt.annotation.NonNullByDefault; -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByDayFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.Weekday; 22 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 23 | 24 | import java.util.Set; 25 | 26 | 27 | /** 28 | * A filter that limits recurrence rules by day of week. 29 | * 30 | * @author Marten Gajda 31 | */ 32 | final class ByDayFilter implements ByFilter 33 | { 34 | 35 | /** 36 | * The {@link CalendarMetrics} to use. 37 | */ 38 | private final CalendarMetrics mCalendarMetrics; 39 | 40 | private final Set mWeekdays; 41 | 42 | private final Weekday[] mWeekdayArray = Weekday.values(); 43 | 44 | 45 | public ByDayFilter(CalendarMetrics calendarMetrics, Set weekdays) 46 | { 47 | mCalendarMetrics = calendarMetrics; 48 | mWeekdays = weekdays; 49 | } 50 | 51 | 52 | @Override 53 | public boolean filter(long instance) 54 | { 55 | return !mWeekdays.contains(mWeekdayArray[mCalendarMetrics.getDayOfWeek( 56 | Instance.year(instance), 57 | Instance.month(instance), 58 | Instance.dayOfMonth(instance))]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByDayPrefixedFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.jems2.BiFunction; 21 | import org.dmfs.rfc5545.Instance; 22 | import org.dmfs.rfc5545.Weekday; 23 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 24 | 25 | import java.util.Map; 26 | import java.util.Set; 27 | 28 | 29 | /** 30 | * A filter that limits recurrence rules by day of week. 31 | * 32 | * @author Marten Gajda 33 | */ 34 | final class ByDayPrefixedFilter implements ByFilter 35 | { 36 | public enum Scope 37 | { 38 | MONTH( 39 | (instance, metrics) -> 40 | (Instance.dayOfMonth(instance) - 1) / 7 + 1, 41 | (instance, metrics) -> 42 | (Instance.dayOfMonth(instance) - metrics.getDaysPerPackedMonth(Instance.year(instance), Instance.month(instance))) / 7 - 1), 43 | YEAR( 44 | (instance, metrics) -> 45 | (metrics.getDayOfYear(Instance.year(instance), Instance.month(instance), Instance.dayOfMonth(instance)) - 1) / 7 + 1, 46 | (instance, metrics) -> 47 | (metrics.getDayOfYear(Instance.year(instance), Instance.month(instance), Instance.dayOfMonth(instance)) 48 | - metrics.getDaysPerYear(Instance.year(instance))) / 7 - 1); 49 | 50 | private final BiFunction mNthDay; 51 | private final BiFunction mNthLastDay; 52 | 53 | 54 | Scope(BiFunction nthDay, BiFunction nthLastDay) 55 | { 56 | mNthDay = nthDay; 57 | mNthLastDay = nthLastDay; 58 | } 59 | } 60 | 61 | 62 | /** 63 | * The {@link CalendarMetrics} to use. 64 | */ 65 | private final CalendarMetrics mCalendarMetrics; 66 | private final Map> mPrefixedWeekDays; 67 | private final Scope mScope; 68 | private final Weekday[] mWeekdayArray = Weekday.values(); 69 | 70 | 71 | public ByDayPrefixedFilter( 72 | CalendarMetrics calendarMetrics, 73 | Map> prefixedWeekDays, 74 | Scope scope) 75 | { 76 | mCalendarMetrics = calendarMetrics; 77 | mPrefixedWeekDays = prefixedWeekDays; 78 | mScope = scope; 79 | } 80 | 81 | 82 | @Override 83 | public boolean filter(long instance) 84 | { 85 | Set prefixes = mPrefixedWeekDays.get(mWeekdayArray[mCalendarMetrics.getDayOfWeek( 86 | Instance.year(instance), 87 | Instance.month(instance), 88 | Instance.dayOfMonth(instance))]); 89 | return prefixes == null 90 | || (!prefixes.contains(mScope.mNthDay.value(instance, mCalendarMetrics)) 91 | && !prefixes.contains(mScope.mNthLastDay.value(instance, mCalendarMetrics))); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByDayWeeklyAndMonthlyExpander.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 23 | import org.dmfs.rfc5545.recur.RecurrenceRule.WeekdayNum; 24 | 25 | import java.util.List; 26 | 27 | 28 | /** 29 | * An expander that expands recurrence rules by day of week in a monthly and weekly scope. 30 | * 31 | * @author Marten Gajda 32 | */ 33 | final class ByDayWeeklyAndMonthlyExpander extends ByExpander 34 | { 35 | /** 36 | * The bitmap of week days to expand. 37 | */ 38 | private final int mDayBitMap; 39 | 40 | /** 41 | * The list of months if a BYMONTH part is specified in the rule. We need this to filter by month if the rule has a monthly and weekly scope. 42 | */ 43 | private final int[] mMonths; 44 | 45 | 46 | public ByDayWeeklyAndMonthlyExpander(RecurrenceRule rule, RuleIterator previous, CalendarMetrics calendarTools, long start) 47 | { 48 | super(previous, calendarTools, start); 49 | 50 | // get the list of WeekDayNums and convert it into an array 51 | List byDay = rule.getByDayPart(); 52 | 53 | int dayBitMap = 0; 54 | 55 | for (WeekdayNum weekdayNum : byDay) 56 | { 57 | if (weekdayNum.pos == 0) // ignore any positional days 58 | { 59 | dayBitMap |= 1 << weekdayNum.weekday.ordinal(); 60 | } 61 | } 62 | mDayBitMap = dayBitMap; 63 | 64 | if (rule.hasPart(Part.BYMONTH)) 65 | { 66 | // we have to filter by month 67 | mMonths = StaticUtils.ListToArray(rule.getByPart(Part.BYMONTH)); 68 | } 69 | else 70 | { 71 | mMonths = null; 72 | } 73 | } 74 | 75 | 76 | @Override 77 | void expand(long instance, long start) 78 | { 79 | CalendarMetrics calendarMetrics = mCalendarMetrics; 80 | int month = Instance.month(instance); 81 | 82 | int dayBitMap = mDayBitMap; 83 | int day = 0; 84 | while (dayBitMap > 0) 85 | { 86 | while ((dayBitMap & 1) == 0) 87 | { 88 | // fast forward to the next bit 89 | dayBitMap = dayBitMap >> 1; 90 | day += 1; 91 | } 92 | long newInstance = calendarMetrics.setDayOfWeek(instance, day); 93 | 94 | int newMonth = Instance.month(newInstance); 95 | 96 | if (mMonths != null && StaticUtils.linearSearch(mMonths, 97 | newMonth) >= 0 || mMonths == null && newMonth == month) 98 | { 99 | addInstance(newInstance); 100 | } 101 | // goo 102 | dayBitMap = dayBitMap >> 1; 103 | day++; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByDayWeeklyExpander.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 21 | import org.dmfs.rfc5545.recur.RecurrenceRule.WeekdayNum; 22 | 23 | import java.util.List; 24 | 25 | 26 | /** 27 | * An expander that expands recurrence rules by day of week in a weekly scope. 28 | * 29 | * @author Marten Gajda 30 | */ 31 | final class ByDayWeeklyExpander extends ByExpander 32 | { 33 | /** 34 | * The bitmap of week days to expand. 35 | */ 36 | private final int mDayBitMap; 37 | 38 | 39 | public ByDayWeeklyExpander(RecurrenceRule rule, RuleIterator previous, CalendarMetrics calendarTools, long start) 40 | { 41 | super(previous, calendarTools, start); 42 | 43 | // get the list of WeekDayNums and convert it into an array 44 | List byDay = rule.getByDayPart(); 45 | 46 | int dayBitMap = 0; 47 | 48 | for (WeekdayNum weekdayNum : byDay) 49 | { 50 | if (weekdayNum.pos == 0) // ignore any positional days 51 | { 52 | dayBitMap |= 1 << weekdayNum.weekday.ordinal(); 53 | } 54 | } 55 | mDayBitMap = dayBitMap; 56 | } 57 | 58 | 59 | @Override 60 | void expand(long instance, long start) 61 | { 62 | CalendarMetrics calendarMetrics = mCalendarMetrics; 63 | 64 | int dayBitMap = mDayBitMap; 65 | int day = 0; 66 | while (dayBitMap > 0) 67 | { 68 | while ((dayBitMap & 1) == 0) 69 | { 70 | // fast forward to the next bit 71 | dayBitMap = dayBitMap >> 1; 72 | day += 1; 73 | } 74 | addInstance(calendarMetrics.setDayOfWeek(instance, day)); 75 | // go to next day 76 | dayBitMap = dayBitMap >> 1; 77 | day++; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | 22 | 23 | /** 24 | * The interface of a "BY*"-part filter. 25 | * 26 | * @author Marten Gajda 27 | */ 28 | interface ByFilter 29 | { 30 | 31 | /** 32 | * Filter an instance. This method determines if a given {@link Instance} should be removed from the result set or not. 33 | * 34 | * @param instance 35 | * The instance to filter. 36 | * 37 | * @return true to remove the instance from the result set, false to include it. 38 | */ 39 | boolean filter(long instance); 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByHourExpander.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 23 | 24 | 25 | /** 26 | * A filter that expands recurrence rules by hour. 27 | * 28 | * @author Marten Gajda 29 | */ 30 | final class ByHourExpander extends ByExpander 31 | { 32 | /** 33 | * The hour list from the rule. 34 | */ 35 | private final int[] mHours; 36 | 37 | 38 | public ByHourExpander(RecurrenceRule rule, RuleIterator previous, CalendarMetrics calendarTools, long start) 39 | { 40 | super(previous, calendarTools, start); 41 | mHours = StaticUtils.ListToSortedArray(rule.getByPart(Part.BYHOUR)); 42 | } 43 | 44 | 45 | @Override 46 | void expand(long instance, long start) 47 | { 48 | // add a new instance for every hour in mHours 49 | for (int hour : mHours) 50 | { 51 | addInstance(Instance.setHour(instance, hour)); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByHourFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 22 | 23 | 24 | /** 25 | * A filter that limits recurrence rules by hour. 26 | * 27 | * @author Marten Gajda 28 | */ 29 | final class ByHourFilter implements ByFilter 30 | { 31 | /** 32 | * The hour list from the rule. 33 | */ 34 | private final int[] mHours; 35 | 36 | 37 | public ByHourFilter(RecurrenceRule rule) 38 | { 39 | mHours = StaticUtils.ListToArray(rule.getByPart(Part.BYHOUR)); 40 | } 41 | 42 | 43 | @Override 44 | public boolean filter(long instance) 45 | { 46 | // check that the hour of the instance is in mHours 47 | return StaticUtils.linearSearch(mHours, Instance.hour(instance)) < 0; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByMinuteExpander.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 23 | 24 | 25 | /** 26 | * A filter that expands recurrence rules by minute. 27 | * 28 | * @author Marten Gajda 29 | */ 30 | final class ByMinuteExpander extends ByExpander 31 | { 32 | /** 33 | * The list of minutes from the recurrence rule. 34 | */ 35 | private final int[] mMinutes; 36 | 37 | 38 | public ByMinuteExpander(RecurrenceRule rule, RuleIterator previous, CalendarMetrics calendarTools, long start) 39 | { 40 | super(previous, calendarTools, start); 41 | mMinutes = StaticUtils.ListToSortedArray(rule.getByPart(Part.BYMINUTE)); 42 | } 43 | 44 | 45 | @Override 46 | void expand(long instance, long start) 47 | { 48 | // add a new instance for every minute value in the list 49 | for (int minute : mMinutes) 50 | { 51 | addInstance(Instance.setMinute(instance, minute)); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByMinuteFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 22 | 23 | 24 | /** 25 | * A filter that limits recurrence rules by minute. 26 | * 27 | * @author Marten Gajda 28 | */ 29 | final class ByMinuteFilter implements ByFilter 30 | { 31 | /** 32 | * The list of minutes from the recurrence rule. 33 | */ 34 | private final int[] mMinutes; 35 | 36 | 37 | public ByMinuteFilter(RecurrenceRule rule) 38 | { 39 | mMinutes = StaticUtils.ListToArray(rule.getByPart(Part.BYMINUTE)); 40 | } 41 | 42 | 43 | @Override 44 | public boolean filter(long instance) 45 | { 46 | // filter all minutes not in the list 47 | return StaticUtils.linearSearch(mMinutes, Instance.minute(instance)) < 0; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByMonthDayFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 23 | 24 | 25 | /** 26 | * A filter that limits recurrence rules by day of month.

Even though RFC 5545 doesn't explicitly say it, we filter YEARLY, MONTHLY or WEEKLY rules if 27 | * BYYEARDAY has been specified. BYYEARDAY is evaluated prior to BYMONTHDAY and there is no point in expanding these days since they already are expanded.

28 | * 29 | * @author Marten Gajda 30 | */ 31 | final class ByMonthDayFilter implements ByFilter 32 | { 33 | /** 34 | * A list of days of month to let pass. 35 | */ 36 | private final int[] mMonthDays; 37 | 38 | private final CalendarMetrics mCalendarMetrics; 39 | 40 | 41 | public ByMonthDayFilter(RecurrenceRule rule, CalendarMetrics calendarMetrics) 42 | { 43 | mCalendarMetrics = calendarMetrics; 44 | 45 | // get a list of the month days 46 | mMonthDays = StaticUtils.ListToArray(rule.getByPart(Part.BYMONTHDAY)); 47 | 48 | } 49 | 50 | 51 | @Override 52 | public boolean filter(long instance) 53 | { 54 | int monthDays = mCalendarMetrics.getDaysPerPackedMonth(Instance.year(instance), Instance.month(instance)); 55 | int dayOfMonth = Instance.dayOfMonth(instance); 56 | return (StaticUtils.linearSearch(mMonthDays, dayOfMonth) < 0 && StaticUtils.linearSearch(mMonthDays, 57 | dayOfMonth - 1 - monthDays) < 0) 58 | || dayOfMonth > monthDays; 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByMonthDayMonthlyExpander.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 23 | 24 | 25 | /** 26 | * An expander that expands recurrence rules by day of month. This expander expands instances for YEARLY, MONTHLY rules. 27 | * 28 | * @author Marten Gajda 29 | */ 30 | final class ByMonthDayMonthlyExpander extends ByExpander 31 | { 32 | /** 33 | * A list of days of month to expand. 34 | */ 35 | private final int[] mMonthDays; 36 | 37 | 38 | public ByMonthDayMonthlyExpander(RecurrenceRule rule, RuleIterator previous, CalendarMetrics calendarTools, long start) 39 | { 40 | super(previous, calendarTools, start); 41 | 42 | // get a sorted list of the month days 43 | mMonthDays = StaticUtils.ListToSortedArray(rule.getByPart(Part.BYMONTHDAY)); 44 | } 45 | 46 | 47 | @Override 48 | void expand(long instance, long start) 49 | { 50 | CalendarMetrics calendarMetrics = mCalendarMetrics; 51 | 52 | int year = Instance.year(instance); 53 | int month = Instance.month(instance); 54 | 55 | int monthDays = calendarMetrics.getDaysPerPackedMonth(year, month); 56 | for (int day : mMonthDays) 57 | { 58 | int actualDay = day; 59 | if (day < 0) 60 | { 61 | actualDay = day + monthDays + 1; 62 | } 63 | /* 64 | * Expand all days in the current month. The SanityFilter will remove all instances before start. 65 | */ 66 | if (0 < actualDay) 67 | { 68 | addInstance(Instance.setDayOfMonth(instance, actualDay)); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByMonthDaySkipFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule.Skip; 23 | 24 | 25 | /** 26 | * A filter that ensures invalid day of month numbers are skipped. 27 | * 28 | * @author Marten Gajda 29 | */ 30 | final class ByMonthDaySkipFilter extends RuleIterator 31 | { 32 | /** 33 | * Stop iterating (throwing an exception) if this number of empty sets passed in a line, i.e. sets that contain no elements because they have been filtered 34 | * or nothing was expanded. 35 | */ 36 | private final static int MAX_EMPTY_SETS = 1000; 37 | 38 | private final CalendarMetrics mCalendarMetrics; 39 | private final Skip mSkip; 40 | 41 | /** 42 | * The set we work on. This comes from the previous instance. 43 | */ 44 | private LongArray mWorkingSet = null; 45 | 46 | /** 47 | * The set we return. 48 | */ 49 | private final LongArray mResultSet = new LongArray(); 50 | 51 | 52 | public ByMonthDaySkipFilter(RecurrenceRule rule, RuleIterator previous, CalendarMetrics calendarMetrics, long start) 53 | { 54 | super(previous); 55 | mCalendarMetrics = calendarMetrics; 56 | mSkip = rule.getSkip(); 57 | } 58 | 59 | 60 | @Override 61 | public long next() 62 | { 63 | LongArray workingSet = mWorkingSet; 64 | if (workingSet == null || !workingSet.hasNext()) 65 | { 66 | mWorkingSet = workingSet = nextSet(); 67 | } 68 | return workingSet.next(); 69 | } 70 | 71 | 72 | @Override 73 | LongArray nextSet() 74 | { 75 | LongArray resultSet = mResultSet; 76 | CalendarMetrics calendarMetrics = mCalendarMetrics; 77 | 78 | int counter = 0; 79 | do 80 | { 81 | if (counter == MAX_EMPTY_SETS) 82 | { 83 | throw new IllegalArgumentException("too many empty recurrence sets"); 84 | } 85 | counter++; 86 | 87 | LongArray prev = mPrevious.nextSet(); 88 | while (prev.hasNext()) 89 | { 90 | long next = Instance.maskWeekday(prev.next()); 91 | 92 | if (!calendarMetrics.validate(next)) 93 | { 94 | if (mSkip == Skip.BACKWARD) 95 | { 96 | next = calendarMetrics.prevDay(next); 97 | } 98 | else 99 | // mSkip == Skip.FORWARD 100 | { 101 | next = calendarMetrics.nextDay(next); 102 | } 103 | } 104 | resultSet.add(next); 105 | } 106 | } while (!resultSet.hasNext()); 107 | 108 | return resultSet; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByMonthExpander.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 23 | 24 | 25 | /** 26 | * A filter that expands recurrence rules by month. Months are expanded for yearly rules only. 27 | * 28 | * @author Marten Gajda 29 | */ 30 | final class ByMonthExpander extends ByExpander 31 | { 32 | /** 33 | * The list of months to expand. 34 | */ 35 | private final int[] mMonths; 36 | 37 | 38 | public ByMonthExpander(RecurrenceRule rule, RuleIterator previous, CalendarMetrics calendarTools, long start) 39 | { 40 | super(previous, calendarTools, start); 41 | // sort array to iterate the months in the right order, that saves an additional sorting of each interval 42 | mMonths = StaticUtils.ListToSortedArray(rule.getByPart(Part.BYMONTH)); 43 | } 44 | 45 | 46 | @Override 47 | void expand(long instance, long start) 48 | { 49 | for (int month : mMonths) 50 | { 51 | long newInstance = Instance.setMonth(instance, month); 52 | if (newInstance < start) 53 | { 54 | // instance is before start, nothing to do here 55 | continue; 56 | } 57 | 58 | addInstance(newInstance); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByMonthFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 23 | 24 | 25 | /** 26 | * A filter that limits recurrence rules by month for rules having a weekly scope (i.e. when FREQ=WEEKLY and any by-day filter is present). This filter allows 27 | * weeks that overlap the month to pass. This ensures the by day filters can expand all relevant instances. The expanding by-day filter will take care of 28 | * filtering days not belonging to this month. 29 | * 30 | * @author Marten Gajda 31 | */ 32 | final class ByMonthFilter implements ByFilter 33 | { 34 | /** 35 | * The list of months to let pass. 36 | */ 37 | private final int[] mMonths; 38 | 39 | private final CalendarMetrics mCalendarMetrics; 40 | 41 | 42 | public ByMonthFilter(RecurrenceRule rule, CalendarMetrics calendarMetrics) 43 | { 44 | mCalendarMetrics = calendarMetrics; 45 | mMonths = StaticUtils.ListToArray(rule.getByPart(Part.BYMONTH)); 46 | } 47 | 48 | 49 | @Override 50 | public boolean filter(long instance) 51 | { 52 | final int month = Instance.month(instance); 53 | 54 | int[] months = mMonths; 55 | CalendarMetrics calendarMetrics = mCalendarMetrics; 56 | 57 | if (StaticUtils.linearSearch(months, month) >= 0) 58 | { 59 | return false; 60 | } 61 | 62 | long startOfWeek = calendarMetrics.startOfWeek(instance); 63 | 64 | // check if the month of the week start is in mMonths 65 | if (StaticUtils.linearSearch(months, Instance.month(startOfWeek)) >= 0) 66 | { 67 | return false; 68 | } 69 | 70 | long endOfWeek = calendarMetrics.nextDay(startOfWeek, 6); 71 | 72 | // check if the month of the week end is in mMonths 73 | return StaticUtils.linearSearch(months, Instance.month(endOfWeek)) < 0; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/BySecondExpander.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 23 | 24 | 25 | /** 26 | * A filter that expands recurrence rules by seconds. 27 | * 28 | * @author Marten Gajda 29 | */ 30 | final class BySecondExpander extends ByExpander 31 | { 32 | /** 33 | * The list of minutes from the recurrence rule. 34 | */ 35 | private final int[] mSeconds; 36 | 37 | 38 | public BySecondExpander(RecurrenceRule rule, RuleIterator previous, CalendarMetrics calendarTools, long start) 39 | { 40 | super(previous, calendarTools, start); 41 | mSeconds = StaticUtils.ListToSortedArray(rule.getByPart(Part.BYSECOND)); 42 | } 43 | 44 | 45 | @Override 46 | void expand(long instance, long start) 47 | { 48 | // add a new instance for every second in the list 49 | for (int second : mSeconds) 50 | { 51 | addInstance(Instance.setSecond(instance, second)); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/BySecondFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 22 | 23 | 24 | /** 25 | * A filter that limits recurrence rules by seconds. 26 | * 27 | * @author Marten Gajda 28 | */ 29 | final class BySecondFilter implements ByFilter 30 | { 31 | /** 32 | * The list of minutes from the recurrence rule. 33 | */ 34 | private final int[] mSeconds; 35 | 36 | 37 | public BySecondFilter(RecurrenceRule rule) 38 | { 39 | mSeconds = StaticUtils.ListToArray(rule.getByPart(Part.BYSECOND)); 40 | } 41 | 42 | 43 | @Override 44 | public boolean filter(long instance) 45 | { 46 | // filter all minutes not in the list 47 | return StaticUtils.linearSearch(mSeconds, Instance.second(instance)) < 0; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByWeekNoFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 23 | 24 | 25 | /** 26 | * A filter that limits recurrence rules by week number. Note, neither RFC 5545 nor RFC 2445 specify filtering by week number. This is meant for internal use 27 | * only. 28 | * 29 | * @author Marten Gajda 30 | */ 31 | final class ByWeekNoFilter implements ByFilter 32 | { 33 | /** 34 | * An array of the week numbers. 35 | */ 36 | private final int[] mWeekNumbers; 37 | 38 | /** 39 | * The {@link CalendarMetrics} to use. 40 | */ 41 | final CalendarMetrics mCalendarMetrics; 42 | 43 | 44 | public ByWeekNoFilter(RecurrenceRule rule, CalendarMetrics calendarMetrics) 45 | { 46 | mCalendarMetrics = calendarMetrics; 47 | mWeekNumbers = StaticUtils.ListToSortedArray(rule.getByPart(Part.BYWEEKNO)); 48 | } 49 | 50 | 51 | @Override 52 | public boolean filter(long instance) 53 | { 54 | int year = Instance.year(instance); 55 | int week = mCalendarMetrics.getWeekOfYear(year, Instance.month(instance), Instance.dayOfMonth(instance)); 56 | int weeks; 57 | if (week > 10 && Instance.month(instance) == 1) 58 | { 59 | // week belongs to the previous iso year 60 | weeks = mCalendarMetrics.getWeeksPerYear(year - 1); 61 | } 62 | else if (week == 1 && Instance.month(instance) > 1) 63 | { 64 | // week belongs to the next iso year 65 | weeks = mCalendarMetrics.getWeeksPerYear(year + 1); 66 | } 67 | else 68 | { 69 | weeks = mCalendarMetrics.getWeeksPerYear(year); 70 | } 71 | 72 | return (StaticUtils.linearSearch(mWeekNumbers, week) < 0 && StaticUtils.linearSearch(mWeekNumbers, week - 1 - weeks) < 0) || week > weeks; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByWeekNoMonthlyExpander.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 23 | 24 | 25 | /** 26 | * A filter expands recurrence rules by week of year. This is allowed for yearly rules only.

If a BYMONTH part is present and any BY*DAY rules follows this 27 | * filter also expands weeks that overlap the expanded month. That means two subsequent interval sets can include the same week. The BY*DAY filters will take 28 | * care of filtering those.

29 | * 30 | * @author Marten Gajda 31 | */ 32 | final class ByWeekNoMonthlyExpander extends ByExpander 33 | { 34 | /** 35 | * The week numbers to expand. 36 | */ 37 | private final int[] mByWeekNo; 38 | 39 | private final int mOriginalWeekDay; 40 | 41 | 42 | public ByWeekNoMonthlyExpander(RecurrenceRule rule, RuleIterator previous, CalendarMetrics calendarTools, long start) 43 | { 44 | super(previous, calendarTools, start); 45 | 46 | mByWeekNo = StaticUtils.ListToSortedArray(rule.getByPart(Part.BYWEEKNO)); 47 | 48 | // BYWEEKNO expansion preserves the original day of week 49 | mOriginalWeekDay = calendarTools.getDayOfWeek(Instance.year(start), Instance.month(start), Instance.dayOfMonth(start)); 50 | } 51 | 52 | 53 | @Override 54 | void expand(long instance, long notBefore) 55 | { 56 | int year = Instance.year(instance); 57 | int month = Instance.month(instance); 58 | 59 | // get the number of weeks in that year 60 | int yearWeeks = mCalendarMetrics.getWeeksPerYear(year); 61 | 62 | for (int weekOfYear : mByWeekNo) 63 | { 64 | int actualWeek = weekOfYear; 65 | if (weekOfYear < 0) 66 | { 67 | actualWeek = yearWeeks + weekOfYear + 1; 68 | } 69 | 70 | if (actualWeek <= 0 || actualWeek > yearWeeks) 71 | { 72 | continue; 73 | } 74 | 75 | /* 76 | * Expand instances in this week and month. 77 | */ 78 | int yearDay = mCalendarMetrics.getYearDayOfIsoYear(year, actualWeek, mOriginalWeekDay); 79 | 80 | if (yearDay < 1 || yearDay > mCalendarMetrics.getDaysPerYear(year)) 81 | { 82 | // definitely a different month 83 | continue; 84 | } 85 | 86 | final int monthAndDay = mCalendarMetrics.getMonthAndDayOfYearDay(year, yearDay); 87 | final int newMonth = CalendarMetrics.packedMonth(monthAndDay); 88 | if (newMonth == month) 89 | { 90 | addInstance(Instance.setMonthAndDayOfMonth(instance, newMonth, 91 | CalendarMetrics.dayOfMonth(monthAndDay))); 92 | } 93 | 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByYearDayFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 23 | 24 | 25 | /** 26 | * A filter that limits recurrence rules by day of year.

Note: RFC 5545 27 | * Doesn't allow BYYEARDAY to be used with DAILY, WEEKLY and MONTHLY rules, but RFC 2445 does. This filter tries to return a reasonable result for these cases. 28 | * In particular that means we expand MONTHLY and WEEKLY rules and filter DAILY rules.

29 | * 30 | * @author Marten Gajda 31 | */ 32 | final class ByYearDayFilter implements ByFilter 33 | { 34 | /** 35 | * The year days to let pass. 36 | */ 37 | private final int[] mYearDays; 38 | 39 | private final CalendarMetrics mCalendarMetrics; 40 | 41 | 42 | public ByYearDayFilter(RecurrenceRule rule, CalendarMetrics calendarMetrics) 43 | { 44 | mCalendarMetrics = calendarMetrics; 45 | mYearDays = StaticUtils.ListToArray(rule.getByPart(Part.BYYEARDAY)); 46 | } 47 | 48 | 49 | @Override 50 | public boolean filter(long instance) 51 | { 52 | int year = Instance.year(instance); 53 | int yearDays = mCalendarMetrics.getDaysPerYear(year); 54 | int dayOfYear = mCalendarMetrics.getDayOfYear(year, Instance.month(instance), Instance.dayOfMonth(instance)); 55 | return StaticUtils.linearSearch(mYearDays, dayOfYear) < 0 && StaticUtils.linearSearch(mYearDays, 56 | dayOfYear - yearDays) < 0 || dayOfYear > yearDays; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByYearDayMonthlyExpander.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 23 | 24 | 25 | /** 26 | * A filter that expands recurrence rules by day of year. This filter expands instances for YEARLY, MONTHLY and WEEKLY rules.

Note: RFC 5545 Doesn't allow BYYEARDAY to be used with DAILY, WEEKLY and MONTHLY rules, but RFC 2445 28 | * does. This filter tries to return a reasonable result for these cases. In particular that means we expand MONTHLY and WEEKLY rules and filter DAILY rules. 29 | *

30 | * 31 | * @author Marten Gajda 32 | */ 33 | final class ByYearDayMonthlyExpander extends ByExpander 34 | { 35 | /** 36 | * The year days to let pass or to expand. 37 | */ 38 | private final int[] mYearDays; 39 | 40 | 41 | public ByYearDayMonthlyExpander(RecurrenceRule rule, RuleIterator previous, CalendarMetrics calendarMetrics, long start) 42 | { 43 | super(previous, calendarMetrics, start); 44 | 45 | mYearDays = StaticUtils.ListToSortedArray(rule.getByPart(Part.BYYEARDAY)); 46 | } 47 | 48 | 49 | @Override 50 | void expand(long instance, long start) 51 | { 52 | int year = Instance.year(instance); 53 | int month = Instance.month(instance); 54 | int yearDays = mCalendarMetrics.getDaysPerYear(year); 55 | int startDayOfYear = mCalendarMetrics.getDayOfYear(Instance.year(start), Instance.month(start), Instance.dayOfMonth(start)); 56 | for (int day : mYearDays) 57 | { 58 | int actualDay = day; 59 | if (day < 0) 60 | { 61 | actualDay = day + yearDays + 1; 62 | } 63 | 64 | if (0 < actualDay && actualDay <= yearDays && !(actualDay < startDayOfYear && year == Instance.year(start))) 65 | { 66 | int monthAndDay = mCalendarMetrics.getMonthAndDayOfYearDay(year, actualDay); 67 | int newMonth = CalendarMetrics.packedMonth(monthAndDay); 68 | if (newMonth == month) 69 | { 70 | addInstance(Instance.setMonthAndDayOfMonth(instance, newMonth, CalendarMetrics.dayOfMonth(monthAndDay))); 71 | } 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/ByYearDayYearlyExpander.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 23 | 24 | 25 | /** 26 | * A filter that expands recurrence rules by day of year. This filter expands instances for YEARLY, MONTHLY and WEEKLY rules.

Note: RFC 5545 Doesn't allow BYYEARDAY to be used with DAILY, WEEKLY and MONTHLY rules, but RFC 2445 28 | * does. This filter tries to return a reasonable result for these cases. In particular that means we expand MONTHLY and WEEKLY rules and filter DAILY rules. 29 | *

30 | * 31 | * @author Marten Gajda 32 | */ 33 | final class ByYearDayYearlyExpander extends ByExpander 34 | { 35 | /** 36 | * The year days to let pass or to expand. 37 | */ 38 | private final int[] mYearDays; 39 | 40 | 41 | public ByYearDayYearlyExpander(RecurrenceRule rule, RuleIterator previous, CalendarMetrics calendarMetrics, long start) 42 | { 43 | super(previous, calendarMetrics, start); 44 | 45 | mYearDays = StaticUtils.ListToSortedArray(rule.getByPart(Part.BYYEARDAY)); 46 | } 47 | 48 | 49 | @Override 50 | void expand(long instance, long start) 51 | { 52 | int year = Instance.year(instance); 53 | int yearDays = mCalendarMetrics.getDaysPerYear(year); 54 | int startDayOfYear = mCalendarMetrics.getDayOfYear(Instance.year(start), Instance.month(start), Instance.dayOfMonth(start)); 55 | for (int day : mYearDays) 56 | { 57 | int actualDay = day; 58 | if (day < 0) 59 | { 60 | actualDay = day + yearDays + 1; 61 | } 62 | 63 | if (0 < actualDay && actualDay <= yearDays && !(actualDay < startDayOfYear && year == Instance.year(start))) 64 | { 65 | int monthAndDay = mCalendarMetrics.getMonthAndDayOfYearDay(year, actualDay); 66 | addInstance(Instance.setMonthAndDayOfMonth(instance, CalendarMetrics.packedMonth(monthAndDay), CalendarMetrics.dayOfMonth(monthAndDay))); 67 | } 68 | else if (actualDay <= 0) 69 | { 70 | addInstance(Instance.setMonthAndDayOfMonth(instance, 0, 0)); 71 | } 72 | else if (actualDay > yearDays) 73 | { 74 | int monthAndDay = mCalendarMetrics.getMonthAndDayOfYearDay(year, yearDays); 75 | addInstance(Instance.setMonthAndDayOfMonth(instance, CalendarMetrics.packedMonth(monthAndDay), CalendarMetrics.dayOfMonth(monthAndDay) + 1)); 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/CountLimiter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | /** 21 | * A {@link Limiter} that limits by the number of iterated instances. 22 | * 23 | * @author Marten Gajda 24 | */ 25 | final class CountLimiter extends Limiter 26 | { 27 | /** 28 | * The number of instances to let pass. 29 | */ 30 | private final int mLimit; 31 | 32 | /** 33 | * The number of instances already passed. 34 | */ 35 | private int mCounter = 0; 36 | 37 | 38 | /** 39 | * Creates a limiter that limits by instance number. 40 | * 41 | * @param rule 42 | * The {@link RecurrenceRule} to limit. 43 | * @param previous 44 | * The previous iterator instance. 45 | */ 46 | CountLimiter(RecurrenceRule rule, RuleIterator previous) 47 | { 48 | super(previous); 49 | mLimit = rule.getCount(); 50 | } 51 | 52 | 53 | @Override 54 | boolean stop(long instance) 55 | { 56 | // Stop if more than mLimit instances have been iterated. 57 | return ++mCounter > mLimit; 58 | } 59 | 60 | 61 | @Override 62 | void fastForward(long untilInstance) 63 | { 64 | // we can not fast forward, because we have to count the instances 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/InvalidRecurrenceRuleException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | /** 21 | * An Exception that indicates an error in a recurrence rule. 22 | * 23 | * @author Marten Gajda 24 | */ 25 | public class InvalidRecurrenceRuleException extends Exception 26 | { 27 | 28 | /** 29 | * Generated serial id. 30 | */ 31 | private static final long serialVersionUID = 2282570760598972553L; 32 | 33 | 34 | public InvalidRecurrenceRuleException(String msg) 35 | { 36 | super(msg); 37 | } 38 | 39 | 40 | public InvalidRecurrenceRuleException(String msg, Throwable e) 41 | { 42 | super(msg, e); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/Limiter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | /** 21 | * Limits the instance set of a recurrence rule. The subclasses are {@link CountLimiter} and {@link UntilLimiter} which limit by instance count or by last 22 | * recurrence date respectively. 23 | * 24 | * @author Marten Gajda 25 | */ 26 | abstract class Limiter extends RuleIterator 27 | { 28 | /** 29 | * Constructor for Limiter that just passes through the previous parameter. 30 | * 31 | * @param previous 32 | * The previous iterator instance. 33 | */ 34 | Limiter(RuleIterator previous) 35 | { 36 | super(previous); 37 | } 38 | 39 | 40 | @Override 41 | public long next() 42 | { 43 | long instance = mPrevious.next(); 44 | return stop(instance) ? Long.MIN_VALUE : instance; 45 | } 46 | 47 | 48 | @Override 49 | LongArray nextSet() 50 | { 51 | throw new UnsupportedOperationException( 52 | "nextSet is not implemented for Limiters, since it should never be called"); 53 | } 54 | 55 | 56 | /** 57 | * Returns true if the last instance has been iterated. 58 | * 59 | * @param instance 60 | * The instance to check. 61 | * 62 | * @return true if the limit of the recurrence set has been reached and no more instances should be iterated. 63 | */ 64 | abstract boolean stop(long instance); 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/TrivialByMonthFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.Instance; 21 | import org.dmfs.rfc5545.recur.RecurrenceRule.Part; 22 | 23 | 24 | /** 25 | * A trivial "ByMonth" filter that doesn't support overlapping weeks. 26 | * 27 | * @author Marten Gajda 28 | */ 29 | final class TrivialByMonthFilter implements ByFilter 30 | { 31 | /** 32 | * The list of months to let pass. 33 | */ 34 | private final int[] mMonths; 35 | 36 | 37 | public TrivialByMonthFilter(RecurrenceRule rule) 38 | { 39 | mMonths = StaticUtils.ListToArray(rule.getByPart(Part.BYMONTH)); 40 | } 41 | 42 | 43 | @Override 44 | public boolean filter(long instance) 45 | { 46 | return StaticUtils.linearSearch(mMonths, Instance.month(instance)) < 0; 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/UnicodeCalendarScales.java: -------------------------------------------------------------------------------- 1 | package org.dmfs.rfc5545.recur; 2 | 3 | import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics.CalendarMetricsFactory; 4 | import org.dmfs.rfc5545.calendarmetrics.GregorianCalendarMetrics; 5 | import org.dmfs.rfc5545.calendarmetrics.IslamicCalendarMetrics; 6 | import org.dmfs.rfc5545.calendarmetrics.IslamicCalendarMetrics.LeapYearPattern; 7 | import org.dmfs.rfc5545.calendarmetrics.JulianCalendarMetrics; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | 13 | public final class UnicodeCalendarScales 14 | { 15 | 16 | private final static Map CALENDAR_SCALES = new HashMap( 17 | 10); 18 | 19 | 20 | public static CalendarMetricsFactory getCalendarMetricsForName(String calendarScaleName) 21 | { 22 | return CALENDAR_SCALES.get(calendarScaleName); 23 | } 24 | 25 | 26 | static 27 | { 28 | CALENDAR_SCALES.put(GregorianCalendarMetrics.CALENDAR_SCALE_ALIAS, GregorianCalendarMetrics.FACTORY); 29 | CALENDAR_SCALES.put(GregorianCalendarMetrics.CALENDAR_SCALE_NAME, GregorianCalendarMetrics.FACTORY); 30 | CALENDAR_SCALES.put(JulianCalendarMetrics.CALENDAR_SCALE_ALIAS, JulianCalendarMetrics.FACTORY); 31 | CALENDAR_SCALES.put(JulianCalendarMetrics.CALENDAR_SCALE_NAME, JulianCalendarMetrics.FACTORY); 32 | CALENDAR_SCALES.put(IslamicCalendarMetrics.CALENDAR_SCALE_TLBA, 33 | new IslamicCalendarMetrics.IslamicCalendarMetricsFactory( 34 | IslamicCalendarMetrics.CALENDAR_SCALE_TLBA, LeapYearPattern.II, false)); 35 | CALENDAR_SCALES.put(IslamicCalendarMetrics.CALENDAR_SCALE_CIVIL, 36 | new IslamicCalendarMetrics.IslamicCalendarMetricsFactory( 37 | IslamicCalendarMetrics.CALENDAR_SCALE_CIVIL, LeapYearPattern.II, true)); 38 | CALENDAR_SCALES.put("ISLAMICC", CALENDAR_SCALES.get(IslamicCalendarMetrics.CALENDAR_SCALE_CIVIL)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/UntilDateLimiter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.Instance; 22 | 23 | 24 | /** 25 | * A {@link Limiter} that filters all instances after a certain all-day date (the one specified in the UNTIL part). 26 | */ 27 | final class UntilDateLimiter extends Limiter 28 | { 29 | /** 30 | * The latest allowed instance start date. 31 | */ 32 | private final long mUntil; 33 | 34 | 35 | /** 36 | * Create a new limiter for an all-day UNTIL part. 37 | */ 38 | public UntilDateLimiter(RecurrenceRule rule, RuleIterator previous) 39 | { 40 | super(previous); 41 | DateTime until = rule.getUntil(); 42 | if (!until.isAllDay()) 43 | { 44 | throw new RuntimeException("Illegal use of UntilDateLimiter with non-allday date " + until); 45 | } 46 | mUntil = until.getInstance(); 47 | } 48 | 49 | 50 | @Override 51 | boolean stop(long instance) 52 | { 53 | return mUntil < Instance.setSecond(Instance.setMinute(Instance.setHour(Instance.maskWeekday(instance), 0), 0), 0); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recur/UntilLimiter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Marten Gajda 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.dmfs.rfc5545.recur; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.Instance; 22 | 23 | import java.util.TimeZone; 24 | 25 | 26 | /** 27 | * A {@link Limiter} that filters all instances after a certain date (the one specified in the UNTIL part). 28 | * 29 | * @author Marten Gajda 30 | */ 31 | final class UntilLimiter extends Limiter 32 | { 33 | /** 34 | * The latest allowed instance start date. 35 | */ 36 | private final long mUntil; 37 | 38 | 39 | /** 40 | * Create a new limiter for the UNTIL part. 41 | * 42 | * @param rule 43 | * The {@link RecurrenceRule} to filter. 44 | * @param previous 45 | * The previous filter instance. 46 | */ 47 | public UntilLimiter(RecurrenceRule rule, RuleIterator previous, TimeZone startTimezone) 48 | { 49 | super(previous); 50 | DateTime until = rule.getUntil(); 51 | if (!until.isFloating()) 52 | { 53 | until = until.shiftTimeZone(startTimezone); 54 | } 55 | mUntil = until.getInstance(); 56 | } 57 | 58 | 59 | @Override 60 | boolean stop(long instance) 61 | { 62 | return mUntil < Instance.maskWeekday(instance); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recurrenceset/Difference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recurrenceset; 19 | 20 | import org.dmfs.rfc5545.InstanceIterator; 21 | import org.dmfs.rfc5545.RecurrenceSet; 22 | import org.dmfs.rfc5545.instanceiterator.EffectiveInstancesIterator; 23 | 24 | /** 25 | * A {@link RecurrenceSet} that contains all the instances of a given {@link RecurrenceSet} except the ones that are 26 | * in the exceptions {@link RecurrenceSet}. 27 | */ 28 | public final class Difference implements RecurrenceSet 29 | { 30 | private final RecurrenceSet mMinuend; 31 | private final RecurrenceSet mSubtrahend; 32 | 33 | public Difference(RecurrenceSet minuend, RecurrenceSet subtrahend) 34 | { 35 | mMinuend = minuend; 36 | mSubtrahend = subtrahend; 37 | } 38 | 39 | @Override 40 | public InstanceIterator iterator() 41 | { 42 | return new EffectiveInstancesIterator(mMinuend.iterator(), mSubtrahend.iterator()); 43 | } 44 | 45 | @Override 46 | public boolean isInfinite() 47 | { 48 | return mMinuend.isInfinite(); 49 | } 50 | 51 | @Override 52 | public boolean isFinite() 53 | { 54 | return mMinuend.isFinite(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recurrenceset/FastForwarded.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recurrenceset; 19 | 20 | import org.dmfs.jems2.iterable.Seq; 21 | import org.dmfs.rfc5545.DateTime; 22 | import org.dmfs.rfc5545.InstanceIterator; 23 | import org.dmfs.rfc5545.RecurrenceSet; 24 | 25 | 26 | /** 27 | * A {@link RecurrenceSet} that fast forwards the iteration to a given instant. 28 | * All instances prior to that instant will be skipped. 29 | */ 30 | public final class FastForwarded implements RecurrenceSet 31 | { 32 | private final DateTime mTimeStamp; 33 | private final RecurrenceSet mDelegate; 34 | 35 | 36 | public FastForwarded(DateTime fastForwardTo, RecurrenceSet... delegate) 37 | { 38 | this(fastForwardTo, new Seq<>(delegate)); 39 | } 40 | 41 | 42 | public FastForwarded(DateTime fastForwardTo, Iterable delegate) 43 | { 44 | this(fastForwardTo, new Merged(delegate)); 45 | } 46 | 47 | 48 | public FastForwarded(DateTime timeStamp, RecurrenceSet delegate) 49 | { 50 | mTimeStamp = timeStamp; 51 | mDelegate = delegate; 52 | } 53 | 54 | 55 | @Override 56 | public InstanceIterator iterator() 57 | { 58 | InstanceIterator iterator = mDelegate.iterator(); 59 | iterator.fastForward(mTimeStamp); 60 | return iterator; 61 | } 62 | 63 | @Override 64 | public boolean isInfinite() 65 | { 66 | return mDelegate.isInfinite(); 67 | } 68 | 69 | @Override 70 | public boolean isFinite() 71 | { 72 | return mDelegate.isFinite(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recurrenceset/Merged.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recurrenceset; 19 | 20 | import org.dmfs.jems2.iterable.Mapped; 21 | import org.dmfs.jems2.iterable.Seq; 22 | import org.dmfs.jems2.iterable.Sieved; 23 | import org.dmfs.jems2.predicate.Not; 24 | import org.dmfs.rfc5545.InstanceIterator; 25 | import org.dmfs.rfc5545.RecurrenceSet; 26 | 27 | 28 | /** 29 | * A composite {@link RecurrenceSet} composed of other {@link RecurrenceSet}s. This {@link RecurrenceSet} 30 | * iterates the instances of all given {@link RecurrenceSet}s in chronological order. 31 | */ 32 | public final class Merged implements RecurrenceSet 33 | { 34 | private final RecurrenceSet mDelegate; 35 | 36 | 37 | public Merged(RecurrenceSet... delegates) 38 | { 39 | this(new Seq<>(delegates)); 40 | } 41 | 42 | 43 | public Merged(Iterable delegates) 44 | { 45 | this( 46 | new RecurrenceSet() 47 | { 48 | @Override 49 | public InstanceIterator iterator() 50 | { 51 | return new org.dmfs.rfc5545.instanceiterator.Merged(new Mapped<>(RecurrenceSet::iterator, delegates)); 52 | } 53 | 54 | @Override 55 | public boolean isInfinite() 56 | { 57 | return new Sieved<>(RecurrenceSet::isInfinite, delegates).iterator().hasNext(); 58 | } 59 | 60 | @Override 61 | public boolean isFinite() 62 | { 63 | return !new Sieved<>(new Not<>(RecurrenceSet::isFinite), delegates).iterator().hasNext(); 64 | } 65 | }); 66 | } 67 | 68 | 69 | private Merged(RecurrenceSet delegate) 70 | { 71 | mDelegate = delegate; 72 | } 73 | 74 | 75 | @Override 76 | public InstanceIterator iterator() 77 | { 78 | return mDelegate.iterator(); 79 | } 80 | 81 | @Override 82 | public boolean isInfinite() 83 | { 84 | return mDelegate.isInfinite(); 85 | } 86 | 87 | @Override 88 | public boolean isFinite() 89 | { 90 | return mDelegate.isFinite(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recurrenceset/OfList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recurrenceset; 19 | 20 | import org.dmfs.jems2.comparator.By; 21 | import org.dmfs.jems2.iterable.Expanded; 22 | import org.dmfs.jems2.iterable.Frozen; 23 | import org.dmfs.jems2.iterable.Seq; 24 | import org.dmfs.jems2.iterable.Sorted; 25 | import org.dmfs.rfc5545.DateTime; 26 | import org.dmfs.rfc5545.InstanceIterator; 27 | import org.dmfs.rfc5545.RecurrenceSet; 28 | import org.dmfs.rfc5545.instanceiterator.EmptyIterator; 29 | import org.dmfs.rfc5545.instanceiterator.FastForwardable; 30 | import org.dmfs.rfc5545.iterable.ParsedDates; 31 | 32 | import java.util.Iterator; 33 | import java.util.TimeZone; 34 | 35 | 36 | /** 37 | * A {@link RecurrenceSet} of a given list of instances, typically provided in the form of {@code RDATE}s or {@code EXDATE}s. 38 | *

39 | * Note, that this class does not support parsing the {@code PERIOD} type specified in 40 | * RFC 5545, section 3.3.9. 41 | */ 42 | public final class OfList implements RecurrenceSet 43 | { 44 | private final Iterable mInstances; 45 | 46 | 47 | public OfList(TimeZone timeZone, String... instances) 48 | { 49 | this(timeZone, new Seq<>(instances)); 50 | } 51 | 52 | public OfList(TimeZone timeZone, Iterable instances) 53 | { 54 | this(new Expanded<>(list -> new ParsedDates(timeZone, list), instances)); 55 | } 56 | 57 | public OfList(TimeZone timeZone, String instances) 58 | { 59 | this(new ParsedDates(timeZone, instances)); 60 | } 61 | 62 | public OfList(String... instances) 63 | { 64 | this(new Expanded<>(ParsedDates::new, new Seq<>(instances))); 65 | } 66 | 67 | public OfList(String instances) 68 | { 69 | this(new ParsedDates(instances)); 70 | } 71 | 72 | public OfList(DateTime... instances) 73 | { 74 | this(new Seq<>(instances)); 75 | } 76 | 77 | public OfList(Iterable instances) 78 | { 79 | mInstances = new Frozen<>(new Sorted<>(new By<>(DateTime::getTimestamp), instances)); 80 | } 81 | 82 | @Override 83 | public InstanceIterator iterator() 84 | { 85 | Iterator delegate = mInstances.iterator(); 86 | return delegate.hasNext() 87 | ? new FastForwardable(delegate.next(), delegate.hasNext() ? delegate : EmptyIterator.INSTANCE) 88 | : EmptyIterator.INSTANCE; 89 | } 90 | 91 | @Override 92 | public boolean isInfinite() 93 | { 94 | return false; 95 | } 96 | 97 | @Override 98 | public boolean isFinite() 99 | { 100 | return true; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recurrenceset/OfRule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recurrenceset; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.InstanceIterator; 22 | import org.dmfs.rfc5545.RecurrenceSet; 23 | import org.dmfs.rfc5545.recur.RecurrenceRule; 24 | 25 | /** 26 | * The {@link RecurrenceSet} of a single {@link RecurrenceRule}. 27 | */ 28 | public final class OfRule implements RecurrenceSet 29 | { 30 | private final RecurrenceRule mRecurrenceRule; 31 | private final DateTime mFirstInstance; 32 | 33 | public OfRule(RecurrenceRule recurrenceRule, DateTime firstInstance) 34 | { 35 | mRecurrenceRule = recurrenceRule; 36 | mFirstInstance = firstInstance; 37 | } 38 | 39 | @Override 40 | public InstanceIterator iterator() 41 | { 42 | return mRecurrenceRule.iterator(mFirstInstance); 43 | } 44 | 45 | @Override 46 | public boolean isInfinite() 47 | { 48 | return mRecurrenceRule.isInfinite(); 49 | } 50 | 51 | @Override 52 | public boolean isFinite() 53 | { 54 | return !mRecurrenceRule.isInfinite(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recurrenceset/OfRuleAndFirst.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recurrenceset; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.InstanceIterator; 22 | import org.dmfs.rfc5545.RecurrenceSet; 23 | import org.dmfs.rfc5545.instanceiterator.CountLimitedRecurrenceRuleIterator; 24 | import org.dmfs.rfc5545.instanceiterator.Merged; 25 | import org.dmfs.rfc5545.recur.RecurrenceRule; 26 | import org.dmfs.rfc5545.recur.RecurrenceRuleIterator; 27 | 28 | 29 | /** 30 | * The {@link RecurrenceSet} of a single {@link RecurrenceRule} that also returns any non-synchronized first instance. 31 | */ 32 | public final class OfRuleAndFirst implements RecurrenceSet 33 | { 34 | private final RecurrenceRule mRecurrenceRule; 35 | private final DateTime mFirst; 36 | 37 | /** 38 | * Create a new adapter for the given rule and start. 39 | * 40 | * @param rule The recurrence rule to adapt to. 41 | */ 42 | public OfRuleAndFirst(RecurrenceRule rule, DateTime first) 43 | { 44 | mRecurrenceRule = rule; 45 | mFirst = first; 46 | } 47 | 48 | @Override 49 | public InstanceIterator iterator() 50 | { 51 | RecurrenceRuleIterator ruleIterator = mRecurrenceRule.iterator(mFirst); 52 | if (!ruleIterator.peekDateTime().equals(mFirst)) 53 | { 54 | return new Merged( 55 | new OfList(mFirst).iterator(), 56 | mRecurrenceRule.getCount() != null 57 | // we have a count limited rule and an unsynced start date 58 | // since the start date counts as the first element, the RRULE iterator should return one less element. 59 | ? new CountLimitedRecurrenceRuleIterator(ruleIterator, mRecurrenceRule.getCount() - 1) 60 | : ruleIterator); 61 | } 62 | return ruleIterator; 63 | } 64 | 65 | @Override 66 | public boolean isInfinite() 67 | { 68 | return mRecurrenceRule.isInfinite(); 69 | } 70 | 71 | @Override 72 | public boolean isFinite() 73 | { 74 | return !mRecurrenceRule.isInfinite(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recurrenceset/Preceding.java: -------------------------------------------------------------------------------- 1 | package org.dmfs.rfc5545.recurrenceset; 2 | 3 | import org.dmfs.rfc5545.DateTime; 4 | import org.dmfs.rfc5545.InstanceIterator; 5 | import org.dmfs.rfc5545.RecurrenceSet; 6 | import org.dmfs.rfc5545.instanceiterator.PeekableInstanceIterator; 7 | 8 | import java.util.NoSuchElementException; 9 | 10 | /** 11 | * A {@link RecurrenceSet} of all elements of another {@link RecurrenceSet} that precede a 12 | * given {@link DateTime}. 13 | * A {@link Preceding} {@link RecurrenceSet} is always finite. 14 | * 15 | *

Example

16 | *
{@code
17 |  * // a RecurrenceSet that only contains past occurrences.
18 |  * new Preceding(DateTime.now(), recurrenceSet());
19 |  * }
20 | */ 21 | public final class Preceding implements RecurrenceSet 22 | { 23 | private final DateTime mBoundary; 24 | private final RecurrenceSet mDelegate; 25 | 26 | public Preceding(DateTime boundary, RecurrenceSet delegate) 27 | { 28 | mBoundary = boundary; 29 | mDelegate = delegate; 30 | } 31 | 32 | @Override 33 | public InstanceIterator iterator() 34 | { 35 | PeekableInstanceIterator delegate = new PeekableInstanceIterator(mDelegate.iterator()); 36 | return new InstanceIterator() 37 | { 38 | @Override 39 | public void fastForward(DateTime until) 40 | { 41 | delegate.fastForward(until); 42 | } 43 | 44 | @Override 45 | public boolean hasNext() 46 | { 47 | return delegate.hasNext() && delegate.peek().before(mBoundary); 48 | } 49 | 50 | @Override 51 | public DateTime next() 52 | { 53 | DateTime result = delegate.next(); 54 | if (!result.before(mBoundary)) 55 | { 56 | throw new NoSuchElementException("No more elements"); 57 | } 58 | return result; 59 | } 60 | }; 61 | } 62 | 63 | @Override 64 | public boolean isInfinite() 65 | { 66 | return false; 67 | } 68 | 69 | @Override 70 | public boolean isFinite() 71 | { 72 | return true; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recurrenceset/Within.java: -------------------------------------------------------------------------------- 1 | package org.dmfs.rfc5545.recurrenceset; 2 | 3 | import org.dmfs.rfc5545.DateTime; 4 | import org.dmfs.rfc5545.RecurrenceSet; 5 | import org.dmfs.rfc5545.RecurrenceSetComposition; 6 | 7 | /** 8 | * {@link RecurrenceSet} of the elements of another {@link RecurrenceSet} that fall 9 | * in the given right-open interval of time. 10 | * A {@link Within} {@link RecurrenceSet} is always finite. 11 | * 12 | *

Example

13 | *
{@code
14 |  * // every occurrence in 2024 (UTC)
15 |  * new Within(
16 |  *   DateTime.parse("20240101T000000Z"),
17 |  *   DateTime.parse("20250101T000000Z"),
18 |  *   recurrenceSet());
19 |  * }
20 | */ 21 | public final class Within extends RecurrenceSetComposition 22 | { 23 | public Within(DateTime fromIncluding, DateTime toExcluding, RecurrenceSet delegate) 24 | { 25 | super(new Preceding(toExcluding, new FastForwarded(fromIncluding, delegate))); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/dmfs/rfc5545/recurrenceset/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | @NonNullByDefault 19 | package org.dmfs.rfc5545.recurrenceset; 20 | 21 | import org.eclipse.jdt.annotation.NonNullByDefault; -------------------------------------------------------------------------------- /src/test/java/org/dmfs/rfc5545/iterable/ParsedDatesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.iterable; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.iterable.ParsedDates; 22 | import org.junit.jupiter.api.Test; 23 | import org.saynotobugs.confidence.quality.Core; 24 | 25 | import static java.util.TimeZone.getTimeZone; 26 | import static org.saynotobugs.confidence.Assertion.assertThat; 27 | import static org.saynotobugs.confidence.quality.Core.*; 28 | 29 | class ParsedDatesTest 30 | { 31 | @Test 32 | void testEmpty() 33 | { 34 | assertThat(new ParsedDates(""), is(emptyIterable())); 35 | } 36 | 37 | @Test 38 | void testSingletonDate() 39 | { 40 | assertThat(new ParsedDates("20240216"), Core.iterates(DateTime.parse("20240216"))); 41 | } 42 | 43 | @Test 44 | void testMultipleDates() 45 | { 46 | assertThat(new ParsedDates("20240216,20240217,20240218"), 47 | iterates( 48 | DateTime.parse("20240216"), 49 | DateTime.parse("20240217"), 50 | DateTime.parse("20240218"))); 51 | } 52 | 53 | @Test 54 | void testSingletonDateTime() 55 | { 56 | assertThat(new ParsedDates("20240216T123456"), iterates(DateTime.parse("20240216T123456"))); 57 | } 58 | 59 | @Test 60 | void testMultipleDateTimes() 61 | { 62 | assertThat(new ParsedDates("20240216T123456,20240217T123456,20240218T123456"), 63 | iterates( 64 | DateTime.parse("20240216T123456"), 65 | DateTime.parse("20240217T123456"), 66 | DateTime.parse("20240218T123456"))); 67 | } 68 | 69 | @Test 70 | void testAbsoluteSingletonDateTime() 71 | { 72 | assertThat(new ParsedDates(getTimeZone("Europe/Berlin"), "20240216T123456"), 73 | iterates(DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T123456"))); 74 | } 75 | 76 | @Test 77 | void testAbsoluteMultipleDateTimes() 78 | { 79 | assertThat(new ParsedDates(getTimeZone("Europe/Berlin"), "20240216T123456,20240217T123456,20240218T123456"), 80 | iterates( 81 | DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T123456"), 82 | DateTime.parse(getTimeZone("Europe/Berlin"), "20240217T123456"), 83 | DateTime.parse(getTimeZone("Europe/Berlin"), "20240218T123456"))); 84 | } 85 | } -------------------------------------------------------------------------------- /src/test/java/org/dmfs/rfc5545/optional/LastInstanceTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.optional; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.recur.InvalidRecurrenceRuleException; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule; 23 | import org.dmfs.rfc5545.recurrenceset.OfList; 24 | import org.dmfs.rfc5545.recurrenceset.OfRule; 25 | import org.junit.jupiter.api.Test; 26 | 27 | import static org.dmfs.jems2.confidence.Jems2.absent; 28 | import static org.dmfs.jems2.confidence.Jems2.present; 29 | import static org.dmfs.jems2.iterable.EmptyIterable.emptyIterable; 30 | import static org.saynotobugs.confidence.Assertion.assertThat; 31 | import static org.saynotobugs.confidence.quality.Core.is; 32 | 33 | class LastInstanceTest 34 | { 35 | @Test 36 | void testEmptyList() 37 | { 38 | assertThat(new LastInstance(new OfList(emptyIterable())), 39 | is(absent())); 40 | } 41 | 42 | 43 | @Test 44 | void testSingleton() 45 | { 46 | assertThat(new LastInstance(new OfList(DateTime.parse("20240215"))), 47 | is(present(DateTime.parse("20240215")))); 48 | } 49 | 50 | @Test 51 | void testFiniteRule() throws InvalidRecurrenceRuleException 52 | { 53 | assertThat(new LastInstance( 54 | new OfRule( 55 | new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240215"))), 56 | is(present(DateTime.parse("20240219")))); 57 | } 58 | 59 | 60 | @Test 61 | void testInfiniteRule() throws InvalidRecurrenceRuleException 62 | { 63 | assertThat(new LastInstance( 64 | new OfRule( 65 | new RecurrenceRule("FREQ=DAILY"), DateTime.parse("20240215"))), 66 | is(absent())); 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /src/test/java/org/dmfs/rfc5545/recur/LongArrayTest.java: -------------------------------------------------------------------------------- 1 | package org.dmfs.rfc5545.recur; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.assertFalse; 7 | 8 | 9 | public class LongArrayTest 10 | { 11 | 12 | @Test 13 | public void testDeduplicate() 14 | { 15 | LongArray l1 = new LongArray(); 16 | l1.add(1); 17 | l1.add(2); 18 | l1.add(3); 19 | l1.add(4); 20 | l1.add(5); 21 | l1.add(6); 22 | 23 | l1.deduplicate(); 24 | assertEquals(6, l1.size()); 25 | assertEquals(1, l1.next()); 26 | assertEquals(2, l1.next()); 27 | assertEquals(3, l1.next()); 28 | assertEquals(4, l1.next()); 29 | assertEquals(5, l1.next()); 30 | assertEquals(6, l1.next()); 31 | assertFalse(l1.hasNext()); 32 | 33 | LongArray l2 = new LongArray(); 34 | l2.add(1); 35 | l2.add(1); 36 | l2.add(1); 37 | l2.add(1); 38 | l2.add(2); 39 | l2.add(2); 40 | l2.add(2); 41 | l2.add(2); 42 | l2.add(3); 43 | l2.add(3); 44 | l2.add(3); 45 | l2.add(3); 46 | l2.add(3); 47 | l2.add(3); 48 | l2.add(3); 49 | l2.add(3); 50 | l2.add(3); 51 | l2.add(3); 52 | l2.add(4); 53 | l2.add(5); 54 | l2.add(5); 55 | l2.add(5); 56 | l2.add(5); 57 | l2.add(5); 58 | l2.add(6); 59 | l2.add(6); 60 | l2.add(6); 61 | l2.add(6); 62 | l2.add(6); 63 | l2.add(6); 64 | l2.add(6); 65 | l2.add(6); 66 | l2.add(6); 67 | l2.add(6); 68 | l2.add(6); 69 | l2.add(6); 70 | l2.add(6); 71 | 72 | l2.deduplicate(); 73 | assertEquals(6, l2.size()); 74 | assertEquals(1, l2.next()); 75 | assertEquals(2, l2.next()); 76 | assertEquals(3, l2.next()); 77 | assertEquals(4, l2.next()); 78 | assertEquals(5, l2.next()); 79 | assertEquals(6, l2.next()); 80 | assertFalse(l2.hasNext()); 81 | 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/org/dmfs/rfc5545/recurrenceset/OfRuleTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Marten Gajda 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.dmfs.rfc5545.recurrenceset; 19 | 20 | import org.dmfs.rfc5545.DateTime; 21 | import org.dmfs.rfc5545.recur.InvalidRecurrenceRuleException; 22 | import org.dmfs.rfc5545.recur.RecurrenceRule; 23 | import org.junit.jupiter.api.Test; 24 | 25 | import static org.dmfs.rfc5545.confidence.Recur.*; 26 | import static org.saynotobugs.confidence.Assertion.assertThat; 27 | import static org.saynotobugs.confidence.quality.Core.*; 28 | 29 | class OfRuleTest 30 | { 31 | @Test 32 | void testRuleWithCount() throws InvalidRecurrenceRuleException 33 | { 34 | assertThat(new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240215")), 35 | allOf( 36 | is(finite()), 37 | iterates( 38 | DateTime.parse("20240215"), 39 | DateTime.parse("20240216"), 40 | DateTime.parse("20240217"), 41 | DateTime.parse("20240218"), 42 | DateTime.parse("20240219")))); 43 | } 44 | 45 | @Test 46 | void testRuleWithUntil() throws InvalidRecurrenceRuleException 47 | { 48 | assertThat(new OfRule(new RecurrenceRule("FREQ=DAILY;UNTIL=20240219"), DateTime.parse("20240215")), 49 | allOf( 50 | is(finite()), 51 | iterates( 52 | DateTime.parse("20240215"), 53 | DateTime.parse("20240216"), 54 | DateTime.parse("20240217"), 55 | DateTime.parse("20240218"), 56 | DateTime.parse("20240219")))); 57 | } 58 | 59 | 60 | @Test 61 | void testInfiniteRule() throws InvalidRecurrenceRuleException 62 | { 63 | assertThat(new OfRule(new RecurrenceRule("FREQ=DAILY"), DateTime.parse("20240215")), 64 | allOf( 65 | is(infinite()), 66 | startsWith( 67 | DateTime.parse("20240215"), 68 | DateTime.parse("20240216"), 69 | DateTime.parse("20240217"), 70 | DateTime.parse("20240218"), 71 | DateTime.parse("20240219")))); 72 | } 73 | } -------------------------------------------------------------------------------- /src/test/java/org/dmfs/rfc5545/recurrenceset/WithinTest.java: -------------------------------------------------------------------------------- 1 | package org.dmfs.rfc5545.recurrenceset; 2 | 3 | import org.dmfs.rfc5545.DateTime; 4 | import org.saynotobugs.confidence.junit5.engine.Assertion; 5 | import org.saynotobugs.confidence.junit5.engine.Confidence; 6 | import org.saynotobugs.confidence.junit5.engine.assertion.AssertionThat; 7 | 8 | import static org.dmfs.rfc5545.confidence.Recur.emptyRecurrenceSet; 9 | import static org.dmfs.rfc5545.confidence.Recur.finite; 10 | import static org.saynotobugs.confidence.quality.Core.*; 11 | 12 | @Confidence 13 | class WithinTest 14 | { 15 | 16 | Assertion within_empty_RecurrenceSet_remains_empty = 17 | new AssertionThat<>( 18 | new Within( 19 | DateTime.parse("20240406"), 20 | DateTime.parse("20240408"), 21 | new OfList("")), 22 | is(emptyRecurrenceSet())); 23 | 24 | Assertion within_before_delegate_is_empty = 25 | new AssertionThat<>( 26 | new Within( 27 | DateTime.parse("20240306"), 28 | DateTime.parse("20240406"), 29 | new OfList("20240505,20240606,20240707")), 30 | is(emptyRecurrenceSet())); 31 | 32 | Assertion within_after_delegate_is_empty = 33 | new AssertionThat<>( 34 | new Within( 35 | DateTime.parse("20241006"), 36 | DateTime.parse("20241106"), 37 | new OfList("20240505,20240606,20240707")), 38 | is(emptyRecurrenceSet())); 39 | 40 | Assertion within_entire_delegate_is_delegate = 41 | new AssertionThat<>( 42 | new Within( 43 | DateTime.parse("20240106"), 44 | DateTime.parse("20241106"), 45 | new OfList("20240505,20240606,20240707")), 46 | is(allOf( 47 | finite(), 48 | iterates( 49 | DateTime.parse("20240505"), 50 | DateTime.parse("20240606"), 51 | DateTime.parse("20240707") 52 | ) 53 | ))); 54 | 55 | Assertion within_partial_delegate_is_partial_delegate = 56 | new AssertionThat<>( 57 | new Within( 58 | DateTime.parse("20240506"), 59 | DateTime.parse("20240706"), 60 | new OfList("20240505,20240606,20240707")), 61 | is(allOf( 62 | finite(), 63 | iterates(DateTime.parse("20240606")) 64 | ))); 65 | 66 | Assertion within_same_start_and_end_is_empty = 67 | new AssertionThat<>( 68 | new Within( 69 | DateTime.parse("20240606"), 70 | DateTime.parse("20240606"), 71 | new OfList("20240505,20240606,20240707")), 72 | is(emptyRecurrenceSet())); 73 | 74 | } --------------------------------------------------------------------------------