commit 2aead18a0fcb6ac754f290341c1971cff3c49032 Author: Jörg Prante Date: Wed Nov 2 11:46:35 2016 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86023d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +/data +/work +/logs +/.idea +/target +.DS_Store +*.iml +/.settings +/.classpath +/.project +/.gradle +build +/plugins +/sessions +*~ +*.MARC diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..d12b784 --- /dev/null +++ b/build.gradle @@ -0,0 +1,79 @@ +plugins { + id "org.sonarqube" version "2.2" + id 'org.ajoberstar.github-pages' version '1.6.0-rc.1' + id "org.xbib.gradle.plugin.jbake" version "1.1.0" +} + +group = 'org.xbib' +version = '1.0.0' + +printf "Host: %s\nOS: %s %s %s\nJVM: %s %s %s %s\nGroovy: %s\nGradle: %s\n" + + "Build: group: ${project.group} name: ${project.name} version: ${project.version}\n", + InetAddress.getLocalHost(), + System.getProperty("os.name"), + System.getProperty("os.arch"), + System.getProperty("os.version"), + System.getProperty("java.version"), + System.getProperty("java.vm.version"), + System.getProperty("java.vm.vendor"), + System.getProperty("java.vm.name"), + GroovySystem.getVersion(), + gradle.gradleVersion + +apply plugin: 'java' +apply plugin: 'maven' +apply plugin: 'signing' +apply plugin: 'findbugs' +apply plugin: 'pmd' +apply plugin: 'checkstyle' +apply plugin: "jacoco" +apply plugin: 'org.ajoberstar.github-pages' + +configurations { + wagon + provided + testCompile.extendsFrom(provided) +} + +dependencies { + testCompile 'junit:junit:4.12' + testCompile 'org.apache.logging.log4j:log4j-core:2.7' + testCompile 'org.apache.logging.log4j:log4j-jul:2.7' + wagon 'org.apache.maven.wagon:wagon-ssh-external:2.10' +} + +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 + +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' +tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:all" << "-profile" << "compact2" +} + +test { + testLogging { + showStandardStreams = false + exceptionFormat = 'full' + } + systemProperty 'java.util.logging.manager', 'org.apache.logging.log4j.jul.LogManager' +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier 'sources' + from sourceSets.main.allSource +} +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier 'javadoc' +} +artifacts { + archives sourcesJar, javadocJar +} +if (project.hasProperty('signing.keyId')) { + signing { + sign configurations.archives + } +} + +apply from: 'gradle/ext.gradle' +apply from: 'gradle/publish.gradle' +apply from: 'gradle/sonarqube.gradle' diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000..52fe33c --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,323 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/ext.gradle b/gradle/ext.gradle new file mode 100644 index 0000000..7dc1403 --- /dev/null +++ b/gradle/ext.gradle @@ -0,0 +1,8 @@ +ext { + user = 'xbib' + projectName = 'time' + projectDescription = 'A collection of date/time methods for Java 8' + scmUrl = 'https://github.com/xbib/time' + scmConnection = 'scm:git:git://github.com/xbib/time.git' + scmDeveloperConnection = 'scm:git:git://github.com/xbib/time.git' +} diff --git a/gradle/publish.gradle b/gradle/publish.gradle new file mode 100644 index 0000000..663c405 --- /dev/null +++ b/gradle/publish.gradle @@ -0,0 +1,73 @@ + +task xbibUpload(type: Upload) { + configuration = configurations.archives + uploadDescriptor = true + repositories { + if (project.hasProperty('xbibUsername')) { + mavenDeployer { + configuration = configurations.wagon + repository(url: uri('scpexe://xbib.org/repository')) { + authentication(userName: xbibUsername, privateKey: xbibPrivateKey) + } + } + } + } +} + +task sonatypeUpload(type: Upload) { + configuration = configurations.archives + uploadDescriptor = true + repositories { + if (project.hasProperty('ossrhUsername')) { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + repository(url: uri(ossrhReleaseUrl)) { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + snapshotRepository(url: uri(ossrhSnapshotUrl)) { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + pom.project { + name projectName + description projectDescription + packaging 'jar' + inceptionYear '2016' + url scmUrl + organization { + name 'xbib' + url 'http://xbib.org' + } + developers { + developer { + id user + name 'Jörg Prante' + email 'joergprante@gmail.com' + url 'https://github.com/jprante' + } + } + scm { + url scmUrl + connection scmConnection + developerConnection scmDeveloperConnection + } + licenses { + license { + name 'The Apache License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + } + } + } + } + } + } +} + +githubPages { + repoUri = 'git@github.com:xbib/marc.git' + targetBranch = "gh-pages" + pages { + from(file('build/jbake')) { + into '.' + } + } +} diff --git a/gradle/sonarqube.gradle b/gradle/sonarqube.gradle new file mode 100644 index 0000000..6d4c3fa --- /dev/null +++ b/gradle/sonarqube.gradle @@ -0,0 +1,41 @@ +tasks.withType(FindBugs) { + ignoreFailures = true + reports { + xml.enabled = true + html.enabled = false + } +} +tasks.withType(Pmd) { + ignoreFailures = true + reports { + xml.enabled = true + html.enabled = true + } +} +tasks.withType(Checkstyle) { + ignoreFailures = true + reports { + xml.enabled = true + html.enabled = true + } +} + +jacocoTestReport { + reports { + xml.enabled true + csv.enabled false + xml.destination "${buildDir}/reports/jacoco-xml" + html.destination "${buildDir}/reports/jacoco-html" + } +} + +sonarqube { + properties { + property "sonar.projectName", "${project.group} ${project.name}" + property "sonar.sourceEncoding", "UTF-8" + property "sonar.tests", "src/test/java" + property "sonar.scm.provider", "git" + property "sonar.java.coveragePlugin", "jacoco" + property "sonar.junit.reportsPath", "build/test-results/test/" + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..6ffa237 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3ba7d3b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun Oct 02 23:57:55 CEST 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..90eb05d --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'time' diff --git a/src/main/java/org/xbib/time/chronic/Chronic.java b/src/main/java/org/xbib/time/chronic/Chronic.java new file mode 100644 index 0000000..4b62eae --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/Chronic.java @@ -0,0 +1,193 @@ +package org.xbib.time.chronic; + +import org.xbib.time.chronic.handlers.Handler; +import org.xbib.time.chronic.numerizer.Numerizer; +import org.xbib.time.chronic.repeaters.Repeater; +import org.xbib.time.chronic.tags.*; + +import java.text.ParseException; +import java.util.LinkedList; +import java.util.List; + +/** + * + */ +public class Chronic { + + private Chronic() { + } + + public static Span parse(String text) throws ParseException { + return Chronic.parse(text, new Options()); + } + + /** + * Parses a string containing a natural language date or time. If the parser + * can find a date or time, either a Time or Chronic::Span will be returned + * (depending on the value of :guess). If no date or time can be found, + * +nil+ will be returned. + *

+ * Options are: + *

+ * [:context] + * :past or :future (defaults to :future) + *

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

+ * [:now] + * Time (defaults to Time.now) + *

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

+ * [:guess] + * +true+ or +false+ (defaults to +true+) + *

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

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

+ * If an Integer is given, ambiguous times (like 5:00) will be + * assumed to be within the range of that time in the AM to that time + * in the PM. For example, if you set it to 7, then the parser will + * look for the time between 7am and 7pm. In the case of 5:00, it would + * assume that means 5:00pm. If :none is given, no assumption + * will be made, and the first matching instance of that time will + * be used. + * @param text text + * @param options options + * @return span + * @throws ParseException parse exception + */ + @SuppressWarnings("unchecked") + public static Span parse(String text, Options options) throws ParseException { + String normalizedText = Chronic.preNormalize(text); + List tokens = Chronic.baseTokenize(normalizedText); + List> optionScannerClasses = new LinkedList<>(); + optionScannerClasses.add(Repeater.class); + for (Class optionScannerClass : optionScannerClasses) { + try { + tokens = (List) optionScannerClass.getMethod("scan", List.class, Options.class) + .invoke(null, tokens, options); + } catch (Throwable e) { + throw new ParseException("failed to scan tokens", 0); + } + } + List> scannerClasses = new LinkedList<>(); + scannerClasses.add(Grabber.class); + scannerClasses.add(Pointer.class); + scannerClasses.add(Scalar.class); + scannerClasses.add(Ordinal.class); + scannerClasses.add(Separator.class); + scannerClasses.add(TimeZone.class); + for (Class scannerClass : scannerClasses) { + try { + tokens = (List) scannerClass.getMethod("scan", List.class, Options.class) + .invoke(null, tokens, options); + } catch (Throwable e) { + throw new ParseException("failed to scan tokens", 0); + } + } + List taggedTokens = new LinkedList<>(); + for (Token token : tokens) { + if (token.isTagged()) { + taggedTokens.add(token); + } + } + tokens = taggedTokens; + Span span = Handler.tokensToSpan(tokens, options); + // guess a time within a span if required + if (options.isGuess()) { + span = guess(span); + } + return span; + } + + /** + * Clean up the specified input text by stripping unwanted characters, + * converting idioms to their canonical form, converting number words + * to numbers (three => 3), and converting ordinal words to numeric + * ordinals (third => 3rd). + * @param text text + * @return string + */ + protected static String preNormalize(String text) { + String normalizedText = text.toLowerCase(); + normalizedText = Chronic.numericizeNumbers(normalizedText); + normalizedText = normalizedText.replaceAll("['\"\\.]", ""); + normalizedText = normalizedText.replaceAll("([/\\-,@])", " $1 "); + normalizedText = normalizedText.replaceAll("\\btoday\\b", "this day"); + normalizedText = normalizedText.replaceAll("\\btomm?orr?ow\\b", "next day"); + normalizedText = normalizedText.replaceAll("\\byesterday\\b", "last day"); + normalizedText = normalizedText.replaceAll("\\bnoon\\b", "12:00"); + normalizedText = normalizedText.replaceAll("\\bmidnight\\b", "24:00"); + normalizedText = normalizedText.replaceAll("\\bbefore now\\b", "past"); + normalizedText = normalizedText.replaceAll("\\bnow\\b", "this second"); + normalizedText = normalizedText.replaceAll("\\b(ago|before)\\b", "past"); + normalizedText = normalizedText.replaceAll("\\bthis past\\b", "last"); + normalizedText = normalizedText.replaceAll("\\bthis last\\b", "last"); + normalizedText = normalizedText.replaceAll("\\b(?:in|during) the (morning)\\b", "$1"); + normalizedText = normalizedText.replaceAll("\\b(?:in the|during the|at) (afternoon|evening|night)\\b", "$1"); + normalizedText = normalizedText.replaceAll("\\btonight\\b", "this night"); + normalizedText = normalizedText.replaceAll("(?=\\w)([ap]m|oclock)\\b", " $1"); + normalizedText = normalizedText.replaceAll("\\b(hence|after|from)\\b", "future"); + normalizedText = Chronic.numericizeOrdinals(normalizedText); + return normalizedText; + } + + /** + * Convert number words to numbers (three => 3). + * @param text text + * @return string + */ + protected static String numericizeNumbers(String text) { + return Numerizer.numerize(text); + } + + /** + * Convert ordinal words to numeric ordinals (third => 3rd). + * @param text text + * @return string + */ + protected static String numericizeOrdinals(String text) { + return text; + } + + /** + * Split the text on spaces and convert each word into + * a Token. + * @param text text + * @return list of tokens + */ + protected static List baseTokenize(String text) { + String[] words = text.split(" "); + List tokens = new LinkedList<>(); + for (String word : words) { + tokens.add(new Token(word)); + } + return tokens; + } + + /** + * Guess a specific time within the given span. + * @param span span + * @return span + */ + protected static Span guess(Span span) { + if (span == null) { + return null; + } + Long guessValue; + if (span.getWidth() > 1) { + guessValue = span.getBegin() + (span.getWidth() / 2); + } else { + guessValue = span.getBegin(); + } + return new Span(guessValue, guessValue, span.getZoneId()); + } +} diff --git a/src/main/java/org/xbib/time/chronic/Options.java b/src/main/java/org/xbib/time/chronic/Options.java new file mode 100644 index 0000000..6945431 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/Options.java @@ -0,0 +1,81 @@ +package org.xbib.time.chronic; + +import org.xbib.time.chronic.tags.Pointer; + +import java.time.ZoneId; +import java.time.ZonedDateTime; + +/** + * + */ +public class Options { + private ZonedDateTime now; + private ZoneId zoneId; + private Pointer.PointerType context; + private boolean guess; + private int ambiguousTimeRange; + private boolean compatibilityMode; + + public Options() { + setContext(Pointer.PointerType.FUTURE); + setNow(ZonedDateTime.now()); + setZoneId(ZoneId.of("GMT")); + setGuess(true); + setAmbiguousTimeRange(6); + } + + public boolean isCompatibilityMode() { + return compatibilityMode; + } + + public Options setCompatibilityMode(boolean compatibilityMode) { + this.compatibilityMode = compatibilityMode; + return this; + } + + public Pointer.PointerType getContext() { + return context; + } + + public Options setContext(Pointer.PointerType context) { + this.context = context; + return this; + } + + public ZonedDateTime getNow() { + return now; + } + + public Options setNow(ZonedDateTime now) { + this.now = now; + return this; + } + + public ZoneId getZoneId() { + return zoneId; + } + + public Options setZoneId(ZoneId zoneId) { + this.zoneId = zoneId; + return this; + } + + public boolean isGuess() { + return guess; + } + + public Options setGuess(boolean guess) { + this.guess = guess; + return this; + } + + public int getAmbiguousTimeRange() { + return ambiguousTimeRange; + } + + public Options setAmbiguousTimeRange(int ambiguousTimeRange) { + this.ambiguousTimeRange = ambiguousTimeRange; + return this; + } + +} diff --git a/src/main/java/org/xbib/time/chronic/Range.java b/src/main/java/org/xbib/time/chronic/Range.java new file mode 100644 index 0000000..0bd6239 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/Range.java @@ -0,0 +1,40 @@ +package org.xbib.time.chronic; + +/** + * + */ +public class Range { + private Long begin; + private Long end; + + public Range(long begin, long end) { + this.begin = begin; + this.end = end; + } + + public Long getBegin() { + return begin; + } + + public Long getEnd() { + return end; + } + + public Long getWidth() { + return getEnd() - getBegin(); + } + + public boolean contains(long value) { + return begin <= value && end >= value; + } + + @Override + public int hashCode() { + return begin.hashCode() * end.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Range && ((Range) obj).begin.equals(begin) && ((Range) obj).end.equals(end); + } +} diff --git a/src/main/java/org/xbib/time/chronic/Span.java b/src/main/java/org/xbib/time/chronic/Span.java new file mode 100644 index 0000000..940eb7f --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/Span.java @@ -0,0 +1,54 @@ +package org.xbib.time.chronic; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalUnit; + +/** + * + */ +public class Span extends Range { + + private final ZoneId zoneId; + + public Span(ZonedDateTime begin, TemporalUnit field, int amount) { + this(begin, begin.plus(amount, field)); + } + + public Span(ZonedDateTime begin, ZonedDateTime end) { + this(begin.toInstant(), end.toInstant(), begin.getZone()); + } + + public Span(Instant begin, Instant end, ZoneId zoneId) { + this(begin.getEpochSecond(), end.getEpochSecond(), zoneId); + } + + public Span(long begin, long end, ZoneId zoneId) { + super(begin, end); + this.zoneId = zoneId; + } + + public ZonedDateTime getBeginCalendar() { + return ZonedDateTime.ofInstant(Instant.ofEpochSecond(getBegin()), zoneId); + } + + public ZonedDateTime getEndCalendar() { + return ZonedDateTime.ofInstant(Instant.ofEpochSecond(getEnd()), zoneId); + } + + public Span add(long seconds) { + return new Span(getBegin() + seconds, getEnd() + seconds, zoneId); + } + + public ZoneId getZoneId() { + return zoneId; + } + + @Override + public String toString() { + return "(" + DateTimeFormatter.ISO_INSTANT.format(getBeginCalendar()) + + ".." + DateTimeFormatter.ISO_INSTANT.format(getEndCalendar()) + ")"; + } +} diff --git a/src/main/java/org/xbib/time/chronic/Tick.java b/src/main/java/org/xbib/time/chronic/Tick.java new file mode 100644 index 0000000..4bb12d8 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/Tick.java @@ -0,0 +1,35 @@ +package org.xbib.time.chronic; + +/** + * + */ +public class Tick { + private int time; + private boolean ambiguous; + + public Tick(int time, boolean ambiguous) { + this.time = time; + this.ambiguous = ambiguous; + } + + public boolean isAmbiguous() { + return ambiguous; + } + + public void setTime(int time) { + this.time = time; + } + + public Tick times(int other) { + return new Tick(time * other, ambiguous); + } + + public int intValue() { + return time; + } + + @Override + public String toString() { + return time + (ambiguous ? "?" : ""); + } +} diff --git a/src/main/java/org/xbib/time/chronic/Token.java b/src/main/java/org/xbib/time/chronic/Token.java new file mode 100644 index 0000000..13d07f7 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/Token.java @@ -0,0 +1,97 @@ +package org.xbib.time.chronic; + +import org.xbib.time.chronic.tags.Tag; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * + */ +public class Token { + private String word; + private List> tags; + + public Token(String word) { + this.word = word; + tags = new LinkedList<>(); + } + + public String getWord() { + return word; + } + + /** + * Tag this token with the specified tag. + * @param newTag new tag + */ + public void tag(Tag newTag) { + tags.add(newTag); + } + + /** + * Remove all tags of the given class. + * @param tagClass tag class + */ + public void untag(Class tagClass) { + Iterator> tagIter = tags.iterator(); + while (tagIter.hasNext()) { + Tag tag = tagIter.next(); + if (tagClass.isInstance(tag)) { + tagIter.remove(); + } + } + } + + /** + * Return true if this token has any tags. + * @return true if token has tags + */ + public boolean isTagged() { + return !tags.isEmpty(); + } + + /** + * Return the Tag that matches the given class. + * @param tagClass tag class + * @param type parameter + * @return tag + */ + @SuppressWarnings("unchecked") + public > T getTag(Class tagClass) { + List matches = getTags(tagClass); + T matchingTag = null; + if (matches.size() > 0) { + matchingTag = matches.get(0); + } + return matchingTag; + } + + public List> getTags() { + return tags; + } + + /** + * Return the Tag that matches the given class. + * + * @param tagClass tag class + * @param type parameter + * @return list of tags + */ + @SuppressWarnings("unchecked") + public > List getTags(Class tagClass) { + List matches = new LinkedList<>(); + for (Tag tag : tags) { + if (tagClass.isInstance(tag)) { + matches.add((T) tag); + } + } + return matches; + } + + @Override + public String toString() { + return word + " " + tags; + } +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/Handler.java b/src/main/java/org/xbib/time/chronic/handlers/Handler.java new file mode 100644 index 0000000..aa0214f --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/Handler.java @@ -0,0 +1,386 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.repeaters.EnumRepeaterDayPortion; +import org.xbib.time.chronic.repeaters.IntegerRepeaterDayPortion; +import org.xbib.time.chronic.repeaters.Repeater; +import org.xbib.time.chronic.repeaters.RepeaterDayName; +import org.xbib.time.chronic.repeaters.RepeaterDayPortion; +import org.xbib.time.chronic.repeaters.RepeaterMonthName; +import org.xbib.time.chronic.repeaters.RepeaterTime; +import org.xbib.time.chronic.tags.Grabber; +import org.xbib.time.chronic.tags.Ordinal; +import org.xbib.time.chronic.tags.OrdinalDay; +import org.xbib.time.chronic.tags.Pointer; +import org.xbib.time.chronic.tags.Pointer.PointerType; +import org.xbib.time.chronic.tags.Scalar; +import org.xbib.time.chronic.tags.ScalarDay; +import org.xbib.time.chronic.tags.ScalarMonth; +import org.xbib.time.chronic.tags.ScalarYear; +import org.xbib.time.chronic.tags.Separator; +import org.xbib.time.chronic.tags.SeparatorAt; +import org.xbib.time.chronic.tags.SeparatorComma; +import org.xbib.time.chronic.tags.SeparatorIn; +import org.xbib.time.chronic.tags.SeparatorSlashOrDash; +import org.xbib.time.chronic.tags.Tag; +import org.xbib.time.chronic.tags.TimeZone; + +import java.text.ParseException; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * + */ +public class Handler { + private static Map> definitions; + private HandlerPattern[] patterns; + private IHandler handler; + private boolean compatible; + + public Handler(IHandler handler, HandlerPattern... patterns) { + this(handler, true, patterns); + } + + public Handler(IHandler handler, boolean compatible, HandlerPattern... patterns) { + this.handler = handler; + this.compatible = compatible; + this.patterns = patterns; + } + + public static synchronized Map> definitions() { + if (definitions == null) { + Map> definitions = new HashMap<>(); + + List timeHandlers = new LinkedList<>(); + timeHandlers.add(new Handler(null, new TagPattern(RepeaterTime.class), + new TagPattern(RepeaterDayPortion.class, true))); + definitions.put(HandlerType.TIME, timeHandlers); + + List dateHandlers = new LinkedList<>(); + dateHandlers.add(new Handler(new RdnRmnSdTTzSyHandler(), + new TagPattern(RepeaterDayName.class), new TagPattern(RepeaterMonthName.class), + new TagPattern(ScalarDay.class), new TagPattern(RepeaterTime.class), + new TagPattern(TimeZone.class), new TagPattern(ScalarYear.class))); + // DIFF: We add an optional comma to MDY + dateHandlers.add(new Handler(new RmnSdSyHandler(), + new TagPattern(RepeaterMonthName.class), new TagPattern(ScalarDay.class), + new TagPattern(SeparatorComma.class, true), new TagPattern(ScalarYear.class))); + dateHandlers.add(new Handler(new RmnSdSyHandler(), + new TagPattern(RepeaterMonthName.class), new TagPattern(ScalarDay.class), + new TagPattern(ScalarYear.class), new TagPattern(SeparatorAt.class, true), + new HandlerTypePattern(HandlerType.TIME, true))); + dateHandlers.add(new Handler(new RmnSdHandler(), + new TagPattern(RepeaterMonthName.class), new TagPattern(ScalarDay.class), + new TagPattern(SeparatorAt.class, true), new HandlerTypePattern(HandlerType.TIME, true))); + dateHandlers.add(new Handler(new RmnOdHandler(), new TagPattern(RepeaterMonthName.class), + new TagPattern(OrdinalDay.class), new TagPattern(SeparatorAt.class, true), + new HandlerTypePattern(HandlerType.TIME, true))); + dateHandlers.add(new Handler(new RmnSyHandler(), new TagPattern(RepeaterMonthName.class), + new TagPattern(ScalarYear.class))); + dateHandlers.add(new Handler(new SdRmnSyHandler(), new TagPattern(ScalarDay.class), + new TagPattern(RepeaterMonthName.class), new TagPattern(ScalarYear.class), + new TagPattern(SeparatorAt.class, true), new HandlerTypePattern(HandlerType.TIME, true))); + dateHandlers.add(new Handler(new SmSdSyHandler(), new TagPattern(ScalarMonth.class), + new TagPattern(SeparatorSlashOrDash.class), new TagPattern(ScalarDay.class), + new TagPattern(SeparatorSlashOrDash.class), new TagPattern(ScalarYear.class), + new TagPattern(SeparatorAt.class, true), new HandlerTypePattern(HandlerType.TIME, true))); + dateHandlers.add(new Handler(new SdSmSyHandler(), new TagPattern(ScalarDay.class), + new TagPattern(SeparatorSlashOrDash.class), new TagPattern(ScalarMonth.class), + new TagPattern(SeparatorSlashOrDash.class), new TagPattern(ScalarYear.class), + new TagPattern(SeparatorAt.class, true), new HandlerTypePattern(HandlerType.TIME, true))); + dateHandlers.add(new Handler(new SySmSdHandler(), new TagPattern(ScalarYear.class), + new TagPattern(SeparatorSlashOrDash.class), new TagPattern(ScalarMonth.class), + new TagPattern(SeparatorSlashOrDash.class), new TagPattern(ScalarDay.class), + new TagPattern(SeparatorAt.class, true), new HandlerTypePattern(HandlerType.TIME, true))); + // DIFF: We make 05/06 interpret as month/day before month/year + dateHandlers.add(new Handler(new SmSdHandler(), false, new TagPattern(ScalarMonth.class), + new TagPattern(SeparatorSlashOrDash.class), new TagPattern(ScalarDay.class))); + dateHandlers.add(new Handler(new SmSyHandler(), new TagPattern(ScalarMonth.class), + new TagPattern(SeparatorSlashOrDash.class), new TagPattern(ScalarYear.class))); + definitions.put(HandlerType.DATE, dateHandlers); + + // tonight at 7pm + List anchorHandlers = new LinkedList<>(); + anchorHandlers.add(new Handler(new RHandler(), new TagPattern(Grabber.class, true), + new TagPattern(Repeater.class), new TagPattern(SeparatorAt.class, true), + new TagPattern(Repeater.class, true), new TagPattern(Repeater.class, true))); + anchorHandlers.add(new Handler(new RHandler(), new TagPattern(Grabber.class, true), + new TagPattern(Repeater.class), new TagPattern(Repeater.class), + new TagPattern(SeparatorAt.class, true), new TagPattern(Repeater.class, true), + new TagPattern(Repeater.class, true))); + anchorHandlers.add(new Handler(new RGRHandler(), new TagPattern(Repeater.class), + new TagPattern(Grabber.class), new TagPattern(Repeater.class))); + definitions.put(HandlerType.ANCHOR, anchorHandlers); + + // 3 weeks from now, in 2 months + List arrowHandlers = new LinkedList<>(); + arrowHandlers.add(new Handler(new SRPHandler(), new TagPattern(Scalar.class), + new TagPattern(Repeater.class), new TagPattern(Pointer.class))); + arrowHandlers.add(new Handler(new PSRHandler(), new TagPattern(Pointer.class), + new TagPattern(Scalar.class), new TagPattern(Repeater.class))); + arrowHandlers.add(new Handler(new SRPAHandler(), new TagPattern(Scalar.class), + new TagPattern(Repeater.class), new TagPattern(Pointer.class), + new HandlerTypePattern(HandlerType.ANCHOR))); + definitions.put(HandlerType.ARROW, arrowHandlers); + + // 3rd week in march + List narrowHandlers = new LinkedList<>(); + narrowHandlers.add(new Handler(new ORSRHandler(), new TagPattern(Ordinal.class), + new TagPattern(Repeater.class), new TagPattern(SeparatorIn.class), new TagPattern(Repeater.class))); + narrowHandlers.add(new Handler(new ORGRHandler(), new TagPattern(Ordinal.class), + new TagPattern(Repeater.class), new TagPattern(Grabber.class), new TagPattern(Repeater.class))); + definitions.put(HandlerType.NARROW, narrowHandlers); + Handler.definitions = definitions; + } + return definitions; + } + + public static Span tokensToSpan(List tokens, Options options) throws ParseException { + // maybe it's a specific date + Map> definitions = definitions(); + for (Handler handler : definitions.get(HandlerType.DATE)) { + if (handler.isCompatible(options) && handler.match(tokens, definitions)) { + List goodTokens = new LinkedList<>(); + for (Token token : tokens) { + if (token.getTag(Separator.class) == null) { + goodTokens.add(token); + } + } + return handler.getHandler().handle(goodTokens, options); + } + } + // I guess it's not a specific date, maybe it's just an anchor + for (Handler handler : definitions.get(HandlerType.ANCHOR)) { + if (handler.isCompatible(options) && handler.match(tokens, definitions)) { + List goodTokens = new LinkedList<>(); + for (Token token : tokens) { + if (token.getTag(Separator.class) == null) { + goodTokens.add(token); + } + } + return handler.getHandler().handle(goodTokens, options); + } + } + // not an anchor, perhaps it's an arrow + for (Handler handler : definitions.get(HandlerType.ARROW)) { + if (handler.isCompatible(options) && handler.match(tokens, definitions)) { + List goodTokens = new LinkedList<>(); + for (Token token : tokens) { + if (token.getTag(SeparatorAt.class) == null && + token.getTag(SeparatorSlashOrDash.class) == null && + token.getTag(SeparatorComma.class) == null) { + goodTokens.add(token); + } + } + return handler.getHandler().handle(goodTokens, options); + } + } + // not an arrow, let's hope it's a narrow + for (Handler handler : definitions.get(HandlerType.NARROW)) { + if (handler.isCompatible(options) && handler.match(tokens, definitions)) { + return handler.getHandler().handle(tokens, options); + } + } + throw new ParseException("no span found for " + tokens, 0); + } + + public static List> getRepeaters(List tokens) { + List> repeaters = new LinkedList<>(); + for (Token token : tokens) { + Repeater tag = token.getTag(Repeater.class); + if (tag != null) { + repeaters.add(tag); + } + } + Collections.sort(repeaters); + Collections.reverse(repeaters); + return repeaters; + } + + public static Span getAnchor(List tokens, Options options) { + Grabber grabber = new Grabber(Grabber.Relative.THIS); + PointerType pointer = PointerType.FUTURE; + + List> repeaters = getRepeaters(tokens); + for (int i = 0; i < repeaters.size(); i++) { + tokens.remove(tokens.size() - 1); + } + + if (!tokens.isEmpty() && tokens.get(0).getTag(Grabber.class) != null) { + grabber = tokens.get(0).getTag(Grabber.class); + tokens.remove(tokens.size() - 1); + } + + Repeater head = repeaters.remove(0); + head.setNow(options.getNow()); + + Span outerSpan; + Grabber.Relative grabberType = grabber.getType(); + if (grabberType == Grabber.Relative.LAST) { + outerSpan = head.nextSpan(PointerType.PAST); + } else if (grabberType == Grabber.Relative.THIS) { + if (repeaters.size() > 0) { + outerSpan = head.thisSpan(PointerType.NONE); + } else { + outerSpan = head.thisSpan(options.getContext()); + } + } else if (grabberType == Grabber.Relative.NEXT) { + outerSpan = head.nextSpan(PointerType.FUTURE); + } else { + throw new IllegalArgumentException("Invalid grabber type " + grabberType + "."); + } + return findWithin(repeaters, outerSpan, pointer); + } + + public static Span dayOrTime(ZonedDateTime dayStart, List timeTokens, Options options) { + Span outerSpan = new Span(dayStart, dayStart.plus(1, ChronoUnit.DAYS)); + if (!timeTokens.isEmpty()) { + options.setNow(outerSpan.getBeginCalendar()); + return getAnchor(dealiasAndDisambiguateTimes(timeTokens, options), options); + } + return outerSpan; + } + + /** + * Recursively finds repeaters within other repeaters. + * Returns a Span representing the innermost time span + * or nil if no repeater union could be found + * @param tags tags + * @param span span + * @param pointer pointer + * @return span + */ + public static Span findWithin(List> tags, Span span, PointerType pointer) { + if (tags.isEmpty()) { + return span; + } + Repeater head = tags.get(0); + List> rest = (tags.size() > 1) ? tags.subList(1, tags.size()) : new LinkedList<>(); + head.setNow((pointer == PointerType.FUTURE) ? span.getBeginCalendar() : span.getEndCalendar()); + Span h = head.thisSpan(PointerType.NONE); + if (span.contains(h.getBegin()) || span.contains(h.getEnd())) { + return findWithin(rest, h, pointer); + } + return null; + } + + @SuppressWarnings("unchecked") + public static List dealiasAndDisambiguateTimes(List tokens, Options options) { + // handle aliases of am/pm + // 5:00 in the morning => 5:00 am + // 7:00 in the evening => 7:00 pm + + int dayPortionIndex = -1; + int tokenSize = tokens.size(); + for (int i = 0; dayPortionIndex == -1 && i < tokenSize; i++) { + Token t = tokens.get(i); + if (t.getTag(RepeaterDayPortion.class) != null) { + dayPortionIndex = i; + } + } + + int timeIndex = -1; + for (int i = 0; timeIndex == -1 && i < tokenSize; i++) { + Token t = tokens.get(i); + if (t.getTag(RepeaterTime.class) != null) { + timeIndex = i; + } + } + + if (dayPortionIndex != -1 && timeIndex != -1) { + Token t1 = tokens.get(dayPortionIndex); + Tag> t1Tag = t1.getTag(RepeaterDayPortion.class); + + Object t1TagType = t1Tag.getType(); + if (RepeaterDayPortion.DayPortion.MORNING.equals(t1TagType)) { + t1.untag(RepeaterDayPortion.class); + t1.tag(new EnumRepeaterDayPortion(RepeaterDayPortion.DayPortion.AM)); + } else if (RepeaterDayPortion.DayPortion.AFTERNOON.equals(t1TagType) || + RepeaterDayPortion.DayPortion.EVENING.equals(t1TagType) || + RepeaterDayPortion.DayPortion.NIGHT.equals(t1TagType)) { + t1.untag(RepeaterDayPortion.class); + t1.tag(new EnumRepeaterDayPortion(RepeaterDayPortion.DayPortion.PM)); + } + } + + // handle ambiguous times if :ambiguous_time_range is specified + if (options.getAmbiguousTimeRange() != 0) { + List ttokens = new LinkedList(); + for (int i = 0; i < tokenSize; i++) { + Token t0 = tokens.get(i); + ttokens.add(t0); + Token t1 = null; + if (i < tokenSize - 1) { + t1 = tokens.get(i + 1); + } + if (t0.getTag(RepeaterTime.class) != null && + t0.getTag(RepeaterTime.class).getType().isAmbiguous() && + (t1 == null || t1.getTag(RepeaterDayPortion.class) == null)) { + Token distoken = new Token("disambiguator"); + distoken.tag(new IntegerRepeaterDayPortion(options.getAmbiguousTimeRange())); + ttokens.add(distoken); + } + } + tokens = ttokens; + } + return tokens; + } + + public boolean isCompatible(Options options) { + return !options.isCompatibilityMode() || compatible; + } + + public IHandler getHandler() { + return handler; + } + + public boolean match(List tokens, Map> definitions) { + int tokenIndex = 0; + for (HandlerPattern pattern : patterns) { + boolean optional = pattern.isOptional(); + if (pattern instanceof TagPattern) { + boolean match = (tokenIndex < tokens.size() && + tokens.get(tokenIndex).getTags(((TagPattern) pattern).getTagClass()).size() > 0); + if (!match && !optional) { + return false; + } + if (match) { + tokenIndex++; + } + // next if !match && optional ? + } else if (pattern instanceof HandlerTypePattern) { + if (optional && tokenIndex == tokens.size()) { + return true; + } + List subHandlers = definitions.get(((HandlerTypePattern) pattern).getType()); + for (Handler subHandler : subHandlers) { + if (subHandler.match(tokens.subList(tokenIndex, tokens.size()), definitions)) { + return true; + } + } + return false; + } + } + return tokenIndex == tokens.size(); + } + + @Override + public String toString() { + return "[Handler: " + handler + "]"; + } + + /** + * + */ + public enum HandlerType { + TIME, DATE, ANCHOR, ARROW, NARROW + } +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/HandlerPattern.java b/src/main/java/org/xbib/time/chronic/handlers/HandlerPattern.java new file mode 100644 index 0000000..1aa1584 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/HandlerPattern.java @@ -0,0 +1,16 @@ +package org.xbib.time.chronic.handlers; + +/** + * + */ +public class HandlerPattern { + private boolean optional; + + public HandlerPattern(boolean optional) { + this.optional = optional; + } + + public boolean isOptional() { + return optional; + } +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/HandlerTypePattern.java b/src/main/java/org/xbib/time/chronic/handlers/HandlerTypePattern.java new file mode 100644 index 0000000..0778db6 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/HandlerTypePattern.java @@ -0,0 +1,21 @@ +package org.xbib.time.chronic.handlers; + +/** + * + */ +public class HandlerTypePattern extends HandlerPattern { + private Handler.HandlerType type; + + public HandlerTypePattern(Handler.HandlerType type) { + this(type, false); + } + + public HandlerTypePattern(Handler.HandlerType type, boolean optional) { + super(optional); + this.type = type; + } + + public Handler.HandlerType getType() { + return type; + } +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/IHandler.java b/src/main/java/org/xbib/time/chronic/handlers/IHandler.java new file mode 100644 index 0000000..771f467 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/IHandler.java @@ -0,0 +1,14 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; + +import java.util.List; + +/** + * + */ +public interface IHandler { + Span handle(List tokens, Options options); +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/MDHandler.java b/src/main/java/org/xbib/time/chronic/handlers/MDHandler.java new file mode 100644 index 0000000..927e9ec --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/MDHandler.java @@ -0,0 +1,25 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.repeaters.Repeater; +import org.xbib.time.chronic.tags.Tag; + +import java.time.ZonedDateTime; +import java.util.List; + +/** + * + */ +public abstract class MDHandler implements IHandler { + + public Span handle(Repeater monthRep, Tag day, List timeTokens, Options options) { + monthRep.setNow(options.getNow()); + Span span = monthRep.thisSpan(options.getContext()); + int year = span.getBeginCalendar().getYear(); + int month = span.getBeginCalendar().getMonthValue(); + ZonedDateTime dayStart = ZonedDateTime.of(year, month, day.getType(), 0, 0, 0, 0, options.getZoneId()); + return Handler.dayOrTime(dayStart, timeTokens, options); + } +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/ORGRHandler.java b/src/main/java/org/xbib/time/chronic/handlers/ORGRHandler.java new file mode 100644 index 0000000..f103ff4 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/ORGRHandler.java @@ -0,0 +1,19 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; + +import java.util.List; + +/** + * + */ +public class ORGRHandler extends ORRHandler { + + public Span handle(List tokens, Options options) { + Span outerSpan = Handler.getAnchor(tokens.subList(2, 4), options); + return handle(tokens.subList(0, 2), outerSpan, options); + } + +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/ORRHandler.java b/src/main/java/org/xbib/time/chronic/handlers/ORRHandler.java new file mode 100644 index 0000000..cd59cb9 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/ORRHandler.java @@ -0,0 +1,32 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.repeaters.Repeater; +import org.xbib.time.chronic.tags.Ordinal; +import org.xbib.time.chronic.tags.Pointer; + +import java.time.temporal.ChronoUnit; +import java.util.List; + +/** + * + */ +public abstract class ORRHandler implements IHandler { + + public Span handle(List tokens, Span outerSpan, Options options) { + Repeater repeater = tokens.get(1).getTag(Repeater.class); + repeater.setNow(outerSpan.getBeginCalendar().minus(1, ChronoUnit.SECONDS)); + Integer ordinalValue = tokens.get(0).getTag(Ordinal.class).getType(); + Span span = null; + for (int i = 0; i < ordinalValue; i++) { + span = repeater.nextSpan(Pointer.PointerType.FUTURE); + if (span.getBegin() > outerSpan.getEnd()) { + span = null; + break; + } + } + return span; + } +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/ORSRHandler.java b/src/main/java/org/xbib/time/chronic/handlers/ORSRHandler.java new file mode 100644 index 0000000..0cba6e3 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/ORSRHandler.java @@ -0,0 +1,19 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; + +import java.util.List; + +/** + * + */ +public class ORSRHandler extends ORRHandler { + + public Span handle(List tokens, Options options) { + Span outerSpan = Handler.getAnchor(tokens.subList(3, 4), options); + return handle(tokens.subList(0, 2), outerSpan, options); + } + +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/PSRHandler.java b/src/main/java/org/xbib/time/chronic/handlers/PSRHandler.java new file mode 100644 index 0000000..5a6e0d0 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/PSRHandler.java @@ -0,0 +1,23 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; + +import java.util.LinkedList; +import java.util.List; + +/** + * + */ +public class PSRHandler extends SRPHandler { + + @Override + public Span handle(List tokens, Options options) { + List newTokens = new LinkedList<>(); + newTokens.add(tokens.get(1)); + newTokens.add(tokens.get(2)); + newTokens.add(tokens.get(0)); + return super.handle(newTokens, options); + } +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/RGRHandler.java b/src/main/java/org/xbib/time/chronic/handlers/RGRHandler.java new file mode 100644 index 0000000..98fad41 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/RGRHandler.java @@ -0,0 +1,23 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; + +import java.util.LinkedList; +import java.util.List; + +/** + * + */ +public class RGRHandler extends RHandler { + + @Override + public Span handle(List tokens, Options options) { + List newTokens = new LinkedList<>(); + newTokens.add(tokens.get(1)); + newTokens.add(tokens.get(0)); + newTokens.add(tokens.get(2)); + return super.handle(newTokens, options); + } +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/RHandler.java b/src/main/java/org/xbib/time/chronic/handlers/RHandler.java new file mode 100644 index 0000000..8c756bd --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/RHandler.java @@ -0,0 +1,19 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; + +import java.util.List; + +/** + * + */ +public class RHandler implements IHandler { + + public Span handle(List tokens, Options options) { + List ddTokens = Handler.dealiasAndDisambiguateTimes(tokens, options); + return Handler.getAnchor(ddTokens, options); + } + +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/RdnRmnSdTTzSyHandler.java b/src/main/java/org/xbib/time/chronic/handlers/RdnRmnSdTTzSyHandler.java new file mode 100644 index 0000000..f78f282 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/RdnRmnSdTTzSyHandler.java @@ -0,0 +1,32 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.repeaters.RepeaterMonthName; +import org.xbib.time.chronic.tags.ScalarDay; +import org.xbib.time.chronic.tags.ScalarYear; + +import java.time.ZonedDateTime; +import java.util.List; + +/** + * + */ +public class RdnRmnSdTTzSyHandler implements IHandler { + + public Span handle(List tokens, Options options) { + int month = tokens.get(1).getTag(RepeaterMonthName.class).getType().ordinal(); + int day = tokens.get(2).getTag(ScalarDay.class).getType(); + int year = tokens.get(5).getTag(ScalarYear.class).getType(); + Span span; + try { + List timeTokens = tokens.subList(3, 4); + ZonedDateTime dayStart = ZonedDateTime.of(year, month, day, 0, 0, 0, 0, options.getZoneId()); + span = Handler.dayOrTime(dayStart, timeTokens, options); + } catch (IllegalArgumentException e) { + span = null; + } + return span; + } +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/RmnOdHandler.java b/src/main/java/org/xbib/time/chronic/handlers/RmnOdHandler.java new file mode 100644 index 0000000..2640d70 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/RmnOdHandler.java @@ -0,0 +1,19 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.repeaters.RepeaterMonthName; +import org.xbib.time.chronic.tags.OrdinalDay; + +import java.util.List; + +/** + * + */ +public class RmnOdHandler extends MDHandler { + public Span handle(List tokens, Options options) { + return handle(tokens.get(0).getTag(RepeaterMonthName.class), + tokens.get(1).getTag(OrdinalDay.class), tokens.subList(2, tokens.size()), options); + } +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/RmnSdHandler.java b/src/main/java/org/xbib/time/chronic/handlers/RmnSdHandler.java new file mode 100644 index 0000000..b1ce4b6 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/RmnSdHandler.java @@ -0,0 +1,19 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.repeaters.RepeaterMonthName; +import org.xbib.time.chronic.tags.ScalarDay; + +import java.util.List; + +/** + * + */ +public class RmnSdHandler extends MDHandler { + public Span handle(List tokens, Options options) { + return handle(tokens.get(0).getTag(RepeaterMonthName.class), + tokens.get(1).getTag(ScalarDay.class), tokens.subList(2, tokens.size()), options); + } +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/RmnSdSyHandler.java b/src/main/java/org/xbib/time/chronic/handlers/RmnSdSyHandler.java new file mode 100644 index 0000000..2e4a36f --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/RmnSdSyHandler.java @@ -0,0 +1,33 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.repeaters.RepeaterMonthName; +import org.xbib.time.chronic.tags.ScalarDay; +import org.xbib.time.chronic.tags.ScalarYear; + +import java.time.ZonedDateTime; +import java.util.List; + +/** + * + */ +public class RmnSdSyHandler implements IHandler { + + public Span handle(List tokens, Options options) { + int month = tokens.get(0).getTag(RepeaterMonthName.class).getType().ordinal(); + int day = tokens.get(1).getTag(ScalarDay.class).getType(); + int year = tokens.get(2).getTag(ScalarYear.class).getType(); + Span span; + try { + List timeTokens = tokens.subList(3, tokens.size()); + ZonedDateTime dayStart = ZonedDateTime.of(year, month, day, 0, 0, 0, 0, options.getZoneId()); + span = Handler.dayOrTime(dayStart, timeTokens, options); + } catch (IllegalArgumentException e) { + span = null; + } + return span; + } + +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/RmnSyHandler.java b/src/main/java/org/xbib/time/chronic/handlers/RmnSyHandler.java new file mode 100644 index 0000000..ba28063 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/RmnSyHandler.java @@ -0,0 +1,32 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.repeaters.RepeaterMonthName; +import org.xbib.time.chronic.tags.ScalarYear; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; + +/** + * + */ +public class RmnSyHandler implements IHandler { + + public Span handle(List tokens, Options options) { + int month = tokens.get(0).getTag(RepeaterMonthName.class).getType().ordinal(); + int year = tokens.get(1).getTag(ScalarYear.class).getType(); + Span span; + try { + ZonedDateTime start = ZonedDateTime.of(year, month, 1, 0, 0, 0, 0, options.getZoneId()); + ZonedDateTime end = start.plus(1, ChronoUnit.MONTHS); + span = new Span(start, end); + } catch (IllegalArgumentException e) { + span = null; + } + return span; + } + +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/SRPAHandler.java b/src/main/java/org/xbib/time/chronic/handlers/SRPAHandler.java new file mode 100644 index 0000000..b373a77 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/SRPAHandler.java @@ -0,0 +1,20 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; + +import java.util.List; + +/** + * + */ +public class SRPAHandler extends SRPHandler { + + @Override + public Span handle(List tokens, Options options) { + Span anchorSpan = Handler.getAnchor(tokens.subList(3, tokens.size()), options); + return super.handle(tokens, anchorSpan, options); + } + +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/SRPHandler.java b/src/main/java/org/xbib/time/chronic/handlers/SRPHandler.java new file mode 100644 index 0000000..5eca74d --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/SRPHandler.java @@ -0,0 +1,35 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Chronic; +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.repeaters.Repeater; +import org.xbib.time.chronic.tags.Pointer; +import org.xbib.time.chronic.tags.Scalar; + +import java.text.ParseException; +import java.util.List; + +/** + * + */ +public class SRPHandler implements IHandler { + + public Span handle(List tokens, Span span, Options options) { + int distance = tokens.get(0).getTag(Scalar.class).getType(); + Repeater repeater = tokens.get(1).getTag(Repeater.class); + Pointer.PointerType pointer = tokens.get(2).getTag(Pointer.class).getType(); + return repeater.getOffset(span, distance, pointer); + } + + @Override + public Span handle(List tokens, Options options) { + try { + Span span = Chronic.parse("this second", new Options().setNow(options.getNow()).setGuess(false)); + return handle(tokens, span, options); + } catch (ParseException e) { + return null; + } + } +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/SdRmnSyHandler.java b/src/main/java/org/xbib/time/chronic/handlers/SdRmnSyHandler.java new file mode 100644 index 0000000..e7c36ed --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/SdRmnSyHandler.java @@ -0,0 +1,25 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; + +import java.util.LinkedList; +import java.util.List; + +/** + * + */ +public class SdRmnSyHandler extends RmnSdSyHandler { + + @Override + public Span handle(List tokens, Options options) { + List newTokens = new LinkedList<>(); + newTokens.add(tokens.get(1)); + newTokens.add(tokens.get(0)); + newTokens.add(tokens.get(2)); + newTokens.addAll(tokens.subList(3, tokens.size())); + return super.handle(newTokens, options); + } + +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/SdSmSyHandler.java b/src/main/java/org/xbib/time/chronic/handlers/SdSmSyHandler.java new file mode 100644 index 0000000..ba180e6 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/SdSmSyHandler.java @@ -0,0 +1,24 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; + +import java.util.LinkedList; +import java.util.List; + +/** + * + */ +public class SdSmSyHandler extends SmSdSyHandler { + + @Override + public Span handle(List tokens, Options options) { + List newTokens = new LinkedList<>(); + newTokens.add(tokens.get(1)); + newTokens.add(tokens.get(0)); + newTokens.add(tokens.get(2)); + newTokens.addAll(tokens.subList(3, tokens.size())); + return super.handle(newTokens, options); + } +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/SmSdHandler.java b/src/main/java/org/xbib/time/chronic/handlers/SmSdHandler.java new file mode 100644 index 0000000..7b63f70 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/SmSdHandler.java @@ -0,0 +1,25 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.tags.ScalarDay; +import org.xbib.time.chronic.tags.ScalarMonth; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; + +/** + * + */ +public class SmSdHandler implements IHandler { + public Span handle(List tokens, Options options) { + int month = tokens.get(0).getTag(ScalarMonth.class).getType(); + int day = tokens.get(1).getTag(ScalarDay.class).getType(); + ZonedDateTime start = ZonedDateTime.of(options.getNow().getYear(), month, day, 0, 0, 0, 0, options.getZoneId()); + ZonedDateTime end = start.plus(1, ChronoUnit.MONTHS); + return new Span(start, end); + } + +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/SmSdSyHandler.java b/src/main/java/org/xbib/time/chronic/handlers/SmSdSyHandler.java new file mode 100644 index 0000000..de6c6ed --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/SmSdSyHandler.java @@ -0,0 +1,33 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.tags.ScalarDay; +import org.xbib.time.chronic.tags.ScalarMonth; +import org.xbib.time.chronic.tags.ScalarYear; + +import java.time.ZonedDateTime; +import java.util.List; + +/** + * + */ +public class SmSdSyHandler implements IHandler { + + public Span handle(List tokens, Options options) { + int month = tokens.get(0).getTag(ScalarMonth.class).getType(); + int day = tokens.get(1).getTag(ScalarDay.class).getType(); + int year = tokens.get(2).getTag(ScalarYear.class).getType(); + Span span; + try { + List timeTokens = tokens.subList(3, tokens.size()); + ZonedDateTime dayStart = ZonedDateTime.of(year, month, day, 0, 0, 0, 0, options.getZoneId()); + span = Handler.dayOrTime(dayStart, timeTokens, options); + } catch (IllegalArgumentException e) { + span = null; + } + return span; + } + +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/SmSyHandler.java b/src/main/java/org/xbib/time/chronic/handlers/SmSyHandler.java new file mode 100644 index 0000000..4f49309 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/SmSyHandler.java @@ -0,0 +1,32 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.tags.ScalarMonth; +import org.xbib.time.chronic.tags.ScalarYear; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; + +/** + * + */ +public class SmSyHandler implements IHandler { + + public Span handle(List tokens, Options options) { + int month = tokens.get(0).getTag(ScalarMonth.class).getType(); + int year = tokens.get(1).getTag(ScalarYear.class).getType(); + Span span; + try { + ZonedDateTime start = ZonedDateTime.of(year, month, 1, 0, 0, 0, 0, options.getZoneId()); + ZonedDateTime end = start.plus(1, ChronoUnit.MONTHS); + span = new Span(start, end); + } catch (IllegalArgumentException e) { + span = null; + } + return span; + } + +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/SySmSdHandler.java b/src/main/java/org/xbib/time/chronic/handlers/SySmSdHandler.java new file mode 100644 index 0000000..76f8d62 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/SySmSdHandler.java @@ -0,0 +1,24 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; + +import java.util.LinkedList; +import java.util.List; + +/** + * + */ +public class SySmSdHandler extends SmSdSyHandler { + + @Override + public Span handle(List tokens, Options options) { + List newTokens = new LinkedList<>(); + newTokens.add(tokens.get(1)); + newTokens.add(tokens.get(2)); + newTokens.add(tokens.get(0)); + newTokens.addAll(tokens.subList(3, tokens.size())); + return super.handle(newTokens, options); + } +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/TagPattern.java b/src/main/java/org/xbib/time/chronic/handlers/TagPattern.java new file mode 100644 index 0000000..fc06b5f --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/TagPattern.java @@ -0,0 +1,29 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.tags.Tag; + +/** + * + */ +@SuppressWarnings("rawtypes") +public class TagPattern extends HandlerPattern { + private Class tagClass; + + public TagPattern(Class tagClass) { + this(tagClass, false); + } + + public TagPattern(Class tagClass, boolean optional) { + super(optional); + this.tagClass = tagClass; + } + + public Class getTagClass() { + return tagClass; + } + + @Override + public String toString() { + return "[TagPattern: tagClass = " + tagClass + "]"; + } +} diff --git a/src/main/java/org/xbib/time/chronic/handlers/package-info.java b/src/main/java/org/xbib/time/chronic/handlers/package-info.java new file mode 100644 index 0000000..bfeab65 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/handlers/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for chronic handlers. + */ +package org.xbib.time.chronic.handlers; diff --git a/src/main/java/org/xbib/time/chronic/numerizer/Numerizer.java b/src/main/java/org/xbib/time/chronic/numerizer/Numerizer.java new file mode 100644 index 0000000..cde8e13 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/numerizer/Numerizer.java @@ -0,0 +1,194 @@ +package org.xbib.time.chronic.numerizer; + +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * + */ +public class Numerizer { + + private static final Pattern DEHYPHENATOR = Pattern.compile(" +|(\\D)-(\\D)"); + + private static final Pattern DEHALFER = Pattern.compile("a half", Pattern.CASE_INSENSITIVE); + + private static final Pattern DEHAALFER = Pattern.compile("(\\d+)(?: | and |-)*haAlf", Pattern.CASE_INSENSITIVE); + + private static final Pattern ANDITION_PATTERN = Pattern.compile("(\\d+)( | and )(\\d+)(?=\\W|$)"); + + private static final DirectNum[] DIRECT_NUMS; + + private static final TenPrefix[] TEN_PREFIXES; + + private static final BigPrefix[] BIG_PREFIXES; + + static { + List directNums = new LinkedList<>(); + directNums.add(new DirectNum("eleven", "11")); + directNums.add(new DirectNum("twelve", "12")); + directNums.add(new DirectNum("thirteen", "13")); + directNums.add(new DirectNum("fourteen", "14")); + directNums.add(new DirectNum("fifteen", "15")); + directNums.add(new DirectNum("sixteen", "16")); + directNums.add(new DirectNum("seventeen", "17")); + directNums.add(new DirectNum("eighteen", "18")); + directNums.add(new DirectNum("nineteen", "19")); + directNums.add(new DirectNum("ninteen", "19")); + directNums.add(new DirectNum("zero", "0")); + directNums.add(new DirectNum("one", "1")); + directNums.add(new DirectNum("two", "2")); + directNums.add(new DirectNum("three", "3")); + directNums.add(new DirectNum("four(\\W|$)", "4$1")); + directNums.add(new DirectNum("five", "5")); + directNums.add(new DirectNum("six(\\W|$)", "6$1")); + directNums.add(new DirectNum("seven(\\W|$)", "7$1")); + directNums.add(new DirectNum("eight(\\W|$)", "8$1")); + directNums.add(new DirectNum("nine(\\W|$)", "9$1")); + directNums.add(new DirectNum("ten", "10")); + directNums.add(new DirectNum("\\ba\\b", "1")); + DIRECT_NUMS = directNums.toArray(new DirectNum[directNums.size()]); + + List tenPrefixes = new LinkedList<>(); + tenPrefixes.add(new TenPrefix("twenty", 20)); + tenPrefixes.add(new TenPrefix("thirty", 30)); + tenPrefixes.add(new TenPrefix("fourty", 40)); + tenPrefixes.add(new TenPrefix("fifty", 50)); + tenPrefixes.add(new TenPrefix("sixty", 60)); + tenPrefixes.add(new TenPrefix("seventy", 70)); + tenPrefixes.add(new TenPrefix("eighty", 80)); + tenPrefixes.add(new TenPrefix("ninety", 90)); + tenPrefixes.add(new TenPrefix("ninty", 90)); + TEN_PREFIXES = tenPrefixes.toArray(new TenPrefix[tenPrefixes.size()]); + + List bigPrefixes = new LinkedList<>(); + bigPrefixes.add(new BigPrefix("hundred", 100L)); + bigPrefixes.add(new BigPrefix("thousand", 1000L)); + bigPrefixes.add(new BigPrefix("million", 1000000L)); + bigPrefixes.add(new BigPrefix("billion", 1000000000L)); + bigPrefixes.add(new BigPrefix("trillion", 1000000000000L)); + BIG_PREFIXES = bigPrefixes.toArray(new BigPrefix[bigPrefixes.size()]); + } + + public static String numerize(String str) { + String numerizedStr = str; + numerizedStr = Numerizer.DEHYPHENATOR.matcher(numerizedStr).replaceAll("$1 $2"); + numerizedStr = Numerizer.DEHALFER.matcher(numerizedStr).replaceAll("haAlf"); + for (DirectNum dn : Numerizer.DIRECT_NUMS) { + numerizedStr = dn.getName().matcher(numerizedStr).replaceAll(dn.getNumber()); + } + for (Prefix tp : Numerizer.TEN_PREFIXES) { + Matcher matcher = tp.getName().matcher(numerizedStr); + if (matcher.find()) { + StringBuffer matcherBuffer = new StringBuffer(); + do { + if (matcher.group(1) == null) { + matcher.appendReplacement(matcherBuffer, String.valueOf(tp.getNumber())); + } else { + matcher.appendReplacement(matcherBuffer, String.valueOf(tp.getNumber() + + Long.parseLong(matcher.group(1).trim()))); + } + } while (matcher.find()); + matcher.appendTail(matcherBuffer); + numerizedStr = matcherBuffer.toString(); + } + } + for (Prefix bp : Numerizer.BIG_PREFIXES) { + Matcher matcher = bp.getName().matcher(numerizedStr); + if (matcher.find()) { + StringBuffer matcherBuffer = new StringBuffer(); + do { + if (matcher.group(1) == null) { + matcher.appendReplacement(matcherBuffer, String.valueOf(bp.getNumber())); + } else { + matcher.appendReplacement(matcherBuffer, String.valueOf(bp.getNumber() * + Long.parseLong(matcher.group(1).trim()))); + } + } while (matcher.find()); + matcher.appendTail(matcherBuffer); + numerizedStr = matcherBuffer.toString(); + numerizedStr = Numerizer.andition(numerizedStr); + } + } + Matcher matcher = Numerizer.DEHAALFER.matcher(numerizedStr); + if (matcher.find()) { + StringBuffer matcherBuffer = new StringBuffer(); + do { + matcher.appendReplacement(matcherBuffer, + String.valueOf(Float.parseFloat(matcher.group(1).trim()) + 0.5f)); + } while (matcher.find()); + matcher.appendTail(matcherBuffer); + numerizedStr = matcherBuffer.toString(); + } + return numerizedStr; + } + + private static String andition(String str) { + StringBuffer anditionStr = new StringBuffer(str); + Matcher matcher = Numerizer.ANDITION_PATTERN.matcher(anditionStr); + while (matcher.find()) { + if (matcher.group(2).equalsIgnoreCase(" and ") || matcher.group(1).length() > matcher.group(3).length()) { + anditionStr.replace(matcher.start(), matcher.end(), + String.valueOf(Integer.parseInt(matcher.group(1).trim()) + + Integer.parseInt(matcher.group(3).trim()))); + matcher = Numerizer.ANDITION_PATTERN.matcher(anditionStr); + } + } + return anditionStr.toString(); + } + + /** + * + */ + static class DirectNum { + private Pattern name; + private String number; + + DirectNum(String name, String number) { + this.name = Pattern.compile(name, Pattern.CASE_INSENSITIVE); + this.number = number; + } + + public Pattern getName() { + return name; + } + + public String getNumber() { + return number; + } + } + + /** + * + */ + static class Prefix { + private Pattern name; + private long number; + + Prefix(Pattern name, long number) { + this.name = name; + this.number = number; + } + + public Pattern getName() { + return name; + } + + public long getNumber() { + return number; + } + } + + private static class TenPrefix extends Prefix { + TenPrefix(String name, long number) { + super(Pattern.compile("(?:" + name + ")( *\\d(?=\\D|$))*", Pattern.CASE_INSENSITIVE), number); + } + } + + private static class BigPrefix extends Prefix { + BigPrefix(String name, long number) { + super(Pattern.compile("(\\d*) *" + name, Pattern.CASE_INSENSITIVE), number); + } + } +} diff --git a/src/main/java/org/xbib/time/chronic/numerizer/package-info.java b/src/main/java/org/xbib/time/chronic/numerizer/package-info.java new file mode 100644 index 0000000..52caab1 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/numerizer/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for chronic numerizers. + */ +package org.xbib.time.chronic.numerizer; diff --git a/src/main/java/org/xbib/time/chronic/package-info.java b/src/main/java/org/xbib/time/chronic/package-info.java new file mode 100644 index 0000000..4c6c4cc --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for time chronic. + */ +package org.xbib.time.chronic; diff --git a/src/main/java/org/xbib/time/chronic/repeaters/EnumRepeaterDayPortion.java b/src/main/java/org/xbib/time/chronic/repeaters/EnumRepeaterDayPortion.java new file mode 100644 index 0000000..212a12e --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/EnumRepeaterDayPortion.java @@ -0,0 +1,45 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Range; + +/** + * + */ +public class EnumRepeaterDayPortion extends RepeaterDayPortion { + private static final Range AM_RANGE = new Range(0, 12 * 60 * 60); // 12am-12pm + private static final Range PM_RANGE = new Range(12 * 60 * 60, 24 * 60 * 60 - 1); // 12pm-12am + private static final Range MORNING_RANGE = new Range(6 * 60 * 60, 12 * 60 * 60); // 6am-12pm + private static final Range AFTERNOON_RANGE = new Range(13 * 60 * 60, 17 * 60 * 60); // 1pm-5pm + private static final Range EVENING_RANGE = new Range(17 * 60 * 60, 20 * 60 * 60); // 5pm-8pm + private static final Range NIGHT_RANGE = new Range(20 * 60 * 60, 24 * 60 * 60); // 8pm-12pm + + public EnumRepeaterDayPortion(DayPortion type) { + super(type); + } + + @Override + protected Range createRange(DayPortion type) { + Range range; + if (type == DayPortion.AM) { + range = EnumRepeaterDayPortion.AM_RANGE; + } else if (type == DayPortion.PM) { + range = EnumRepeaterDayPortion.PM_RANGE; + } else if (type == DayPortion.MORNING) { + range = EnumRepeaterDayPortion.MORNING_RANGE; + } else if (type == DayPortion.AFTERNOON) { + range = EnumRepeaterDayPortion.AFTERNOON_RANGE; + } else if (type == DayPortion.EVENING) { + range = EnumRepeaterDayPortion.EVENING_RANGE; + } else if (type == DayPortion.NIGHT) { + range = EnumRepeaterDayPortion.NIGHT_RANGE; + } else { + throw new IllegalArgumentException("Unknown day portion type " + type); + } + return range; + } + + @Override + protected long getWidth(Range range) { + return range.getWidth(); + } +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/IntegerRepeaterDayPortion.java b/src/main/java/org/xbib/time/chronic/repeaters/IntegerRepeaterDayPortion.java new file mode 100644 index 0000000..21e1811 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/IntegerRepeaterDayPortion.java @@ -0,0 +1,22 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Range; + +/** + * + */ +public class IntegerRepeaterDayPortion extends RepeaterDayPortion { + public IntegerRepeaterDayPortion(Integer type) { + super(type); + } + + @Override + protected Range createRange(Integer type) { + return new Range(type * 60L * 60L, (type + 12) * 60L * 60L); + } + + @Override + protected long getWidth(Range range) { + return 12 * 60L * 60L; + } +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/Repeater.java b/src/main/java/org/xbib/time/chronic/repeaters/Repeater.java new file mode 100644 index 0000000..ca17993 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/Repeater.java @@ -0,0 +1,91 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.tags.Pointer; +import org.xbib.time.chronic.tags.Tag; + +import java.util.List; + +/** + * Repeater. + * @param type parameter + */ +public abstract class Repeater extends Tag implements Comparable> { + + public Repeater(T type) { + super(type); + } + + public static List scan(List tokens) { + return Repeater.scan(tokens, new Options()); + } + + public static List scan(List tokens, Options options) { + for (Token token : tokens) { + Tag t; + t = RepeaterMonthName.scan(token); + if (t != null) { + token.tag(t); + } + t = RepeaterDayName.scan(token); + if (t != null) { + token.tag(t); + } + t = RepeaterDayPortion.scan(token); + if (t != null) { + token.tag(t); + } + t = RepeaterTime.scan(token, tokens, options); + if (t != null) { + token.tag(t); + } + t = RepeaterUnit.scan(token); + if (t != null) { + token.tag(t); + } + } + return tokens; + } + + public int compareTo(Repeater other) { + return Integer.compare(getWidth(), other.getWidth()); + } + + /** + * Returns the width (in seconds or months) of this repeatable. + * @return width + */ + public abstract int getWidth(); + + /** + * Returns the next occurance of this repeatable. + * @param pointer pointer + * @return span + */ + public Span nextSpan(Pointer.PointerType pointer) { + if (getNow() == null) { + throw new IllegalStateException("Start point must be set before calling #next"); + } + return internalNextSpan(pointer); + } + + protected abstract Span internalNextSpan(Pointer.PointerType pointer); + + public Span thisSpan(Pointer.PointerType pointer) { + if (getNow() == null) { + throw new IllegalStateException("Start point must be set before calling #this"); + } + return internalThisSpan(pointer); + } + + protected abstract Span internalThisSpan(Pointer.PointerType pointer); + + public abstract Span getOffset(Span span, int amount, Pointer.PointerType pointer); + + @Override + public String toString() { + return "repeater"; + } +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/RepeaterDay.java b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterDay.java new file mode 100644 index 0000000..5e109c2 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterDay.java @@ -0,0 +1,72 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.tags.Pointer.PointerType; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +/** + * + */ +public class RepeaterDay extends RepeaterUnit { + public static final int DAY_SECONDS = 86400; + + private ZonedDateTime currentDayStart; + + private static ZonedDateTime ymd(ZonedDateTime zonedDateTime) { + return ZonedDateTime.of(zonedDateTime.getYear(), zonedDateTime.getMonthValue(), zonedDateTime.getDayOfMonth(), + 0, 0, 0, 0, zonedDateTime.getZone()); + } + + private static ZonedDateTime ymdh(ZonedDateTime zonedDateTime) { + return ZonedDateTime.of(zonedDateTime.getYear(), zonedDateTime.getMonthValue(), zonedDateTime.getDayOfMonth(), + zonedDateTime.getHour(), 0, 0, 0, zonedDateTime.getZone()); + } + + @Override + protected Span internalNextSpan(PointerType pointer) { + if (currentDayStart == null) { + currentDayStart = ymd(getNow()); + } + int direction = pointer == PointerType.FUTURE ? 1 : -1; + currentDayStart = currentDayStart.plus(direction, ChronoUnit.DAYS); + return new Span(currentDayStart, ChronoUnit.DAYS, 1); + } + + @Override + protected Span internalThisSpan(PointerType pointer) { + ZonedDateTime dayBegin; + ZonedDateTime dayEnd; + if (pointer == PointerType.FUTURE) { + dayBegin = ymdh(getNow()).plus(1, ChronoUnit.HOURS); + dayEnd = ymd(getNow()).plus(1, ChronoUnit.DAYS); + } else if (pointer == PointerType.PAST) { + dayBegin = ymd(getNow()); + dayEnd = ymdh(getNow()); + } else if (pointer == PointerType.NONE) { + dayBegin = ymd(getNow()); + dayEnd = ymdh(getNow()).plus(1, ChronoUnit.DAYS); + } else { + throw new IllegalArgumentException("unable to handle pointer " + pointer + "."); + } + return new Span(dayBegin, dayEnd); + } + + @Override + public Span getOffset(Span span, int amount, PointerType pointer) { + long direction = pointer == PointerType.FUTURE ? 1L : -1L; + return span.add(direction * amount * RepeaterDay.DAY_SECONDS); + } + + @Override + public int getWidth() { + return RepeaterDay.DAY_SECONDS; + } + + @Override + public String toString() { + return super.toString() + "-day"; + } + +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/RepeaterDayName.java b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterDayName.java new file mode 100644 index 0000000..df7e78f --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterDayName.java @@ -0,0 +1,106 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.tags.Pointer.PointerType; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * + */ +public class RepeaterDayName extends Repeater { + public static final int DAY_SECONDS = 86400; // (24 * 60 * 60); + private static final Pattern MON_PATTERN = Pattern.compile("^m[ou]n(day)?$"); + private static final Pattern TUE_PATTERN = Pattern.compile("^t(ue|eu|oo|u|)s(day)?$"); + private static final Pattern TUE_PATTERN_1 = Pattern.compile("^tue$"); + private static final Pattern WED_PATTERN_1 = Pattern.compile("^we(dnes|nds|nns)day$"); + private static final Pattern WED_PATTERN_2 = Pattern.compile("^wed$"); + private static final Pattern THU_PATTERN_1 = Pattern.compile("^th(urs|ers)day$"); + private static final Pattern THU_PATTERN_2 = Pattern.compile("^thu$"); + private static final Pattern FRI_PATTERN = Pattern.compile("^fr[iy](day)?$"); + private static final Pattern SAT_PATTERN = Pattern.compile("^sat(t?[ue]rday)?$"); + private static final Pattern SUN_PATTERN = Pattern.compile("^su[nm](day)?$"); + private ZonedDateTime currentDayStart; + + public RepeaterDayName(DayName type) { + super(type); + } + + public static RepeaterDayName scan(Token token) { + Map scanner = new HashMap<>(); + scanner.put(RepeaterDayName.MON_PATTERN, DayName.MONDAY); + scanner.put(RepeaterDayName.TUE_PATTERN, DayName.TUESDAY); + scanner.put(RepeaterDayName.TUE_PATTERN_1, DayName.TUESDAY); + scanner.put(RepeaterDayName.WED_PATTERN_1, DayName.WEDNESDAY); + scanner.put(RepeaterDayName.WED_PATTERN_2, DayName.WEDNESDAY); + scanner.put(RepeaterDayName.THU_PATTERN_1, DayName.THURSDAY); + scanner.put(RepeaterDayName.THU_PATTERN_2, DayName.THURSDAY); + scanner.put(RepeaterDayName.FRI_PATTERN, DayName.FRIDAY); + scanner.put(RepeaterDayName.SAT_PATTERN, DayName.SATURDAY); + scanner.put(RepeaterDayName.SUN_PATTERN, DayName.SUNDAY); + for (Map.Entry entry : scanner.entrySet()) { + Pattern scannerItem = entry.getKey(); + if (scannerItem.matcher(token.getWord()).matches()) { + return new RepeaterDayName(scanner.get(scannerItem)); + } + } + return null; + } + + private static ZonedDateTime ymd(ZonedDateTime zonedDateTime) { + return ZonedDateTime.of(zonedDateTime.getYear(), zonedDateTime.getMonthValue(), zonedDateTime.getDayOfMonth(), + 0, 0, 0, 0, zonedDateTime.getZone()); + } + + @Override + protected Span internalNextSpan(PointerType pointer) { + int direction = (pointer == PointerType.FUTURE) ? 1 : -1; + if (currentDayStart == null) { + currentDayStart = ymd(getNow()); + currentDayStart = currentDayStart.plus(direction, ChronoUnit.DAYS); + int dayNum = getType().ordinal(); + while ((currentDayStart.get(ChronoField.DAY_OF_WEEK) - 1) != dayNum) { + currentDayStart = currentDayStart.plus(direction, ChronoUnit.DAYS); + } + } else { + currentDayStart = currentDayStart.plus(direction * 7, ChronoUnit.DAYS); + } + return new Span(currentDayStart, ChronoUnit.DAYS, 1); + } + + @Override + protected Span internalThisSpan(PointerType pointer) { + if (pointer == PointerType.NONE) { + pointer = PointerType.FUTURE; + } + return super.nextSpan(pointer); + } + + @Override + public Span getOffset(Span span, int amount, PointerType pointer) { + throw new IllegalStateException("Not implemented."); + } + + @Override + public int getWidth() { + return RepeaterDayName.DAY_SECONDS; + } + + @Override + public String toString() { + return super.toString() + "-dayname-" + getType(); + } + + /** + * + */ + public enum DayName { + MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY + } +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/RepeaterDayPortion.java b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterDayPortion.java new file mode 100644 index 0000000..5aa879d --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterDayPortion.java @@ -0,0 +1,147 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Range; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.tags.Pointer.PointerType; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Repeater day portion. + * @param type parameter + */ +public abstract class RepeaterDayPortion extends Repeater { + + private static final Pattern AM_PATTERN = Pattern.compile("^ams?$"); + private static final Pattern PM_PATTERN = Pattern.compile("^pms?$"); + private static final Pattern MORNING_PATTERN = Pattern.compile("^mornings?$"); + private static final Pattern AFTERNOON_PATTERN = Pattern.compile("^afternoons?$"); + private static final Pattern EVENING_PATTERN = Pattern.compile("^evenings?$"); + private static final Pattern NIGHT_PATTERN = Pattern.compile("^(night|nite)s?$"); + + private static final int FULL_DAY_SECONDS = 60 * 60 * 24; + private Range range; + private Span currentSpan; + + public RepeaterDayPortion(T type) { + super(type); + range = createRange(type); + } + + public static EnumRepeaterDayPortion scan(Token token) { + Map scanner = new HashMap<>(); + scanner.put(RepeaterDayPortion.AM_PATTERN, DayPortion.AM); + scanner.put(RepeaterDayPortion.PM_PATTERN, DayPortion.PM); + scanner.put(RepeaterDayPortion.MORNING_PATTERN, DayPortion.MORNING); + scanner.put(RepeaterDayPortion.AFTERNOON_PATTERN, DayPortion.AFTERNOON); + scanner.put(RepeaterDayPortion.EVENING_PATTERN, DayPortion.EVENING); + scanner.put(RepeaterDayPortion.NIGHT_PATTERN, DayPortion.NIGHT); + for (Map.Entry entry : scanner.entrySet()) { + Pattern scannerItem = entry.getKey(); + if (scannerItem.matcher(token.getWord()).matches()) { + return new EnumRepeaterDayPortion(scanner.get(scannerItem)); + } + } + return null; + } + + private static ZonedDateTime ymd(ZonedDateTime zonedDateTime) { + return ZonedDateTime.of(zonedDateTime.getYear(), zonedDateTime.getMonthValue(), zonedDateTime.getDayOfMonth(), + 0, 0, 0, 0, zonedDateTime.getZone()); + } + + @Override + protected Span internalNextSpan(PointerType pointer) { + ZonedDateTime rangeStart; + ZonedDateTime rangeEnd; + if (currentSpan == null) { + long nowSeconds = getNow().toInstant().getEpochSecond() - ymd(getNow()).toInstant().getEpochSecond(); + if (nowSeconds < range.getBegin()) { + if (pointer == PointerType.FUTURE) { + rangeStart = ymd(getNow()).plus(range.getBegin(), ChronoUnit.SECONDS); + } else if (pointer == PointerType.PAST) { + rangeStart = ymd(getNow()).minus(1, ChronoUnit.DAYS).plus(range.getBegin(), ChronoUnit.SECONDS); + } else { + throw new IllegalArgumentException("unable to handle pointer type " + pointer); + } + } else if (nowSeconds > range.getBegin()) { + if (pointer == PointerType.FUTURE) { + rangeStart = ymd(getNow()).plus(1, ChronoUnit.DAYS).plus(range.getBegin(), ChronoUnit.SECONDS); + } else if (pointer == PointerType.PAST) { + rangeStart = ymd(getNow()).plus(range.getBegin(), ChronoUnit.SECONDS); + } else { + throw new IllegalArgumentException("unable to handle pointer type " + pointer); + } + } else { + if (pointer == PointerType.FUTURE) { + rangeStart = ymd(getNow()).plus(1, ChronoUnit.DAYS).plus(range.getBegin(), ChronoUnit.SECONDS); + } else if (pointer == PointerType.PAST) { + rangeStart = ymd(getNow()).minus(1, ChronoUnit.DAYS).plus(range.getBegin(), ChronoUnit.SECONDS); + } else { + throw new IllegalArgumentException("unable to handle pointer type " + pointer); + } + } + currentSpan = new Span(rangeStart, rangeStart.plus(range.getWidth(), ChronoUnit.SECONDS)); + } else { + if (pointer == PointerType.FUTURE) { + currentSpan = currentSpan.add(RepeaterDayPortion.FULL_DAY_SECONDS); + } else if (pointer == PointerType.PAST) { + currentSpan = currentSpan.add(-RepeaterDayPortion.FULL_DAY_SECONDS); + } else { + throw new IllegalArgumentException("Unable to handle pointer type " + pointer); + } + } + return currentSpan; + } + + @Override + protected Span internalThisSpan(PointerType pointer) { + ZonedDateTime rangeStart = ymd(getNow()).plus(range.getBegin(), ChronoUnit.SECONDS); + currentSpan = new Span(rangeStart, rangeStart.plus(range.getWidth(), ChronoUnit.SECONDS)); + return currentSpan; + } + + @Override + public Span getOffset(Span span, int amount, PointerType pointer) { + setNow(span.getBeginCalendar()); + Span portionSpan = nextSpan(pointer); + long direction = pointer == PointerType.FUTURE ? 1L : -1L; + portionSpan = portionSpan.add(direction * (amount - 1) * RepeaterDay.DAY_SECONDS); + return portionSpan; + } + + @Override + public int getWidth() { + if (range == null) { + throw new IllegalStateException("Range has not been set"); + } + Long width; + if (currentSpan != null) { + width = currentSpan.getWidth(); + } else { + width = getWidth(range); + } + return width.intValue(); + } + + protected abstract long getWidth(Range range); + + protected abstract Range createRange(T type); + + @Override + public String toString() { + return super.toString() + "-dayportion-" + getType(); + } + + /** + * + */ + public enum DayPortion { + AM, PM, MORNING, AFTERNOON, EVENING, NIGHT + } +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/RepeaterFortnight.java b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterFortnight.java new file mode 100644 index 0000000..b501dd3 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterFortnight.java @@ -0,0 +1,93 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.tags.Pointer.PointerType; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +/** + * + */ +public class RepeaterFortnight extends RepeaterUnit { + public static final int FORTNIGHT_SECONDS = 1209600; // (14 * 24 * 60 * 60) + + private ZonedDateTime currentFortnightStart; + + private static ZonedDateTime ymdh(ZonedDateTime zonedDateTime) { + return ZonedDateTime.of(zonedDateTime.getYear(), zonedDateTime.getMonthValue(), zonedDateTime.getDayOfMonth(), + zonedDateTime.getHour(), 0, 0, 0, zonedDateTime.getZone()); + } + + @Override + protected Span internalNextSpan(PointerType pointer) { + if (currentFortnightStart == null) { + if (pointer == PointerType.FUTURE) { + RepeaterDayName sundayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SUNDAY); + sundayRepeater.setNow(getNow()); + Span nextSundaySpan = sundayRepeater.nextSpan(PointerType.FUTURE); + currentFortnightStart = nextSundaySpan.getBeginCalendar(); + } else if (pointer == PointerType.PAST) { + RepeaterDayName sundayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SUNDAY); + sundayRepeater.setNow(getNow().plus(RepeaterDay.DAY_SECONDS, ChronoUnit.SECONDS)); + sundayRepeater.nextSpan(PointerType.PAST); + sundayRepeater.nextSpan(PointerType.PAST); + Span lastSundaySpan = sundayRepeater.nextSpan(PointerType.PAST); + currentFortnightStart = lastSundaySpan.getBeginCalendar(); + } else { + throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); + } + } else { + long direction = (pointer == PointerType.FUTURE) ? 1L : -1L; + currentFortnightStart = currentFortnightStart.plus(direction * RepeaterFortnight.FORTNIGHT_SECONDS, + ChronoUnit.SECONDS); + } + + return new Span(currentFortnightStart, ChronoUnit.SECONDS, RepeaterFortnight.FORTNIGHT_SECONDS); + } + + @Override + protected Span internalThisSpan(PointerType pointer) { + if (pointer == null) { + pointer = PointerType.FUTURE; + } + + Span span; + if (pointer == PointerType.FUTURE) { + ZonedDateTime thisFortnightStart = ymdh(getNow()).plus(RepeaterHour.HOUR_SECONDS, ChronoUnit.SECONDS); + RepeaterDayName sundayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SUNDAY); + sundayRepeater.setNow(getNow()); + sundayRepeater.thisSpan(PointerType.FUTURE); + Span thisSundaySpan = sundayRepeater.thisSpan(PointerType.FUTURE); + ZonedDateTime thisFortnightEnd = thisSundaySpan.getBeginCalendar(); + span = new Span(thisFortnightStart, thisFortnightEnd); + } else if (pointer == PointerType.PAST) { + ZonedDateTime thisFortnightEnd = ymdh(getNow()); + RepeaterDayName sundayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SUNDAY); + sundayRepeater.setNow(getNow()); + Span lastSundaySpan = sundayRepeater.nextSpan(PointerType.PAST); + ZonedDateTime thisFortnightStart = lastSundaySpan.getBeginCalendar(); + span = new Span(thisFortnightStart, thisFortnightEnd); + } else { + throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); + } + + return span; + } + + @Override + public Span getOffset(Span span, int amount, PointerType pointer) { + long direction = (pointer == PointerType.FUTURE) ? 1L : -1L; + return span.add(direction * amount * RepeaterFortnight.FORTNIGHT_SECONDS); + } + + @Override + public int getWidth() { + return RepeaterFortnight.FORTNIGHT_SECONDS; + } + + @Override + public String toString() { + return super.toString() + "-fortnight"; + } +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/RepeaterHour.java b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterHour.java new file mode 100644 index 0000000..a56bc2a --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterHour.java @@ -0,0 +1,78 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.tags.Pointer.PointerType; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +/** + * + */ +public class RepeaterHour extends RepeaterUnit { + public static final int HOUR_SECONDS = 3600; + + private ZonedDateTime currentDayStart; + + private static ZonedDateTime ymdh(ZonedDateTime zonedDateTime) { + return ZonedDateTime.of(zonedDateTime.getYear(), zonedDateTime.getMonthValue(), zonedDateTime.getDayOfMonth(), + zonedDateTime.getHour(), 0, 0, 0, zonedDateTime.getZone()); + } + + private static ZonedDateTime ymdhm(ZonedDateTime zonedDateTime) { + return ZonedDateTime.of(zonedDateTime.getYear(), zonedDateTime.getMonthValue(), zonedDateTime.getDayOfMonth(), + zonedDateTime.getHour(), zonedDateTime.getMinute(), 0, 0, zonedDateTime.getZone()); + } + + @Override + protected Span internalNextSpan(PointerType pointer) { + if (currentDayStart == null) { + if (pointer == PointerType.FUTURE) { + currentDayStart = ymdh(getNow()).plus(1, ChronoUnit.HOURS); + } else if (pointer == PointerType.PAST) { + currentDayStart = ymdh(getNow()).minus(1, ChronoUnit.HOURS); + } else { + throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); + } + } else { + int direction = (pointer == PointerType.FUTURE) ? 1 : -1; + currentDayStart = currentDayStart.plus(direction, ChronoUnit.HOURS); + } + return new Span(currentDayStart, ChronoUnit.HOURS, 1); + } + + @Override + protected Span internalThisSpan(PointerType pointer) { + ZonedDateTime hourStart; + ZonedDateTime hourEnd; + if (pointer == PointerType.FUTURE) { + hourStart = ymdhm(getNow()).plus(1, ChronoUnit.MINUTES); + hourEnd = ymdh(getNow()).plus(1, ChronoUnit.HOURS); + } else if (pointer == PointerType.PAST) { + hourStart = ymdh(getNow()); + hourEnd = ymdhm(getNow()); + } else if (pointer == PointerType.NONE) { + hourStart = ymdh(getNow()); + hourEnd = hourStart.plus(1, ChronoUnit.HOURS); + } else { + throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); + } + return new Span(hourStart, hourEnd); + } + + @Override + public Span getOffset(Span span, int amount, PointerType pointer) { + long direction = pointer == PointerType.FUTURE ? 1L : -1L; + return span.add(direction * amount * RepeaterHour.HOUR_SECONDS); + } + + @Override + public int getWidth() { + return RepeaterHour.HOUR_SECONDS; + } + + @Override + public String toString() { + return super.toString() + "-hour"; + } +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/RepeaterMinute.java b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterMinute.java new file mode 100644 index 0000000..1b06ef5 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterMinute.java @@ -0,0 +1,73 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.tags.Pointer.PointerType; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +/** + * + */ +public class RepeaterMinute extends RepeaterUnit { + public static final int MINUTE_SECONDS = 60; + + private ZonedDateTime currentMinuteStart; + + private static ZonedDateTime ymdhm(ZonedDateTime zonedDateTime) { + return ZonedDateTime.of(zonedDateTime.getYear(), zonedDateTime.getMonthValue(), zonedDateTime.getDayOfMonth(), + zonedDateTime.getHour(), zonedDateTime.getMinute(), 0, 0, zonedDateTime.getZone()); + } + + @Override + protected Span internalNextSpan(PointerType pointer) { + if (currentMinuteStart == null) { + if (pointer == PointerType.FUTURE) { + currentMinuteStart = ymdhm(getNow()).plus(1, ChronoUnit.MINUTES); + } else if (pointer == PointerType.PAST) { + currentMinuteStart = ymdhm(getNow()).minus(1, ChronoUnit.MINUTES); + } else { + throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); + } + } else { + int direction = pointer == PointerType.FUTURE ? 1 : -1; + currentMinuteStart = currentMinuteStart.plus(direction, ChronoUnit.MINUTES); + } + return new Span(currentMinuteStart, ChronoUnit.SECONDS, RepeaterMinute.MINUTE_SECONDS); + } + + @Override + protected Span internalThisSpan(PointerType pointer) { + ZonedDateTime minuteBegin; + ZonedDateTime minuteEnd; + if (pointer == PointerType.FUTURE) { + minuteBegin = getNow(); + minuteEnd = ymdhm(getNow()); + } else if (pointer == PointerType.PAST) { + minuteBegin = ymdhm(getNow()); + minuteEnd = getNow(); + } else if (pointer == PointerType.NONE) { + minuteBegin = ymdhm(getNow()); + minuteEnd = ymdhm(getNow()).plus(RepeaterMinute.MINUTE_SECONDS, ChronoUnit.SECONDS); + } else { + throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); + } + return new Span(minuteBegin, minuteEnd); + } + + @Override + public Span getOffset(Span span, int amount, PointerType pointer) { + long direction = pointer == PointerType.FUTURE ? 1L : -1L; + return span.add(direction * amount * RepeaterMinute.MINUTE_SECONDS); + } + + @Override + public int getWidth() { + return RepeaterMinute.MINUTE_SECONDS; + } + + @Override + public String toString() { + return super.toString() + "-minute"; + } +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/RepeaterMonth.java b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterMonth.java new file mode 100644 index 0000000..4ec2a1e --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterMonth.java @@ -0,0 +1,73 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.tags.Pointer.PointerType; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +/** + * + */ +public class RepeaterMonth extends RepeaterUnit { + private static final int MONTH_SECONDS = 2592000; // 30 * 24 * 60 * 60 + + private ZonedDateTime currentMonthStart; + + private static ZonedDateTime ymd(ZonedDateTime zonedDateTime) { + return ZonedDateTime.of(zonedDateTime.getYear(), zonedDateTime.getMonthValue(), zonedDateTime.getDayOfMonth(), + 0, 0, 0, 0, zonedDateTime.getZone()); + } + + @Override + protected Span internalNextSpan(PointerType pointer) { + int direction = (pointer == PointerType.FUTURE) ? 1 : -1; + if (currentMonthStart == null) { + currentMonthStart = ZonedDateTime.of(getNow().getYear(), getNow().getMonthValue(), 1, 0, 0, 0, 0, + getNow().getZone()) + .plus(direction, ChronoUnit.MONTHS); + } else { + currentMonthStart = currentMonthStart.plus(direction, ChronoUnit.MONTHS); + } + + return new Span(currentMonthStart, ChronoUnit.MONTHS, 1); + } + + @Override + public Span getOffset(Span span, int amount, PointerType pointer) { + long l = amount * (pointer == PointerType.FUTURE ? 1L : -1L); + return new Span(span.getBeginCalendar().plus(l, ChronoUnit.MONTHS), span.getEndCalendar().plus(l, + ChronoUnit.MONTHS)); + } + + @Override + protected Span internalThisSpan(PointerType pointer) { + ZonedDateTime monthStart; + ZonedDateTime monthEnd; + if (pointer == PointerType.FUTURE) { + monthStart = ymd(getNow()).plus(1, ChronoUnit.DAYS); + monthEnd = ZonedDateTime.of(getNow().getYear(), getNow().getMonthValue(), 1, 0, 0, 0, 0, getNow().getZone()) + .plus(1, ChronoUnit.MONTHS); + } else if (pointer == PointerType.PAST) { + monthStart = ZonedDateTime.of(getNow().getYear(), getNow().getMonthValue(), 1, 0, 0, 0, 0, getNow().getZone()); + monthEnd = ymd(getNow()); + } else if (pointer == PointerType.NONE) { + monthStart = ZonedDateTime.of(getNow().getYear(), getNow().getMonthValue(), 1, 0, 0, 0, 0, getNow().getZone()); + monthEnd = ZonedDateTime.of(getNow().getYear(), getNow().getMonthValue(), 1, 0, 0, 0, 0, getNow().getZone()) + .plus(1, ChronoUnit.MONTHS); + } else { + throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); + } + return new Span(monthStart, monthEnd); + } + + @Override + public int getWidth() { + return RepeaterMonth.MONTH_SECONDS; + } + + @Override + public String toString() { + return super.toString() + "-month"; + } +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/RepeaterMonthName.java b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterMonthName.java new file mode 100644 index 0000000..5602305 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterMonthName.java @@ -0,0 +1,145 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.tags.Pointer.PointerType; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * + */ +public class RepeaterMonthName extends Repeater { + private static final Pattern JAN_PATTERN = Pattern.compile("^jan\\.?(uary)?$"); + private static final Pattern FEB_PATTERN = Pattern.compile("^feb\\.?(ruary)?$"); + private static final Pattern MAR_PATTERN = Pattern.compile("^mar\\.?(ch)?$"); + private static final Pattern APR_PATTERN = Pattern.compile("^apr\\.?(il)?$"); + private static final Pattern MAY_PATTERN = Pattern.compile("^may$"); + private static final Pattern JUN_PATTERN = Pattern.compile("^jun\\.?e?$"); + private static final Pattern JUL_PATTERN = Pattern.compile("^jul\\.?y?$"); + private static final Pattern AUG_PATTERN = Pattern.compile("^aug\\.?(ust)?$"); + private static final Pattern SEP_PATTERN = Pattern.compile("^sep\\.?(t\\.?|tember)?$"); + private static final Pattern OCT_PATTERN = Pattern.compile("^oct\\.?(ober)?$"); + private static final Pattern NOV_PATTERN = Pattern.compile("^nov\\.?(ember)?$"); + private static final Pattern DEC_PATTERN = Pattern.compile("^dec\\.?(ember)?$"); + + private static final int MONTH_SECONDS = 2592000; // 30 * 24 * 60 * 60 + private ZonedDateTime currentMonthBegin; + + public RepeaterMonthName(MonthName type) { + super(type); + } + + public static RepeaterMonthName scan(Token token) { + Map scanner = new HashMap<>(); + scanner.put(RepeaterMonthName.JAN_PATTERN, MonthName.JANUARY); + scanner.put(RepeaterMonthName.FEB_PATTERN, MonthName.FEBRUARY); + scanner.put(RepeaterMonthName.MAR_PATTERN, MonthName.MARCH); + scanner.put(RepeaterMonthName.APR_PATTERN, MonthName.APRIL); + scanner.put(RepeaterMonthName.MAY_PATTERN, MonthName.MAY); + scanner.put(RepeaterMonthName.JUN_PATTERN, MonthName.JUNE); + scanner.put(RepeaterMonthName.JUL_PATTERN, MonthName.JULY); + scanner.put(RepeaterMonthName.AUG_PATTERN, MonthName.AUGUST); + scanner.put(RepeaterMonthName.SEP_PATTERN, MonthName.SEPTEMBER); + scanner.put(RepeaterMonthName.OCT_PATTERN, MonthName.OCTOBER); + scanner.put(RepeaterMonthName.NOV_PATTERN, MonthName.NOVEMBER); + scanner.put(RepeaterMonthName.DEC_PATTERN, MonthName.DECEMBER); + for (Map.Entry entry : scanner.entrySet()) { + Pattern scannerItem = entry.getKey(); + if (scannerItem.matcher(token.getWord()).matches()) { + return new RepeaterMonthName(scanner.get(scannerItem)); + } + } + return null; + } + + public int getIndex() { + return getType().ordinal(); + } + + @Override + protected Span internalNextSpan(PointerType pointer) { + if (currentMonthBegin == null) { + int targetMonth = getType().ordinal(); + int nowMonth = getNow().getMonthValue(); + if (pointer == PointerType.FUTURE) { + if (nowMonth < targetMonth) { + currentMonthBegin = ZonedDateTime.of(getNow().getYear(), targetMonth, 1, 0, 0, 0, 0, getNow().getZone()); + } else if (nowMonth > targetMonth) { + currentMonthBegin = ZonedDateTime.of(getNow().getYear(), targetMonth, 1, 0, 0, 0, 0, getNow().getZone()) + .plus(1, ChronoUnit.YEARS); + } + } else if (pointer == PointerType.NONE) { + if (nowMonth <= targetMonth) { + currentMonthBegin = ZonedDateTime.of(getNow().getYear(), targetMonth, 1, 0, 0, 0, 0, getNow().getZone()); + } else if (nowMonth > targetMonth) { + currentMonthBegin = ZonedDateTime.of(getNow().getYear(), targetMonth, 1, 0, 0, 0, 0, getNow().getZone()) + .plus(1, ChronoUnit.YEARS); + } + } else if (pointer == PointerType.PAST) { + if (nowMonth > targetMonth) { + currentMonthBegin = ZonedDateTime.of(getNow().getYear(), targetMonth, 1, 0, 0, 0, 0, getNow().getZone()); + } else if (nowMonth <= targetMonth) { + currentMonthBegin = ZonedDateTime.of(getNow().getYear(), targetMonth, 1, 0, 0, 0, 0, getNow().getZone()) + .minus(1, ChronoUnit.YEARS); + } + } else { + throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); + } + if (currentMonthBegin == null) { + throw new IllegalStateException("Current month should be set by now."); + } + } else { + if (pointer == PointerType.FUTURE) { + currentMonthBegin = currentMonthBegin.plus(1, ChronoUnit.YEARS); + } else if (pointer == PointerType.PAST) { + currentMonthBegin = currentMonthBegin.minus(1, ChronoUnit.YEARS); + } else { + throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); + } + } + + return new Span(currentMonthBegin, ChronoUnit.MONTHS, 1); + } + + @Override + protected Span internalThisSpan(PointerType pointer) { + Span span; + if (pointer == PointerType.PAST) { + span = nextSpan(pointer); + } else if (pointer == PointerType.FUTURE || pointer == PointerType.NONE) { + span = nextSpan(PointerType.NONE); + } else { + throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); + } + return span; + } + + @Override + public Span getOffset(Span span, int amount, PointerType pointer) { + throw new IllegalStateException("Not implemented."); + } + + @Override + public int getWidth() { + return RepeaterMonthName.MONTH_SECONDS; + } + + @Override + public String toString() { + return super.toString() + "-monthname-" + getType(); + } + + /** + * + */ + public enum MonthName { + ZEROMONTH_DO_NOT_REMOVE, + JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER + } + +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/RepeaterSecond.java b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterSecond.java new file mode 100644 index 0000000..5e4b753 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterSecond.java @@ -0,0 +1,49 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.tags.Pointer.PointerType; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +/** + * + */ +public class RepeaterSecond extends RepeaterUnit { + public static final int SECOND_SECONDS = 1; // (60 * 60); + + private ZonedDateTime secondStart; + + @Override + protected Span internalNextSpan(PointerType pointer) { + int direction = pointer == PointerType.FUTURE ? 1 : -1; + if (secondStart == null) { + secondStart = getNow().plus(direction, ChronoUnit.SECONDS); + } else { + secondStart = secondStart.plus(direction, ChronoUnit.SECONDS); + } + + return new Span(secondStart, ChronoUnit.SECONDS, 1); + } + + @Override + protected Span internalThisSpan(PointerType pointer) { + return new Span(getNow(), ChronoUnit.SECONDS, 1); + } + + @Override + public Span getOffset(Span span, int amount, PointerType pointer) { + long direction = pointer == PointerType.FUTURE ? 1L : -1L; + return span.add(direction * amount * RepeaterSecond.SECOND_SECONDS); + } + + @Override + public int getWidth() { + return RepeaterSecond.SECOND_SECONDS; + } + + @Override + public String toString() { + return super.toString() + "-second"; + } +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/RepeaterTime.java b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterTime.java new file mode 100644 index 0000000..5e37fb4 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterTime.java @@ -0,0 +1,223 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Tick; +import org.xbib.time.chronic.Token; +import org.xbib.time.chronic.tags.Pointer.PointerType; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +/** + * + */ +public class RepeaterTime extends Repeater { + private static final Pattern TIME_PATTERN = Pattern.compile("^\\d{1,2}(:?\\d{2})?([\\.:]?\\d{2})?$"); + private ZonedDateTime currentTime; + + public RepeaterTime(String time) { + super(null); + String t = time.replaceAll(":", ""); + Tick type; + int length = t.length(); + if (length <= 2) { + int hours = Integer.parseInt(t); + int hoursInSeconds = hours * 60 * 60; + if (hours == 12) { + type = new Tick(0, true); + } else { + type = new Tick(hoursInSeconds, true); + } + } else if (length == 3) { + int hoursInSeconds = Integer.parseInt(t.substring(0, 1)) * 60 * 60; + int minutesInSeconds = Integer.parseInt(t.substring(1)) * 60; + type = new Tick(hoursInSeconds + minutesInSeconds, true); + } else if (length == 4) { + boolean ambiguous = (time.contains(":") && Integer.parseInt(t.substring(0, 1)) != 0 && + Integer.parseInt(t.substring(0, 2)) <= 12); + int hours = Integer.parseInt(t.substring(0, 2)); + int hoursInSeconds = hours * 60 * 60; + int minutesInSeconds = Integer.parseInt(t.substring(2)) * 60; + if (hours == 12) { + type = new Tick(minutesInSeconds, ambiguous); + } else { + type = new Tick(hoursInSeconds + minutesInSeconds, ambiguous); + } + } else if (length == 5) { + int hoursInSeconds = Integer.parseInt(t.substring(0, 1)) * 60 * 60; + int minutesInSeconds = Integer.parseInt(t.substring(1, 3)) * 60; + int seconds = Integer.parseInt(t.substring(3)); + type = new Tick(hoursInSeconds + minutesInSeconds + seconds, true); + } else if (length == 6) { + boolean ambiguous = (time.contains(":") && Integer.parseInt(t.substring(0, 1)) != 0 && + Integer.parseInt(t.substring(0, 2)) <= 12); + int hours = Integer.parseInt(t.substring(0, 2)); + int hoursInSeconds = hours * 60 * 60; + int minutesInSeconds = Integer.parseInt(t.substring(2, 4)) * 60; + int seconds = Integer.parseInt(t.substring(4, 6)); + if (hours == 12) { + type = new Tick(minutesInSeconds + seconds, ambiguous); + } else { + type = new Tick(hoursInSeconds + minutesInSeconds + seconds, ambiguous); + } + } else { + throw new IllegalArgumentException("Time cannot exceed six digits"); + } + setType(type); + } + + public static RepeaterTime scan(Token token, List tokens, Options options) { + if (RepeaterTime.TIME_PATTERN.matcher(token.getWord()).matches()) { + return new RepeaterTime(token.getWord()); + } + Integer intStrValue = integerValue(token.getWord()); + if (intStrValue != null) { + return new RepeaterTime(intStrValue.toString()); + } + return null; + } + + private static Integer integerValue(String str) { + if (str != null) { + String s = str.toLowerCase(); + if ("one".equals(s)) { + return 1; + } else if ("two".equals(s)) { + return 2; + } else if ("three".equals(s)) { + return 3; + } else if ("four".equals(s)) { + return 4; + } else if ("five".equals(s)) { + return 5; + } else if ("six".equals(s)) { + return 6; + } else if ("seven".equals(s)) { + return 7; + } else if ("eight".equals(s)) { + return 8; + } else if ("nine".equals(s)) { + return 9; + } else if ("ten".equals(s)) { + return 10; + } else if ("eleven".equals(s)) { + return 11; + } else if ("twelve".equals(s)) { + return 12; + } + } + return null; + } + + private static ZonedDateTime ymd(ZonedDateTime zonedDateTime) { + return ZonedDateTime.of(zonedDateTime.getYear(), zonedDateTime.getMonthValue(), zonedDateTime.getDayOfMonth(), + 0, 0, 0, 0, zonedDateTime.getZone()); + } + + @Override + protected Span internalNextSpan(PointerType pointer) { + int halfDay = RepeaterDay.DAY_SECONDS / 2; + int fullDay = RepeaterDay.DAY_SECONDS; + + ZonedDateTime now = getNow(); + Tick tick = getType(); + boolean first = false; + if (currentTime == null) { + first = true; + ZonedDateTime midnight = ymd(now); + ZonedDateTime yesterdayMidnight = midnight.minus(fullDay, ChronoUnit.SECONDS); + ZonedDateTime tomorrowMidnight = midnight.plus(fullDay, ChronoUnit.SECONDS); + boolean done = false; + if (pointer == PointerType.FUTURE) { + if (tick.isAmbiguous()) { + List futureDates = new LinkedList<>(); + futureDates.add(midnight.plus(tick.intValue(), ChronoUnit.SECONDS)); + futureDates.add(midnight.plus(halfDay + tick.intValue(), ChronoUnit.SECONDS)); + futureDates.add(tomorrowMidnight.plus(tick.intValue(), ChronoUnit.SECONDS)); + for (ZonedDateTime futureDate : futureDates) { + if (futureDate.isAfter(now) || futureDate.equals(now)) { + currentTime = futureDate; + done = true; + break; + } + } + } else { + List futureDates = new LinkedList<>(); + futureDates.add(midnight.plus(tick.intValue(), ChronoUnit.SECONDS)); + futureDates.add(tomorrowMidnight.plus(tick.intValue(), ChronoUnit.SECONDS)); + for (ZonedDateTime futureDate : futureDates) { + if (futureDate.isAfter(now) || futureDate.equals(now)) { + currentTime = futureDate; + done = true; + break; + } + } + } + } else { + if (tick.isAmbiguous()) { + List pastDates = new LinkedList<>(); + pastDates.add(midnight.plus(halfDay + tick.intValue(), ChronoUnit.SECONDS)); + pastDates.add(midnight.plus(tick.intValue(), ChronoUnit.SECONDS)); + pastDates.add(yesterdayMidnight.plus(tick.intValue() * 2, ChronoUnit.SECONDS)); + for (ZonedDateTime pastDate : pastDates) { + if (pastDate.isBefore(now) || pastDate.equals(now)) { + currentTime = pastDate; + done = true; + break; + } + } + } else { + List pastDates = new LinkedList<>(); + pastDates.add(midnight.plus(tick.intValue(), ChronoUnit.SECONDS)); + pastDates.add(yesterdayMidnight.plus(tick.intValue(), ChronoUnit.SECONDS)); + for (ZonedDateTime pastDate : pastDates) { + if (pastDate.isBefore(now) || pastDate.equals(now)) { + currentTime = pastDate; + done = true; + break; + } + } + } + } + + if (!done && currentTime == null) { + throw new IllegalStateException("Current time cannot be null at this point."); + } + } + + if (!first) { + int increment = (tick.isAmbiguous()) ? halfDay : fullDay; + int direction = (pointer == PointerType.FUTURE) ? 1 : -1; + currentTime = currentTime.plus(direction * increment, ChronoUnit.SECONDS); + } + + return new Span(currentTime, currentTime.plus(getWidth(), ChronoUnit.SECONDS)); + } + + @Override + protected Span internalThisSpan(PointerType pointer) { + if (pointer == PointerType.NONE) { + pointer = PointerType.FUTURE; + } + return nextSpan(pointer); + } + + @Override + public Span getOffset(Span span, int amount, PointerType pointer) { + throw new IllegalStateException("Not implemented."); + } + + @Override + public int getWidth() { + return 1; + } + + @Override + public String toString() { + return super.toString() + "-time-" + getType(); + } +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/RepeaterUnit.java b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterUnit.java new file mode 100644 index 0000000..0e5484b --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterUnit.java @@ -0,0 +1,61 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Token; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * + */ +public abstract class RepeaterUnit extends Repeater { + private static final Pattern YEAR_PATTERN = Pattern.compile("^years?$"); + private static final Pattern MONTH_PATTERN = Pattern.compile("^months?$"); + private static final Pattern FORTNIGHT_PATTERN = Pattern.compile("^fortnights?$"); + private static final Pattern WEEK_PATTERN = Pattern.compile("^weeks?$"); + private static final Pattern WEEKEND_PATTERN = Pattern.compile("^weekends?$"); + private static final Pattern DAY_PATTERN = Pattern.compile("^days?$"); + private static final Pattern HOUR_PATTERN = Pattern.compile("^hours?$"); + private static final Pattern MINUTE_PATTERN = Pattern.compile("^minutes?$"); + private static final Pattern SECOND_PATTERN = Pattern.compile("^seconds?$"); + + public RepeaterUnit() { + super(null); + } + + public static RepeaterUnit scan(Token token) { + try { + Map scanner = new HashMap<>(); + scanner.put(RepeaterUnit.YEAR_PATTERN, UnitName.YEAR); + scanner.put(RepeaterUnit.MONTH_PATTERN, UnitName.MONTH); + scanner.put(RepeaterUnit.FORTNIGHT_PATTERN, UnitName.FORTNIGHT); + scanner.put(RepeaterUnit.WEEK_PATTERN, UnitName.WEEK); + scanner.put(RepeaterUnit.WEEKEND_PATTERN, UnitName.WEEKEND); + scanner.put(RepeaterUnit.DAY_PATTERN, UnitName.DAY); + scanner.put(RepeaterUnit.HOUR_PATTERN, UnitName.HOUR); + scanner.put(RepeaterUnit.MINUTE_PATTERN, UnitName.MINUTE); + scanner.put(RepeaterUnit.SECOND_PATTERN, UnitName.SECOND); + for (Map.Entry entry : scanner.entrySet()) { + Pattern scannerItem = entry.getKey(); + if (scannerItem.matcher(token.getWord()).matches()) { + UnitName unitNameEnum = scanner.get(scannerItem); + String unitName = unitNameEnum.name(); + String capitalizedUnitName = unitName.substring(0, 1) + unitName.substring(1).toLowerCase(); + String repeaterClassName = RepeaterUnit.class.getPackage().getName() + ".Repeater" + capitalizedUnitName; + return Class.forName(repeaterClassName).asSubclass(RepeaterUnit.class).newInstance(); + } + } + return null; + } catch (Throwable t) { + throw new RuntimeException("Failed to create RepeaterUnit.", t); + } + } + + /** + * + */ + private enum UnitName { + YEAR, MONTH, FORTNIGHT, WEEK, WEEKEND, DAY, HOUR, MINUTE, SECOND + } +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/RepeaterWeek.java b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterWeek.java new file mode 100644 index 0000000..f135f67 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterWeek.java @@ -0,0 +1,96 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.tags.Pointer.PointerType; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +/** + * + */ +public class RepeaterWeek extends RepeaterUnit { + public static final int WEEK_SECONDS = 604800; // (7 * 24 * 60 * 60); + public static final int WEEK_DAYS = 7; + + private ZonedDateTime currentWeekStart; + + private static ZonedDateTime ymdh(ZonedDateTime zonedDateTime) { + return ZonedDateTime.of(zonedDateTime.getYear(), zonedDateTime.getMonthValue(), zonedDateTime.getDayOfMonth(), + zonedDateTime.getHour(), 0, 0, 0, zonedDateTime.getZone()); + } + + @Override + protected Span internalNextSpan(PointerType pointer) { + if (currentWeekStart == null) { + if (pointer == PointerType.FUTURE) { + RepeaterDayName sundayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SUNDAY); + sundayRepeater.setNow(getNow()); + Span nextSundaySpan = sundayRepeater.nextSpan(PointerType.FUTURE); + currentWeekStart = nextSundaySpan.getBeginCalendar(); + } else if (pointer == PointerType.PAST) { + RepeaterDayName sundayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SUNDAY); + sundayRepeater.setNow(getNow().plus(1, ChronoUnit.DAYS)); + sundayRepeater.nextSpan(PointerType.PAST); + Span lastSundaySpan = sundayRepeater.nextSpan(PointerType.PAST); + currentWeekStart = lastSundaySpan.getBeginCalendar(); + } else { + throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); + } + } else { + int direction = (pointer == PointerType.FUTURE) ? 1 : -1; + currentWeekStart = currentWeekStart.plus(RepeaterWeek.WEEK_DAYS * direction, ChronoUnit.DAYS); + } + + return new Span(currentWeekStart, ChronoUnit.DAYS, RepeaterWeek.WEEK_DAYS); + } + + @Override + protected Span internalThisSpan(PointerType pointer) { + Span thisWeekSpan; + ZonedDateTime thisWeekStart; + ZonedDateTime thisWeekEnd; + if (pointer == PointerType.FUTURE) { + thisWeekStart = ymdh(getNow()).plus(1, ChronoUnit.HOURS); + RepeaterDayName sundayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SUNDAY); + sundayRepeater.setNow(getNow()); + Span thisSundaySpan = sundayRepeater.thisSpan(PointerType.FUTURE); + thisWeekEnd = thisSundaySpan.getBeginCalendar(); + thisWeekSpan = new Span(thisWeekStart, thisWeekEnd); + } else if (pointer == PointerType.PAST) { + thisWeekEnd = ymdh(getNow()); + RepeaterDayName sundayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SUNDAY); + sundayRepeater.setNow(getNow()); + Span lastSundaySpan = sundayRepeater.nextSpan(PointerType.PAST); + thisWeekStart = lastSundaySpan.getBeginCalendar(); + thisWeekSpan = new Span(thisWeekStart, thisWeekEnd); + } else if (pointer == PointerType.NONE) { + RepeaterDayName sundayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SUNDAY); + sundayRepeater.setNow(getNow()); + Span lastSundaySpan = sundayRepeater.nextSpan(PointerType.PAST); + thisWeekStart = lastSundaySpan.getBeginCalendar(); + thisWeekEnd = thisWeekStart.plus(RepeaterWeek.WEEK_DAYS, ChronoUnit.DAYS); + thisWeekSpan = new Span(thisWeekStart, thisWeekEnd); + } else { + throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); + } + return thisWeekSpan; + } + + @Override + public Span getOffset(Span span, int amount, PointerType pointer) { + long direction = pointer == PointerType.FUTURE ? 1L : -1L; + return span.add(direction * amount * RepeaterWeek.WEEK_SECONDS); + } + + @Override + public int getWidth() { + return RepeaterWeek.WEEK_SECONDS; + } + + @Override + public String toString() { + return super.toString() + "-week"; + } + +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/RepeaterWeekend.java b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterWeekend.java new file mode 100644 index 0000000..a241ed5 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterWeekend.java @@ -0,0 +1,80 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.tags.Pointer.PointerType; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +/** + * + */ +public class RepeaterWeekend extends RepeaterUnit { + public static final long WEEKEND_SECONDS = 172800L; // (2 * 24 * 60 * 60); + + private ZonedDateTime currentWeekStart; + + @Override + protected Span internalNextSpan(PointerType pointer) { + if (currentWeekStart == null) { + if (pointer == PointerType.FUTURE) { + RepeaterDayName saturdayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SATURDAY); + saturdayRepeater.setNow(getNow()); + Span nextSaturdaySpan = saturdayRepeater.nextSpan(PointerType.FUTURE); + currentWeekStart = nextSaturdaySpan.getBeginCalendar(); + } else if (pointer == PointerType.PAST) { + RepeaterDayName saturdayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SATURDAY); + saturdayRepeater.setNow(getNow().plus(RepeaterDay.DAY_SECONDS, ChronoUnit.SECONDS)); + Span lastSaturdaySpan = saturdayRepeater.nextSpan(PointerType.PAST); + currentWeekStart = lastSaturdaySpan.getBeginCalendar(); + } + } else { + long direction = pointer == PointerType.FUTURE ? 1L : -1L; + currentWeekStart = currentWeekStart.plus(direction * RepeaterWeek.WEEK_SECONDS, ChronoUnit.SECONDS); + } + assert currentWeekStart != null; + ZonedDateTime c = currentWeekStart.plus(RepeaterWeekend.WEEKEND_SECONDS, ChronoUnit.SECONDS); + return new Span(currentWeekStart, c); + } + + @Override + protected Span internalThisSpan(PointerType pointer) { + Span thisSpan; + if (pointer == PointerType.FUTURE || pointer == PointerType.NONE) { + RepeaterDayName saturdayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SATURDAY); + saturdayRepeater.setNow(getNow()); + Span thisSaturdaySpan = saturdayRepeater.nextSpan(PointerType.FUTURE); + thisSpan = new Span(thisSaturdaySpan.getBeginCalendar(), thisSaturdaySpan.getBeginCalendar() + .plus(RepeaterWeekend.WEEKEND_SECONDS, ChronoUnit.SECONDS)); + } else if (pointer == PointerType.PAST) { + RepeaterDayName saturdayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SATURDAY); + saturdayRepeater.setNow(getNow()); + Span lastSaturdaySpan = saturdayRepeater.nextSpan(PointerType.PAST); + thisSpan = new Span(lastSaturdaySpan.getBeginCalendar(), lastSaturdaySpan.getBeginCalendar() + .plus(RepeaterWeekend.WEEKEND_SECONDS, ChronoUnit.SECONDS)); + } else { + throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); + } + return thisSpan; + } + + @Override + public Span getOffset(Span span, int amount, PointerType pointer) { + long direction = pointer == PointerType.FUTURE ? 1L : -1L; + RepeaterWeekend weekend = new RepeaterWeekend(); + weekend.setNow(span.getBeginCalendar()); + ZonedDateTime start = weekend.nextSpan(pointer).getBeginCalendar().plus((amount - 1) * + direction * RepeaterWeek.WEEK_SECONDS, ChronoUnit.SECONDS); + return new Span(start, start.plus(span.getWidth(), ChronoUnit.SECONDS)); + } + + @Override + public int getWidth() { + return (int) RepeaterWeekend.WEEKEND_SECONDS; + } + + @Override + public String toString() { + return super.toString() + "-weekend"; + } +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/RepeaterYear.java b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterYear.java new file mode 100644 index 0000000..5feb588 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/RepeaterYear.java @@ -0,0 +1,77 @@ +package org.xbib.time.chronic.repeaters; + +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.tags.Pointer.PointerType; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +/** + * + */ +public class RepeaterYear extends RepeaterUnit { + private ZonedDateTime currentYearStart; + + private static ZonedDateTime ymd(ZonedDateTime zonedDateTime) { + return ZonedDateTime.of(zonedDateTime.getYear(), zonedDateTime.getMonthValue(), zonedDateTime.getDayOfMonth(), + 0, 0, 0, 0, zonedDateTime.getZone()); + } + + @Override + protected Span internalNextSpan(PointerType pointer) { + if (currentYearStart == null) { + if (pointer == PointerType.FUTURE) { + currentYearStart = ZonedDateTime.of(getNow().getYear(), 1, 1, 0, 0, 0, 0, getNow().getZone()) + .plus(1, ChronoUnit.YEARS); + } else if (pointer == PointerType.PAST) { + currentYearStart = ZonedDateTime.of(getNow().getYear(), 1, 1, 0, 0, 0, 0, getNow().getZone()) + .minus(1, ChronoUnit.YEARS); + } else { + throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); + } + } else { + int direction = (pointer == PointerType.FUTURE) ? 1 : -1; + currentYearStart = currentYearStart.plus(direction, ChronoUnit.YEARS); + } + return new Span(currentYearStart, ChronoUnit.YEARS, 1); + } + + @Override + protected Span internalThisSpan(PointerType pointer) { + ZonedDateTime yearStart; + ZonedDateTime yearEnd; + if (pointer == PointerType.FUTURE) { + yearStart = ymd(getNow()).plus(1, ChronoUnit.DAYS); + yearEnd = ZonedDateTime.of(getNow().getYear(), 1, 1, 0, 0, 0, 0, getNow().getZone()) + .plus(1, ChronoUnit.YEARS); + } else if (pointer == PointerType.PAST) { + yearStart = ZonedDateTime.of(getNow().getYear(), 1, 1, 0, 0, 0, 0, getNow().getZone()); + yearEnd = ymd(getNow()); + } else if (pointer == PointerType.NONE) { + yearStart = ZonedDateTime.of(getNow().getYear(), 1, 1, 0, 0, 0, 0, getNow().getZone()); + yearEnd = ZonedDateTime.of(getNow().getYear(), 1, 1, 0, 0, 0, 0, getNow().getZone()) + .plus(1, ChronoUnit.YEARS); + } else { + throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); + } + return new Span(yearStart, yearEnd); + } + + @Override + public Span getOffset(Span span, int amount, PointerType pointer) { + long l = amount * (pointer == PointerType.FUTURE ? 1L : -1L); + ZonedDateTime newBegin = span.getBeginCalendar().plus(l, ChronoUnit.YEARS); + ZonedDateTime newEnd = span.getEndCalendar().plus(l, ChronoUnit.YEARS); + return new Span(newBegin, newEnd); + } + + @Override + public int getWidth() { + return (365 * 24 * 60 * 60); + } + + @Override + public String toString() { + return super.toString() + "-year"; + } +} diff --git a/src/main/java/org/xbib/time/chronic/repeaters/package-info.java b/src/main/java/org/xbib/time/chronic/repeaters/package-info.java new file mode 100644 index 0000000..031dcc6 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/repeaters/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for chronic repeaters. + */ +package org.xbib.time.chronic.repeaters; diff --git a/src/main/java/org/xbib/time/chronic/tags/Grabber.java b/src/main/java/org/xbib/time/chronic/tags/Grabber.java new file mode 100644 index 0000000..75ef3d1 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/Grabber.java @@ -0,0 +1,58 @@ +package org.xbib.time.chronic.tags; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Token; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * + */ +public class Grabber extends Tag { + private static final Pattern THIS_PATTERN = Pattern.compile("this"); + private static final Pattern NEXT_PATTERN = Pattern.compile("next"); + private static final Pattern LAST_PATTERN = Pattern.compile("last"); + + public Grabber(Relative type) { + super(type); + } + + public static List scan(List tokens, Options options) { + for (Token token : tokens) { + Grabber t = Grabber.scanForAll(token, options); + if (t != null) { + token.tag(t); + } + } + return tokens; + } + + public static Grabber scanForAll(Token token, Options options) { + Map scanner = new HashMap<>(); + scanner.put(Grabber.LAST_PATTERN, Relative.LAST); + scanner.put(Grabber.NEXT_PATTERN, Relative.NEXT); + scanner.put(Grabber.THIS_PATTERN, Relative.THIS); + for (Map.Entry entry : scanner.entrySet()) { + Pattern scannerItem = entry.getKey(); + if (scannerItem.matcher(token.getWord()).matches()) { + return new Grabber(scanner.get(scannerItem)); + } + } + return null; + } + + @Override + public String toString() { + return "grabber-" + getType(); + } + + /** + * + */ + public enum Relative { + LAST, NEXT, THIS + } +} diff --git a/src/main/java/org/xbib/time/chronic/tags/Ordinal.java b/src/main/java/org/xbib/time/chronic/tags/Ordinal.java new file mode 100644 index 0000000..07839e6 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/Ordinal.java @@ -0,0 +1,48 @@ +package org.xbib.time.chronic.tags; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Token; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * + */ +public class Ordinal extends Tag { + + static final Pattern ORDINAL_PATTERN = Pattern.compile("^(\\d*)(st|nd|rd|th)$"); + + Ordinal(Integer type) { + super(type); + } + + public static List scan(List tokens, Options options) { + for (Token token : tokens) { + Ordinal t; + t = Ordinal.scan(token, options); + if (t != null) { + token.tag(t); + } + t = OrdinalDay.scan(token); + if (t != null) { + token.tag(t); + } + } + return tokens; + } + + public static Ordinal scan(Token token, Options options) { + Matcher ordinalMatcher = ORDINAL_PATTERN.matcher(token.getWord()); + if (ordinalMatcher.find()) { + return new Ordinal(Integer.valueOf(ordinalMatcher.group(1))); + } + return null; + } + + @Override + public String toString() { + return "ordinal"; + } +} diff --git a/src/main/java/org/xbib/time/chronic/tags/OrdinalDay.java b/src/main/java/org/xbib/time/chronic/tags/OrdinalDay.java new file mode 100644 index 0000000..7998c66 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/OrdinalDay.java @@ -0,0 +1,30 @@ +package org.xbib.time.chronic.tags; + +import org.xbib.time.chronic.Token; + +import java.util.regex.Matcher; + +/** + * + */ +public class OrdinalDay extends Ordinal { + public OrdinalDay(Integer type) { + super(type); + } + + public static OrdinalDay scan(Token token) { + Matcher ordinalMatcher = Ordinal.ORDINAL_PATTERN.matcher(token.getWord()); + if (ordinalMatcher.find()) { + int ordinalValue = Integer.parseInt(ordinalMatcher.group(1)); + if (!(ordinalValue > 31)) { + return new OrdinalDay(ordinalValue); + } + } + return null; + } + + @Override + public String toString() { + return super.toString() + "-day-" + getType(); + } +} diff --git a/src/main/java/org/xbib/time/chronic/tags/Pointer.java b/src/main/java/org/xbib/time/chronic/tags/Pointer.java new file mode 100644 index 0000000..c5524c6 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/Pointer.java @@ -0,0 +1,61 @@ +package org.xbib.time.chronic.tags; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Token; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * + */ +public class Pointer extends Tag { + + private static final Pattern IN_PATTERN = Pattern.compile("\\bin\\b"); + + private static final Pattern FUTURE_PATTERN = Pattern.compile("\\bfuture\\b"); + + private static final Pattern PAST_PATTERN = Pattern.compile("\\bpast\\b"); + + public Pointer(PointerType type) { + super(type); + } + + public static List scan(List tokens, Options options) { + for (Token token : tokens) { + Pointer t = Pointer.scanForAll(token, options); + if (t != null) { + token.tag(t); + } + } + return tokens; + } + + public static Pointer scanForAll(Token token, Options options) { + Map scanner = new HashMap<>(); + scanner.put(Pointer.PAST_PATTERN, PointerType.PAST); + scanner.put(Pointer.FUTURE_PATTERN, PointerType.FUTURE); + scanner.put(Pointer.IN_PATTERN, PointerType.FUTURE); + for (Map.Entry entry : scanner.entrySet()) { + Pattern scannerItem = entry.getKey(); + if (scannerItem.matcher(token.getWord()).matches()) { + return new Pointer(scanner.get(scannerItem)); + } + } + return null; + } + + @Override + public String toString() { + return "pointer-" + getType(); + } + + /** + * + */ + public enum PointerType { + PAST, FUTURE, NONE + } +} diff --git a/src/main/java/org/xbib/time/chronic/tags/Scalar.java b/src/main/java/org/xbib/time/chronic/tags/Scalar.java new file mode 100644 index 0000000..a4b6177 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/Scalar.java @@ -0,0 +1,103 @@ +package org.xbib.time.chronic.tags; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Token; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * + */ +public class Scalar extends Tag { + static final Set TIMES = + new HashSet<>(Arrays.asList("am", "pm", "morning", "afternoon", "evening", "night")); + private static final Pattern SCALAR_PATTERN = Pattern.compile("^\\d*$"); + + public Scalar(Integer type) { + super(type); + } + + public static List scan(List tokens, Options options) { + for (int i = 0; i < tokens.size(); i++) { + Token token = tokens.get(i); + Token postToken = null; + if (i < tokens.size() - 1) { + postToken = tokens.get(i + 1); + } + Scalar t; + t = Scalar.scan(token, postToken, options); + if (t != null) { + token.tag(t); + } + t = ScalarDay.scan(token, postToken, options); + if (t != null) { + token.tag(t); + } + t = ScalarMonth.scan(token, postToken, options); + if (t != null) { + token.tag(t); + } + t = ScalarYear.scan(token, postToken, options); + if (t != null) { + token.tag(t); + } + } + return tokens; + } + + public static Scalar scan(Token token, Token postToken, Options options) { + if (Scalar.SCALAR_PATTERN.matcher(token.getWord()).matches()) { + if (token.getWord() != null && token.getWord().length() > 0 && !(postToken != null && + Scalar.TIMES.contains(postToken.getWord()))) { + return new Scalar(Integer.valueOf(token.getWord())); + } + } else { + Integer intStrValue = integerValue(token.getWord()); + if (intStrValue != null) { + return new Scalar(intStrValue); + } + } + return null; + } + + private static Integer integerValue(String str) { + if (str != null) { + String s = str.toLowerCase(); + if ("one".equals(s)) { + return 1; + } else if ("two".equals(s)) { + return 2; + } else if ("three".equals(s)) { + return 3; + } else if ("four".equals(s)) { + return 4; + } else if ("five".equals(s)) { + return 5; + } else if ("six".equals(s)) { + return 6; + } else if ("seven".equals(s)) { + return 7; + } else if ("eight".equals(s)) { + return 8; + } else if ("nine".equals(s)) { + return 9; + } else if ("ten".equals(s)) { + return 10; + } else if ("eleven".equals(s)) { + return 11; + } else if ("twelve".equals(s)) { + return 12; + } + } + return null; + } + + @Override + public String toString() { + return "scalar"; + } +} diff --git a/src/main/java/org/xbib/time/chronic/tags/ScalarDay.java b/src/main/java/org/xbib/time/chronic/tags/ScalarDay.java new file mode 100644 index 0000000..d85d2f5 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/ScalarDay.java @@ -0,0 +1,32 @@ +package org.xbib.time.chronic.tags; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Token; + +import java.util.regex.Pattern; + +/** + * + */ +public class ScalarDay extends Scalar { + private static final Pattern DAY_PATTERN = Pattern.compile("^\\d\\d?$"); + + public ScalarDay(Integer type) { + super(type); + } + + public static ScalarDay scan(Token token, Token postToken, Options options) { + if (ScalarDay.DAY_PATTERN.matcher(token.getWord()).matches()) { + int scalarValue = Integer.parseInt(token.getWord()); + if (!(scalarValue > 31 || (postToken != null && Scalar.TIMES.contains(postToken.getWord())))) { + return new ScalarDay(scalarValue); + } + } + return null; + } + + @Override + public String toString() { + return super.toString() + "-day-" + getType(); + } +} diff --git a/src/main/java/org/xbib/time/chronic/tags/ScalarMonth.java b/src/main/java/org/xbib/time/chronic/tags/ScalarMonth.java new file mode 100644 index 0000000..8e4eb47 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/ScalarMonth.java @@ -0,0 +1,32 @@ +package org.xbib.time.chronic.tags; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Token; + +import java.util.regex.Pattern; + +/** + * + */ +public class ScalarMonth extends Scalar { + private static final Pattern MONTH_PATTERN = Pattern.compile("^\\d\\d?$"); + + public ScalarMonth(Integer type) { + super(type); + } + + public static ScalarMonth scan(Token token, Token postToken, Options options) { + if (ScalarMonth.MONTH_PATTERN.matcher(token.getWord()).matches()) { + int scalarValue = Integer.parseInt(token.getWord()); + if (!(scalarValue > 12 || (postToken != null && Scalar.TIMES.contains(postToken.getWord())))) { + return new ScalarMonth(scalarValue); + } + } + return null; + } + + @Override + public String toString() { + return super.toString() + "-month-" + getType(); + } +} diff --git a/src/main/java/org/xbib/time/chronic/tags/ScalarYear.java b/src/main/java/org/xbib/time/chronic/tags/ScalarYear.java new file mode 100644 index 0000000..cf056a0 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/ScalarYear.java @@ -0,0 +1,37 @@ +package org.xbib.time.chronic.tags; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Token; + +import java.util.regex.Pattern; + +/** + * + */ +public class ScalarYear extends Scalar { + private static final Pattern YEAR_PATTERN = Pattern.compile("^([1-9]\\d)?\\d\\d?$"); + + private ScalarYear(Integer type) { + super(type); + } + + public static ScalarYear scan(Token token, Token postToken, Options options) { + if (ScalarYear.YEAR_PATTERN.matcher(token.getWord()).matches()) { + int scalarValue = Integer.parseInt(token.getWord()); + if (!(postToken != null && Scalar.TIMES.contains(postToken.getWord()))) { + if (scalarValue <= 37) { + scalarValue += 2000; + } else if (scalarValue <= 137 && scalarValue >= 69) { + scalarValue += 1900; + } + return new ScalarYear(scalarValue); + } + } + return null; + } + + @Override + public String toString() { + return super.toString() + "-year-" + getType(); + } +} diff --git a/src/main/java/org/xbib/time/chronic/tags/Separator.java b/src/main/java/org/xbib/time/chronic/tags/Separator.java new file mode 100644 index 0000000..971e6f4 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/Separator.java @@ -0,0 +1,51 @@ +package org.xbib.time.chronic.tags; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Token; + +import java.util.List; + +/** + * + */ +public class Separator extends Tag { + + Separator(SeparatorType type) { + super(type); + } + + public static List scan(List tokens, Options options) { + for (Token token : tokens) { + Separator t; + t = SeparatorComma.scan(token, options); + if (t != null) { + token.tag(t); + } + t = SeparatorSlashOrDash.scan(token, options); + if (t != null) { + token.tag(t); + } + t = SeparatorAt.scan(token, options); + if (t != null) { + token.tag(t); + } + t = SeparatorIn.scan(token, options); + if (t != null) { + token.tag(t); + } + } + return tokens; + } + + @Override + public String toString() { + return "separator"; + } + + /** + * + */ + enum SeparatorType { + COMMA, DASH, SLASH, AT, IN + } +} diff --git a/src/main/java/org/xbib/time/chronic/tags/SeparatorAt.java b/src/main/java/org/xbib/time/chronic/tags/SeparatorAt.java new file mode 100644 index 0000000..ddf93b7 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/SeparatorAt.java @@ -0,0 +1,36 @@ +package org.xbib.time.chronic.tags; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Token; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * + */ +public class SeparatorAt extends Separator { + private static final Pattern AT_PATTERN = Pattern.compile("^(at|@)$"); + + private SeparatorAt(SeparatorType type) { + super(type); + } + + public static SeparatorAt scan(Token token, Options options) { + Map scanner = new HashMap<>(); + scanner.put(SeparatorAt.AT_PATTERN, SeparatorType.AT); + for (Map.Entry entry : scanner.entrySet()) { + Pattern scannerItem = entry.getKey(); + if (scannerItem.matcher(token.getWord()).matches()) { + return new SeparatorAt(scanner.get(scannerItem)); + } + } + return null; + } + + @Override + public String toString() { + return super.toString() + "-at"; + } +} diff --git a/src/main/java/org/xbib/time/chronic/tags/SeparatorComma.java b/src/main/java/org/xbib/time/chronic/tags/SeparatorComma.java new file mode 100644 index 0000000..58b93ce --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/SeparatorComma.java @@ -0,0 +1,37 @@ +package org.xbib.time.chronic.tags; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Token; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * + */ +public class SeparatorComma extends Separator { + private static final Pattern COMMA_PATTERN = Pattern.compile("^,$"); + + private SeparatorComma(SeparatorType type) { + super(type); + } + + public static SeparatorComma scan(Token token, Options options) { + Map scanner = new HashMap<>(); + scanner.put(SeparatorComma.COMMA_PATTERN, SeparatorType.COMMA); + for (Map.Entry entry : scanner.entrySet()) { + Pattern scannerItem = entry.getKey(); + if (scannerItem.matcher(token.getWord()).matches()) { + return new SeparatorComma(scanner.get(scannerItem)); + } + } + return null; + } + + @Override + public String toString() { + return super.toString() + "-comma"; + } + +} diff --git a/src/main/java/org/xbib/time/chronic/tags/SeparatorIn.java b/src/main/java/org/xbib/time/chronic/tags/SeparatorIn.java new file mode 100644 index 0000000..a83a865 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/SeparatorIn.java @@ -0,0 +1,36 @@ +package org.xbib.time.chronic.tags; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Token; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * + */ +public class SeparatorIn extends Separator { + private static final Pattern IN_PATTERN = Pattern.compile("^in$"); + + public SeparatorIn(SeparatorType type) { + super(type); + } + + public static SeparatorIn scan(Token token, Options options) { + Map scanner = new HashMap<>(); + scanner.put(SeparatorIn.IN_PATTERN, SeparatorType.IN); + for (Map.Entry entry : scanner.entrySet()) { + Pattern scannerItem = entry.getKey(); + if (scannerItem.matcher(token.getWord()).matches()) { + return new SeparatorIn(scanner.get(scannerItem)); + } + } + return null; + } + + @Override + public String toString() { + return super.toString() + "-in"; + } +} diff --git a/src/main/java/org/xbib/time/chronic/tags/SeparatorSlashOrDash.java b/src/main/java/org/xbib/time/chronic/tags/SeparatorSlashOrDash.java new file mode 100644 index 0000000..4740455 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/SeparatorSlashOrDash.java @@ -0,0 +1,38 @@ +package org.xbib.time.chronic.tags; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Token; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * + */ +public class SeparatorSlashOrDash extends Separator { + private static final Pattern SLASH_PATTERN = Pattern.compile("^/$"); + private static final Pattern DASH_PATTERN = Pattern.compile("^-$"); + + public SeparatorSlashOrDash(SeparatorType type) { + super(type); + } + + public static SeparatorSlashOrDash scan(Token token, Options options) { + Map scanner = new HashMap<>(); + scanner.put(SeparatorSlashOrDash.DASH_PATTERN, SeparatorType.DASH); + scanner.put(SeparatorSlashOrDash.SLASH_PATTERN, SeparatorType.SLASH); + for (Map.Entry entry : scanner.entrySet()) { + Pattern scannerItem = entry.getKey(); + if (scannerItem.matcher(token.getWord()).matches()) { + return new SeparatorSlashOrDash(scanner.get(scannerItem)); + } + } + return null; + } + + @Override + public String toString() { + return super.toString() + "-slashordash-" + getType(); + } +} diff --git a/src/main/java/org/xbib/time/chronic/tags/StringTag.java b/src/main/java/org/xbib/time/chronic/tags/StringTag.java new file mode 100644 index 0000000..6d546a6 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/StringTag.java @@ -0,0 +1,10 @@ +package org.xbib.time.chronic.tags; + +/** + * + */ +public class StringTag extends Tag { + public StringTag(String type) { + super(type); + } +} diff --git a/src/main/java/org/xbib/time/chronic/tags/Tag.java b/src/main/java/org/xbib/time/chronic/tags/Tag.java new file mode 100644 index 0000000..d314c34 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/Tag.java @@ -0,0 +1,36 @@ +package org.xbib.time.chronic.tags; + +import java.time.ZonedDateTime; + +/** + * Tokens are tagged with subclassed instances of this class when + * they match specific criteria. + * @param type parameter + */ +public class Tag { + + private T type; + + private ZonedDateTime now; + + public Tag(T type) { + this.type = type; + } + + public T getType() { + return type; + } + + public void setType(T type) { + this.type = type; + } + + public ZonedDateTime getNow() { + return now; + } + + public void setNow(ZonedDateTime s) { + this.now = s; + } + +} diff --git a/src/main/java/org/xbib/time/chronic/tags/TimeZone.java b/src/main/java/org/xbib/time/chronic/tags/TimeZone.java new file mode 100644 index 0000000..93edc72 --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/TimeZone.java @@ -0,0 +1,46 @@ +package org.xbib.time.chronic.tags; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Token; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * + */ +public class TimeZone extends Tag { + private static final Pattern TIMEZONE_PATTERN = Pattern.compile("[pmce][ds]t"); + + private TimeZone() { + super(null); + } + + public static List scan(List tokens, Options options) { + for (Token token : tokens) { + TimeZone t = TimeZone.scanForAll(token, options); + if (t != null) { + token.tag(t); + } + } + return tokens; + } + + private static TimeZone scanForAll(Token token, Options options) { + Map scanner = new HashMap<>(); + scanner.put(TimeZone.TIMEZONE_PATTERN, null); + for (Pattern scannerItem : scanner.keySet()) { + if (scannerItem.matcher(token.getWord()).matches()) { + return new TimeZone(); + } + } + return null; + } + + @Override + public String toString() { + return "timezone"; + } +} diff --git a/src/main/java/org/xbib/time/chronic/tags/package-info.java b/src/main/java/org/xbib/time/chronic/tags/package-info.java new file mode 100644 index 0000000..d3c056f --- /dev/null +++ b/src/main/java/org/xbib/time/chronic/tags/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for chronic tags. + */ +package org.xbib.time.chronic.tags; diff --git a/src/main/java/org/xbib/time/format/FormatUtils.java b/src/main/java/org/xbib/time/format/FormatUtils.java new file mode 100644 index 0000000..dcd1ded --- /dev/null +++ b/src/main/java/org/xbib/time/format/FormatUtils.java @@ -0,0 +1,430 @@ +package org.xbib.time.format; + +import java.io.IOException; +import java.io.Writer; + +/** + * Utility methods used by formatters. + * FormatUtils is thread-safe and immutable. + */ +public class FormatUtils { + + private static final double LOG_10 = Math.log(10); + + /** + * Restricted constructor. + */ + private FormatUtils() { + } + + /** + * Converts an integer to a string, prepended with a variable amount of '0' + * pad characters, and appends it to the given buffer. + *

+ * This method is optimized for converting small values to strings. + * + * @param buf receives integer converted to a string + * @param value value to convert to a string + * @param size minimum amount of digits to append + */ + public static void appendPaddedInteger(StringBuilder buf, int value, int size) { + try { + appendPaddedInteger((Appendable) buf, value, size); + } catch (IOException e) { + // StringBuilder does not throw IOException + } + } + + /** + * Converts an integer to a string, prepended with a variable amount of '0' + * pad characters, and appends it to the given appendable. + *

+ * This method is optimized for converting small values to strings. + * + * @param appenadble receives integer converted to a string + * @param value value to convert to a string + * @param size minimum amount of digits to append + * @throws IOException exception + */ + public static void appendPaddedInteger(Appendable appenadble, int value, int size) throws IOException { + if (value < 0) { + appenadble.append('-'); + if (value != Integer.MIN_VALUE) { + value = -value; + } else { + for (; size > 10; size--) { + appenadble.append('0'); + } + appenadble.append("" + -(long) Integer.MIN_VALUE); + return; + } + } + if (value < 10) { + for (; size > 1; size--) { + appenadble.append('0'); + } + appenadble.append((char) (value + '0')); + } else if (value < 100) { + for (; size > 2; size--) { + appenadble.append('0'); + } + // Calculate value div/mod by 10 without using two expensive + // division operations. (2 ^ 27) / 10 = 13421772. Add one to + // value to correct rounding error. + int d = ((value + 1) * 13421772) >> 27; + appenadble.append((char) (d + '0')); + // Append remainder by calculating (value - d * 10). + appenadble.append((char) (value - (d << 3) - (d << 1) + '0')); + } else { + int digits; + if (value < 1000) { + digits = 3; + } else if (value < 10000) { + digits = 4; + } else { + digits = (int) (Math.log(value) / LOG_10) + 1; + } + for (; size > digits; size--) { + appenadble.append('0'); + } + appenadble.append(Integer.toString(value)); + } + } + + /** + * Converts an integer to a string, prepended with a variable amount of '0' + * pad characters, and appends it to the given buffer. + *

+ * This method is optimized for converting small values to strings. + * + * @param buf receives integer converted to a string + * @param value value to convert to a string + * @param size minimum amount of digits to append + */ + public static void appendPaddedInteger(StringBuilder buf, long value, int size) { + try { + appendPaddedInteger((Appendable) buf, value, size); + } catch (IOException e) { + // StringBuilder does not throw IOException + } + } + + /** + * Converts an integer to a string, prepended with a variable amount of '0' + * pad characters, and appends it to the given buffer. + *

+ * This method is optimized for converting small values to strings. + * + * @param appendable receives integer converted to a string + * @param value value to convert to a string + * @param size minimum amount of digits to append + * @throws IOException exception + */ + public static void appendPaddedInteger(Appendable appendable, long value, int size) throws IOException { + int intValue = (int) value; + if (intValue == value) { + appendPaddedInteger(appendable, intValue, size); + } else if (size <= 19) { + appendable.append(Long.toString(value)); + } else { + if (value < 0) { + appendable.append('-'); + if (value != Long.MIN_VALUE) { + value = -value; + } else { + for (; size > 19; size--) { + appendable.append('0'); + } + appendable.append("9223372036854775808"); + return; + } + } + int digits = (int) (Math.log(value) / LOG_10) + 1; + for (; size > digits; size--) { + appendable.append('0'); + } + appendable.append(Long.toString(value)); + } + } + + /** + * Converts an integer to a string, prepended with a variable amount of '0' + * pad characters, and writes it to the given writer. + *

+ * This method is optimized for converting small values to strings. + * + * @param out receives integer converted to a string + * @param value value to convert to a string + * @param size minimum amount of digits to append + * @throws IOException exception + */ + public static void writePaddedInteger(Writer out, int value, int size) + throws IOException { + if (value < 0) { + out.write('-'); + if (value != Integer.MIN_VALUE) { + value = -value; + } else { + for (; size > 10; size--) { + out.write('0'); + } + out.write("" + -(long) Integer.MIN_VALUE); + return; + } + } + if (value < 10) { + for (; size > 1; size--) { + out.write('0'); + } + out.write(value + '0'); + } else if (value < 100) { + for (; size > 2; size--) { + out.write('0'); + } + // Calculate value div/mod by 10 without using two expensive + // division operations. (2 ^ 27) / 10 = 13421772. Add one to + // value to correct rounding error. + int d = ((value + 1) * 13421772) >> 27; + out.write(d + '0'); + // Append remainder by calculating (value - d * 10). + out.write(value - (d << 3) - (d << 1) + '0'); + } else { + int digits; + if (value < 1000) { + digits = 3; + } else if (value < 10000) { + digits = 4; + } else { + digits = (int) (Math.log(value) / LOG_10) + 1; + } + for (; size > digits; size--) { + out.write('0'); + } + out.write(Integer.toString(value)); + } + } + + /** + * Converts an integer to a string, prepended with a variable amount of '0' + * pad characters, and writes it to the given writer. + *

+ * This method is optimized for converting small values to strings. + * + * @param out receives integer converted to a string + * @param value value to convert to a string + * @param size minimum amount of digits to append + * @throws IOException exception + */ + public static void writePaddedInteger(Writer out, long value, int size) + throws IOException { + int intValue = (int) value; + if (intValue == value) { + writePaddedInteger(out, intValue, size); + } else if (size <= 19) { + out.write(Long.toString(value)); + } else { + if (value < 0) { + out.write('-'); + if (value != Long.MIN_VALUE) { + value = -value; + } else { + for (; size > 19; size--) { + out.write('0'); + } + out.write("9223372036854775808"); + return; + } + } + int digits = (int) (Math.log(value) / LOG_10) + 1; + for (; size > digits; size--) { + out.write('0'); + } + out.write(Long.toString(value)); + } + } + + /** + * Converts an integer to a string, and appends it to the given buffer. + *

This method is optimized for converting small values to strings. + * + * @param buf receives integer converted to a string + * @param value value to convert to a string + */ + public static void appendUnpaddedInteger(StringBuilder buf, int value) { + try { + appendUnpaddedInteger((Appendable) buf, value); + } catch (IOException e) { + // StringBuilder do not throw IOException + } + } + + /** + * Converts an integer to a string, and appends it to the given appendable. + *

+ * This method is optimized for converting small values to strings. + * + * @param appendable receives integer converted to a string + * @param value value to convert to a string + * @throws IOException exception + */ + public static void appendUnpaddedInteger(Appendable appendable, int value) throws IOException { + if (value < 0) { + appendable.append('-'); + if (value != Integer.MIN_VALUE) { + value = -value; + } else { + appendable.append("" + -(long) Integer.MIN_VALUE); + return; + } + } + if (value < 10) { + appendable.append((char) (value + '0')); + } else if (value < 100) { + // Calculate value div/mod by 10 without using two expensive + // division operations. (2 ^ 27) / 10 = 13421772. Add one to + // value to correct rounding error. + int d = ((value + 1) * 13421772) >> 27; + appendable.append((char) (d + '0')); + // Append remainder by calculating (value - d * 10). + appendable.append((char) (value - (d << 3) - (d << 1) + '0')); + } else { + appendable.append(Integer.toString(value)); + } + } + + /** + * Converts an integer to a string, and appends it to the given buffer. + *

This method is optimized for converting small values to strings. + * + * @param buf receives integer converted to a string + * @param value value to convert to a string + */ + public static void appendUnpaddedInteger(StringBuilder buf, long value) { + try { + appendUnpaddedInteger((Appendable) buf, value); + } catch (IOException e) { + // StringBuilder do not throw IOException + } + } + + /** + * Converts an integer to a string, and appends it to the given appendable. + *

+ * This method is optimized for converting small values to strings. + * + * @param appendable receives integer converted to a string + * @param value value to convert to a string + * @throws IOException exception + */ + public static void appendUnpaddedInteger(Appendable appendable, long value) throws IOException { + int intValue = (int) value; + if (intValue == value) { + appendUnpaddedInteger(appendable, intValue); + } else { + appendable.append(Long.toString(value)); + } + } + + /** + * Converts an integer to a string, and writes it to the given writer. + *

+ * This method is optimized for converting small values to strings. + * + * @param out receives integer converted to a string + * @param value value to convert to a string + * @throws IOException exception + */ + public static void writeUnpaddedInteger(Writer out, int value) + throws IOException { + if (value < 0) { + out.write('-'); + if (value != Integer.MIN_VALUE) { + value = -value; + } else { + out.write("" + -(long) Integer.MIN_VALUE); + return; + } + } + if (value < 10) { + out.write(value + '0'); + } else if (value < 100) { + // Calculate value div/mod by 10 without using two expensive + // division operations. (2 ^ 27) / 10 = 13421772. Add one to + // value to correct rounding error. + int d = ((value + 1) * 13421772) >> 27; + out.write(d + '0'); + // Append remainder by calculating (value - d * 10). + out.write(value - (d << 3) - (d << 1) + '0'); + } else { + out.write(Integer.toString(value)); + } + } + + /** + * Converts an integer to a string, and writes it to the given writer. + *

+ * This method is optimized for converting small values to strings. + * + * @param out receives integer converted to a string + * @param value value to convert to a string + * @throws IOException exception + */ + public static void writeUnpaddedInteger(Writer out, long value) + throws IOException { + int intValue = (int) value; + if (intValue == value) { + writeUnpaddedInteger(out, intValue); + } else { + out.write(Long.toString(value)); + } + } + + /** + * Calculates the number of decimal digits for the given value, + * including the sign. + * + * @param value value + * @return the number of decimal digits + */ + public static int calculateDigitCount(long value) { + if (value < 0) { + if (value != Long.MIN_VALUE) { + return calculateDigitCount(-value) + 1; + } else { + return 20; + } + } + return + (value < 10 ? 1 : + (value < 100 ? 2 : + (value < 1000 ? 3 : + (value < 10000 ? 4 : + ((int) (Math.log(value) / LOG_10) + 1))))); + } + + static int parseTwoDigits(CharSequence text, int position) { + int value = text.charAt(position) - '0'; + return ((value << 3) + (value << 1)) + text.charAt(position + 1) - '0'; + } + + static String createErrorMessage(final String text, final int errorPos) { + int sampleLen = errorPos + 32; + String sampleText; + if (text.length() <= sampleLen + 3) { + sampleText = text; + } else { + sampleText = text.substring(0, sampleLen).concat("..."); + } + + if (errorPos <= 0) { + return "Invalid format: \"" + sampleText + '"'; + } + + if (errorPos >= text.length()) { + return "Invalid format: \"" + sampleText + "\" is too short"; + } + + return "Invalid format: \"" + sampleText + "\" is malformed at \"" + + sampleText.substring(errorPos) + '"'; + } +} diff --git a/src/main/java/org/xbib/time/format/ISOPeriodFormat.java b/src/main/java/org/xbib/time/format/ISOPeriodFormat.java new file mode 100644 index 0000000..b09ad40 --- /dev/null +++ b/src/main/java/org/xbib/time/format/ISOPeriodFormat.java @@ -0,0 +1,190 @@ +package org.xbib.time.format; + +/** + * Factory that creates instances of PeriodFormatter for the ISO8601 standard. + * Period formatting is performed by the {@link PeriodFormatter} class. + * Three classes provide factory methods to create formatters, and this is one. + * The others are {@link PeriodFormat} and {@link PeriodFormatterBuilder}. + * ISOPeriodFormat is thread-safe and immutable, and the formatters it + * returns are as well. + */ +public class ISOPeriodFormat { + + /** + * Cache of standard format. + */ + private static PeriodFormatter cStandard; + /** + * Cache of alternate months format. + */ + private static PeriodFormatter cAlternate; + /** + * Cache of alternate extended months format. + */ + private static PeriodFormatter cAlternateExtended; + /** + * Cache of alternate weeks format. + */ + private static PeriodFormatter cAlternateWithWeeks; + /** + * Cache of alternate extended weeks format. + */ + private static PeriodFormatter cAlternateExtendedWihWeeks; + + /** + * Constructor. + */ + protected ISOPeriodFormat() { + super(); + } + + /** + * The standard ISO format - PyYmMwWdDThHmMsS. + * Milliseconds are not output. + * Note that the ISO8601 standard actually indicates weeks should not + * be shown if any other field is present and vice versa. + * + * @return the formatter + */ + public static PeriodFormatter standard() { + if (cStandard == null) { + cStandard = new PeriodFormatterBuilder() + .appendLiteral("P") + .appendYears() + .appendSuffix("Y") + .appendMonths() + .appendSuffix("M") + .appendWeeks() + .appendSuffix("W") + .appendDays() + .appendSuffix("D") + .appendSeparatorIfFieldsAfter("T") + .appendHours() + .appendSuffix("H") + .appendMinutes() + .appendSuffix("M") + .appendSeconds() + .appendSuffix("S") + .toFormatter(); + } + return cStandard; + } + + /** + * The alternate ISO format, PyyyymmddThhmmss, which excludes weeks. + *

+ * Even if weeks are present in the period, they are not output. + * Fractional seconds (milliseconds) will appear if required. + * + * @return the formatter + */ + public static PeriodFormatter alternate() { + if (cAlternate == null) { + cAlternate = new PeriodFormatterBuilder() + .appendLiteral("P") + .minimumPrintedDigits(4) + .appendYears() + .minimumPrintedDigits(2) + .appendMonths() + .appendDays() + .appendSeparatorIfFieldsAfter("T") + .appendHours() + .appendMinutes() + .appendSeconds() + .appendMillis() + .toFormatter(); + } + return cAlternate; + } + + /** + * The alternate ISO format, Pyyyy-mm-ddThh:mm:ss, which excludes weeks. + *

+ * Even if weeks are present in the period, they are not output. + * Fractional seconds (milliseconds) will appear if required. + * + * @return the formatter + */ + public static PeriodFormatter alternateExtended() { + if (cAlternateExtended == null) { + cAlternateExtended = new PeriodFormatterBuilder() + .appendLiteral("P") + .minimumPrintedDigits(4) + .appendYears() + .appendSeparator("-") + .minimumPrintedDigits(2) + .appendMonths() + .appendSeparator("-") + .appendDays() + .appendSeparatorIfFieldsAfter("T") + .appendHours() + .appendSeparator(":") + .appendMinutes() + .appendSeparator(":") + .appendSeconds() + .appendMillis() + .toFormatter(); + } + return cAlternateExtended; + } + + /** + * The alternate ISO format, PyyyyWwwddThhmmss, which excludes months. + *

+ * Even if months are present in the period, they are not output. + * Fractional seconds (milliseconds) will appear if required. + * + * @return the formatter + */ + public static PeriodFormatter alternateWithWeeks() { + if (cAlternateWithWeeks == null) { + cAlternateWithWeeks = new PeriodFormatterBuilder() + .appendLiteral("P") + .minimumPrintedDigits(4) + .appendYears() + .minimumPrintedDigits(2) + .appendPrefix("W") + .appendWeeks() + .appendDays() + .appendSeparatorIfFieldsAfter("T") + .appendHours() + .appendMinutes() + .appendSeconds() + .appendMillis() + .toFormatter(); + } + return cAlternateWithWeeks; + } + + /** + * The alternate ISO format, Pyyyy-Www-ddThh:mm:ss, which excludes months. + *

+ * Even if months are present in the period, they are not output. + * Fractional seconds (milliseconds) will appear if required. + * + * @return the formatter + */ + public static PeriodFormatter alternateExtendedWithWeeks() { + if (cAlternateExtendedWihWeeks == null) { + cAlternateExtendedWihWeeks = new PeriodFormatterBuilder() + .appendLiteral("P") + .minimumPrintedDigits(4) + .appendYears() + .appendSeparator("-") + .minimumPrintedDigits(2) + .appendPrefix("W") + .appendWeeks() + .appendSeparator("-") + .appendDays() + .appendSeparatorIfFieldsAfter("T") + .appendHours() + .appendSeparator(":") + .appendMinutes() + .appendSeparator(":") + .appendSeconds() + .appendMillis() + .toFormatter(); + } + return cAlternateExtendedWihWeeks; + } +} diff --git a/src/main/java/org/xbib/time/format/PeriodAmount.java b/src/main/java/org/xbib/time/format/PeriodAmount.java new file mode 100644 index 0000000..06a6bb4 --- /dev/null +++ b/src/main/java/org/xbib/time/format/PeriodAmount.java @@ -0,0 +1,41 @@ +package org.xbib.time.format; + +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAmount; +import java.time.temporal.TemporalUnit; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * + */ +public class PeriodAmount implements TemporalAmount { + + private Map amounts = new HashMap<>(); + + public void set(TemporalUnit unit, Long value) { + amounts.put(unit, value); + } + + @Override + public long get(TemporalUnit unit) { + return amounts.get(unit); + } + + @Override + public List getUnits() { + return new ArrayList<>(amounts.keySet()); + } + + @Override + public Temporal addTo(Temporal temporal) { + return temporal.plus(this); + } + + @Override + public Temporal subtractFrom(Temporal temporal) { + return temporal.minus(this); + } +} diff --git a/src/main/java/org/xbib/time/format/PeriodFormat.java b/src/main/java/org/xbib/time/format/PeriodFormat.java new file mode 100644 index 0000000..b5fcc74 --- /dev/null +++ b/src/main/java/org/xbib/time/format/PeriodFormat.java @@ -0,0 +1,374 @@ +package org.xbib.time.format; + +import java.io.IOException; +import java.io.Writer; +import java.time.Period; +import java.util.Enumeration; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Factory that creates instances of PeriodFormatter. + * Period formatting is performed by the {@link PeriodFormatter} class. + * Three classes provide factory methods to create formatters, and this is one. + * The others are {@link ISOPeriodFormat} and {@link PeriodFormatterBuilder}. + * PeriodFormat is thread-safe and immutable, and the formatters it returns + * are as well. + */ +public class PeriodFormat { + + /** + * The resource bundle name. + */ + private static final String BUNDLE_NAME = "org.xbib.time.format.messages"; + /** + * The created formatters. + */ + private static final ConcurrentMap FORMATTERS = new ConcurrentHashMap(); + + /** + * Constructor. + */ + protected PeriodFormat() { + super(); + } + + /** + * Gets the default formatter that outputs words in English. + * This calls {@link #wordBased(Locale)} using a locale of {@code ENGLISH}. + * + * @return the formatter, not null + */ + public static PeriodFormatter getDefault() { + return wordBased(Locale.ENGLISH); + } + + /** + * Returns a word based formatter for the JDK default locale. + * This calls {@link #wordBased(Locale)} using the {@link Locale#getDefault() default locale}. + * + * @return the formatter, not null + */ + public static PeriodFormatter wordBased() { + return wordBased(Locale.getDefault()); + } + + /** + * Returns a word based formatter for the specified locale. + *

+ * The words are configured in a resource bundle text file - + * {@code org.joda.time.format.messages}. + * This can be added to via the normal classpath resource bundle mechanisms. + *

+ * You can add your own translation by creating messages_<locale>.properties file + * and adding it to the {@code org.joda.time.format.messages} path. + *

+ * Simple example (1 = singular suffix, not 1 = plural suffix): + *

+     * PeriodFormat.space=\
+     * PeriodFormat.comma=,
+     * PeriodFormat.commandand=,and
+     * PeriodFormat.commaspaceand=, and
+     * PeriodFormat.commaspace=,
+     * PeriodFormat.spaceandspace=\ and
+     * PeriodFormat.year=\ year
+     * PeriodFormat.years=\ years
+     * PeriodFormat.month=\ month
+     * PeriodFormat.months=\ months
+     * PeriodFormat.week=\ week
+     * PeriodFormat.weeks=\ weeks
+     * PeriodFormat.day=\ day
+     * PeriodFormat.days=\ days
+     * PeriodFormat.hour=\ hour
+     * PeriodFormat.hours=\ hours
+     * PeriodFormat.minute=\ minute
+     * PeriodFormat.minutes=\ minutes
+     * PeriodFormat.second=\ second
+     * PeriodFormat.seconds=\ seconds
+     * PeriodFormat.millisecond=\ millisecond
+     * PeriodFormat.milliseconds=\ milliseconds
+     * 
+ *

+ * Some languages contain more than two suffixes. You can use regular expressions + * for them. Here's an example using regular expression for English: + *

+     * PeriodFormat.space=\
+     * PeriodFormat.comma=,
+     * PeriodFormat.commandand=,and
+     * PeriodFormat.commaspaceand=, and
+     * PeriodFormat.commaspace=,
+     * PeriodFormat.spaceandspace=\ and
+     * PeriodFormat.regex.separator=%
+     * PeriodFormat.years.regex=1$%.*
+     * PeriodFormat.years.list=\ year%\ years
+     * PeriodFormat.months.regex=1$%.*
+     * PeriodFormat.months.list=\ month%\ months
+     * PeriodFormat.weeks.regex=1$%.*
+     * PeriodFormat.weeks.list=\ week%\ weeks
+     * PeriodFormat.days.regex=1$%.*
+     * PeriodFormat.days.list=\ day%\ days
+     * PeriodFormat.hours.regex=1$%.*
+     * PeriodFormat.hours.list=\ hour%\ hours
+     * PeriodFormat.minutes.regex=1$%.*
+     * PeriodFormat.minutes.list=\ minute%\ minutes
+     * PeriodFormat.seconds.regex=1$%.*
+     * PeriodFormat.seconds.list=\ second%\ seconds
+     * PeriodFormat.milliseconds.regex=1$%.*
+     * PeriodFormat.milliseconds.list=\ millisecond%\ milliseconds
+     * 
+ *

+ * You can mix both approaches. Here's example for Polish ( + * "1 year, 2 years, 5 years, 12 years, 15 years, 21 years, 22 years, 25 years" + * translates to + * "1 rok, 2 lata, 5 lat, 12 lat, 15 lat, 21 lat, 22 lata, 25 lat"). Notice that + * PeriodFormat.day and PeriodFormat.days is used for day suffixes as there is no + * need for regular expressions: + *

+     * PeriodFormat.space=\
+     * PeriodFormat.comma=,
+     * PeriodFormat.commandand=,i
+     * PeriodFormat.commaspaceand=, i
+     * PeriodFormat.commaspace=,
+     * PeriodFormat.spaceandspace=\ i
+     * PeriodFormat.regex.separator=%
+     * PeriodFormat.years.regex=^1$%[0-9]*(?<!1)[2-4]$%[0-9]*
+     * PeriodFormat.years.list=\ rok%\ lata%\ lat
+     * PeriodFormat.months.regex=^1$%[0-9]*(?<!1)[2-4]$%[0-9]*
+     * PeriodFormat.months.list=\ miesi\u0105c%\ miesi\u0105ce%\ miesi\u0119cy
+     * PeriodFormat.weeks.regex=^1$%[0-9]*(?<!1)[2-4]$%[0-9]*
+     * PeriodFormat.weeks.list=\ tydzie\u0144%\ tygodnie%\ tygodni
+     * PeriodFormat.day=\ dzie\u0144
+     * PeriodFormat.days=\ dni
+     * PeriodFormat.hours.regex=^1$%[0-9]*(?<!1)[2-4]$%[0-9]*
+     * PeriodFormat.hours.list=\ godzina%\ godziny%\ godzin
+     * PeriodFormat.minutes.regex=^1$%[0-9]*(?<!1)[2-4]$%[0-9]*
+     * PeriodFormat.minutes.list=\ minuta%\ minuty%\ minut
+     * PeriodFormat.seconds.regex=^1$%[0-9]*(?<!1)[2-4]$%[0-9]*
+     * PeriodFormat.seconds.list=\ sekunda%\ sekundy%\ sekund
+     * PeriodFormat.milliseconds.regex=^1$%[0-9]*(?<!1)[2-4]$%[0-9]*
+     * PeriodFormat.milliseconds.list=\ milisekunda%\ milisekundy%\ milisekund
+     * 
+ * Each PeriodFormat.<duration_field_type>.regex property stands for an array of + * regular expressions and is followed by a property + * PeriodFormat.<duration_field_type>.list holding an array of suffixes. + * PeriodFormat.regex.separator is used for splitting. See + * {@link PeriodFormatterBuilder#appendSuffix(String[], String[])} for details. + *

+ * Available languages are English, Danish, Dutch, French, German, Japanese, + * Polish, Portuguese and Spanish. + * + * @param locale locale + * @return the formatter, not null + */ + public static PeriodFormatter wordBased(Locale locale) { + PeriodFormatter pf = FORMATTERS.get(locale); + if (pf == null) { + DynamicWordBased dynamic = new DynamicWordBased(buildWordBased(locale)); + pf = new PeriodFormatter(dynamic, dynamic, locale); + PeriodFormatter existing = FORMATTERS.putIfAbsent(locale, pf); + if (existing != null) { + pf = existing; + } + } + return pf; + } + + private static PeriodFormatter buildWordBased(Locale locale) { + ResourceBundle b = ResourceBundle.getBundle(BUNDLE_NAME, locale); + if (containsKey(b, "PeriodFormat.regex.separator")) { + return buildRegExFormatter(b, locale); + } else { + return buildNonRegExFormatter(b, locale); + } + } + + private static PeriodFormatter buildRegExFormatter(ResourceBundle b, Locale locale) { + String[] variants = retrieveVariants(b); + String regExSeparator = b.getString("PeriodFormat.regex.separator"); + + PeriodFormatterBuilder builder = new PeriodFormatterBuilder(); + builder.appendYears(); + if (containsKey(b, "PeriodFormat.years.regex")) { + builder.appendSuffix( + b.getString("PeriodFormat.years.regex").split(regExSeparator), + b.getString("PeriodFormat.years.list").split(regExSeparator)); + } else { + builder.appendSuffix(b.getString("PeriodFormat.year"), b.getString("PeriodFormat.years")); + } + + builder.appendSeparator(b.getString("PeriodFormat.commaspace"), b.getString("PeriodFormat.spaceandspace"), variants); + builder.appendMonths(); + if (containsKey(b, "PeriodFormat.months.regex")) { + builder.appendSuffix( + b.getString("PeriodFormat.months.regex").split(regExSeparator), + b.getString("PeriodFormat.months.list").split(regExSeparator)); + } else { + builder.appendSuffix(b.getString("PeriodFormat.month"), b.getString("PeriodFormat.months")); + } + + builder.appendSeparator(b.getString("PeriodFormat.commaspace"), b.getString("PeriodFormat.spaceandspace"), variants); + builder.appendWeeks(); + if (containsKey(b, "PeriodFormat.weeks.regex")) { + builder.appendSuffix( + b.getString("PeriodFormat.weeks.regex").split(regExSeparator), + b.getString("PeriodFormat.weeks.list").split(regExSeparator)); + } else { + builder.appendSuffix(b.getString("PeriodFormat.week"), b.getString("PeriodFormat.weeks")); + } + + builder.appendSeparator(b.getString("PeriodFormat.commaspace"), b.getString("PeriodFormat.spaceandspace"), variants); + builder.appendDays(); + if (containsKey(b, "PeriodFormat.days.regex")) { + builder.appendSuffix( + b.getString("PeriodFormat.days.regex").split(regExSeparator), + b.getString("PeriodFormat.days.list").split(regExSeparator)); + } else { + builder.appendSuffix(b.getString("PeriodFormat.day"), b.getString("PeriodFormat.days")); + } + + builder.appendSeparator(b.getString("PeriodFormat.commaspace"), b.getString("PeriodFormat.spaceandspace"), variants); + builder.appendHours(); + if (containsKey(b, "PeriodFormat.hours.regex")) { + builder.appendSuffix( + b.getString("PeriodFormat.hours.regex").split(regExSeparator), + b.getString("PeriodFormat.hours.list").split(regExSeparator)); + } else { + builder.appendSuffix(b.getString("PeriodFormat.hour"), b.getString("PeriodFormat.hours")); + } + + builder.appendSeparator(b.getString("PeriodFormat.commaspace"), b.getString("PeriodFormat.spaceandspace"), variants); + builder.appendMinutes(); + if (containsKey(b, "PeriodFormat.minutes.regex")) { + builder.appendSuffix( + b.getString("PeriodFormat.minutes.regex").split(regExSeparator), + b.getString("PeriodFormat.minutes.list").split(regExSeparator)); + } else { + builder.appendSuffix(b.getString("PeriodFormat.minute"), b.getString("PeriodFormat.minutes")); + } + + builder.appendSeparator(b.getString("PeriodFormat.commaspace"), b.getString("PeriodFormat.spaceandspace"), variants); + builder.appendSeconds(); + if (containsKey(b, "PeriodFormat.seconds.regex")) { + builder.appendSuffix( + b.getString("PeriodFormat.seconds.regex").split(regExSeparator), + b.getString("PeriodFormat.seconds.list").split(regExSeparator)); + } else { + builder.appendSuffix(b.getString("PeriodFormat.second"), b.getString("PeriodFormat.seconds")); + } + + builder.appendSeparator(b.getString("PeriodFormat.commaspace"), b.getString("PeriodFormat.spaceandspace"), variants); + builder.appendMillis(); + if (containsKey(b, "PeriodFormat.milliseconds.regex")) { + builder.appendSuffix( + b.getString("PeriodFormat.milliseconds.regex").split(regExSeparator), + b.getString("PeriodFormat.milliseconds.list").split(regExSeparator)); + } else { + builder.appendSuffix(b.getString("PeriodFormat.millisecond"), b.getString("PeriodFormat.milliseconds")); + } + return builder.toFormatter().withLocale(locale); + } + + private static PeriodFormatter buildNonRegExFormatter(ResourceBundle b, Locale locale) { + String[] variants = retrieveVariants(b); + return new PeriodFormatterBuilder() + .appendYears() + .appendSuffix(b.getString("PeriodFormat.year"), b.getString("PeriodFormat.years")) + .appendSeparator(b.getString("PeriodFormat.commaspace"), b.getString("PeriodFormat.spaceandspace"), variants) + .appendMonths() + .appendSuffix(b.getString("PeriodFormat.month"), b.getString("PeriodFormat.months")) + .appendSeparator(b.getString("PeriodFormat.commaspace"), b.getString("PeriodFormat.spaceandspace"), variants) + .appendWeeks() + .appendSuffix(b.getString("PeriodFormat.week"), b.getString("PeriodFormat.weeks")) + .appendSeparator(b.getString("PeriodFormat.commaspace"), b.getString("PeriodFormat.spaceandspace"), variants) + .appendDays() + .appendSuffix(b.getString("PeriodFormat.day"), b.getString("PeriodFormat.days")) + .appendSeparator(b.getString("PeriodFormat.commaspace"), b.getString("PeriodFormat.spaceandspace"), variants) + .appendHours() + .appendSuffix(b.getString("PeriodFormat.hour"), b.getString("PeriodFormat.hours")) + .appendSeparator(b.getString("PeriodFormat.commaspace"), b.getString("PeriodFormat.spaceandspace"), variants) + .appendMinutes() + .appendSuffix(b.getString("PeriodFormat.minute"), b.getString("PeriodFormat.minutes")) + .appendSeparator(b.getString("PeriodFormat.commaspace"), b.getString("PeriodFormat.spaceandspace"), variants) + .appendSeconds() + .appendSuffix(b.getString("PeriodFormat.second"), b.getString("PeriodFormat.seconds")) + .appendSeparator(b.getString("PeriodFormat.commaspace"), b.getString("PeriodFormat.spaceandspace"), variants) + .appendMillis() + .appendSuffix(b.getString("PeriodFormat.millisecond"), b.getString("PeriodFormat.milliseconds")) + .toFormatter() + .withLocale(locale); + } + + private static String[] retrieveVariants(ResourceBundle b) { + return new String[]{b.getString("PeriodFormat.space"), b.getString("PeriodFormat.comma"), + b.getString("PeriodFormat.commandand"), b.getString("PeriodFormat.commaspaceand")}; + } + + // simulate ResourceBundle.containsKey() + private static boolean containsKey(ResourceBundle bundle, String key) { + for (Enumeration en = bundle.getKeys(); en.hasMoreElements(); ) { + if (en.nextElement().equals(key)) { + return true; + } + } + return false; + } + + /** + * Printer/parser that reacts to the locale and changes the word-based + * pattern if necessary. + */ + static class DynamicWordBased implements PeriodPrinter, PeriodParser { + + /** + * The formatter with the locale selected at construction time. + */ + private final PeriodFormatter iFormatter; + + DynamicWordBased(PeriodFormatter formatter) { + iFormatter = formatter; + } + + @Override + public int countFieldsToPrint(Period period, int stopAt, Locale locale) { + return getPrinter(locale).countFieldsToPrint(period, stopAt, locale); + } + + @Override + public int calculatePrintedLength(Period period, Locale locale) { + return getPrinter(locale).calculatePrintedLength(period, locale); + } + + @Override + public void printTo(StringBuilder buf, Period period, Locale locale) { + getPrinter(locale).printTo(buf, period, locale); + } + + @Override + public void printTo(Writer out, Period period, Locale locale) throws IOException { + getPrinter(locale).printTo(out, period, locale); + } + + + @Override + public int parseInto(PeriodAmount period, String periodStr, int position, Locale locale) { + return getParser(locale).parseInto(period, periodStr, position, locale); + } + + private PeriodParser getParser(Locale locale) { + if (locale != null && !locale.equals(iFormatter.getLocale())) { + return wordBased(locale).getParser(); + } + return iFormatter.getParser(); + } + + private PeriodPrinter getPrinter(Locale locale) { + if (locale != null && !locale.equals(iFormatter.getLocale())) { + return wordBased(locale).getPrinter(); + } + return iFormatter.getPrinter(); + } + + } +} diff --git a/src/main/java/org/xbib/time/format/PeriodFormatter.java b/src/main/java/org/xbib/time/format/PeriodFormatter.java new file mode 100644 index 0000000..2df1e20 --- /dev/null +++ b/src/main/java/org/xbib/time/format/PeriodFormatter.java @@ -0,0 +1,257 @@ +package org.xbib.time.format; + +import java.io.IOException; +import java.io.Writer; +import java.time.Period; +import java.util.Locale; + +/** + * Controls the printing and parsing of a time period to and from a string. + *

+ * This class is the main API for printing and parsing used by most applications. + * Instances of this class are created via one of three factory classes: + *

    + *
  • {@link PeriodFormat} - formats by pattern and style
  • + *
  • {@link ISOPeriodFormat} - ISO8601 formats
  • + *
+ *

+ * An instance of this class holds a reference internally to one printer and + * one parser. It is possible that one of these may be null, in which case the + * formatter cannot print/parse. This can be checked via the {@link #isPrinter()} + * and {@link #isParser()} methods. + *

+ * The underlying printer/parser can be altered to behave exactly as required + * by using a decorator modifier: + *

    + *
  • {@link #withLocale(Locale)} - returns a new formatter that uses the specified locale
  • + *
+ * This returns a new formatter (instances of this class are immutable). + *

+ * The main methods of the class are the printXxx and + * parseXxx methods. These are used as follows: + *

+ * // print using the default locale
+ * String periodStr = formatter.print(period);
+ * // print using the French locale
+ * String periodStr = formatter.withLocale(Locale.FRENCH).print(period);
+ *
+ * // parse using the French locale
+ * Period date = formatter.withLocale(Locale.FRENCH).parsePeriod(str);
+ * 
+ */ +public class PeriodFormatter { + + /** + * The internal printer used to output the datetime. + */ + private final PeriodPrinter iPrinter; + /** + * The internal parser used to output the datetime. + */ + private final PeriodParser iParser; + /** + * The locale to use for printing and parsing. + */ + private final Locale iLocale; + + /** + * Creates a new formatter, however you will normally use the factory + * or the builder. + * + * @param printer the internal printer, null if cannot print + * @param parser the internal parser, null if cannot parse + */ + public PeriodFormatter(PeriodPrinter printer, PeriodParser parser) { + super(); + iPrinter = printer; + iParser = parser; + iLocale = null; + } + + /** + * Constructor. + * + * @param printer the internal printer, null if cannot print + * @param parser the internal parser, null if cannot parse + * @param locale the locale to use + */ + PeriodFormatter(PeriodPrinter printer, PeriodParser parser, Locale locale) { + super(); + iPrinter = printer; + iParser = parser; + iLocale = locale; + } + + /** + * Is this formatter capable of printing. + * + * @return true if this is a printer + */ + public boolean isPrinter() { + return (iPrinter != null); + } + + /** + * Gets the internal printer object that performs the real printing work. + * + * @return the internal printer + */ + public PeriodPrinter getPrinter() { + return iPrinter; + } + + /** + * Is this formatter capable of parsing. + * + * @return true if this is a parser + */ + public boolean isParser() { + return (iParser != null); + } + + /** + * Gets the internal parser object that performs the real parsing work. + * + * @return the internal parser + */ + public PeriodParser getParser() { + return iParser; + } + + /** + * Returns a new formatter with a different locale that will be used + * for printing and parsing. + *

+ * A PeriodFormatter is immutable, so a new instance is returned, + * and the original is unaltered and still usable. + *

+ * A null locale indicates that no specific locale override is in use. + * + * @param locale the locale to use + * @return the new formatter + */ + public PeriodFormatter withLocale(Locale locale) { + if (locale == getLocale() || (locale != null && locale.equals(getLocale()))) { + return this; + } + return new PeriodFormatter(iPrinter, iParser, locale); + } + + /** + * Gets the locale that will be used for printing and parsing. + *

+ * A null locale indicates that no specific locale override is in use. + * + * @return the locale to use + */ + public Locale getLocale() { + return iLocale; + } + + /** + * Prints a Period to a StringBuilder. + * + * @param buf the formatted period is appended to this buffer + * @param period the period to format, not null + */ + public void printTo(StringBuilder buf, Period period) { + checkPrinter(); + checkPeriod(period); + getPrinter().printTo(buf, period, iLocale); + } + + /** + * Prints a Period to a Writer. + * + * @param out the formatted period is written out + * @param period the period to format, not null + * @throws IOException if method fails + */ + public void printTo(Writer out, Period period) throws IOException { + checkPrinter(); + checkPeriod(period); + getPrinter().printTo(out, period, iLocale); + } + + /** + * Prints a Period to a new String. + * + * @param period the period to format, not null + * @return the printed result + */ + public String print(Period period) { + checkPrinter(); + checkPeriod(period); + PeriodPrinter printer = getPrinter(); + StringBuilder buf = new StringBuilder(printer.calculatePrintedLength(period, iLocale)); + printer.printTo(buf, period, iLocale); + return buf.toString(); + } + + /** + * Parses a period from the given text, at the given position, saving the + * result into the fields of the given ReadWritablePeriod. If the parse + * succeeds, the return value is the new text position. Note that the parse + * may succeed without fully reading the text. + * The parse type of the formatter is not used by this method. + * If it fails, the return value is negative, but the period may still be + * modified. To determine the position where the parse failed, apply the + * one's complement operator (~) on the return value. + * + * @param period a period that will be modified + * @param text text to parse + * @param position position to start parsing from + * @return new position, if negative, parse failed. Apply complement + * operator (~) to get position of failure + * @throws IllegalArgumentException if any field is out of range + */ + public int parseInto(PeriodAmount period, String text, int position) { + checkParser(); + checkPeriodAmount(period); + return getParser().parseInto(period, text, position, iLocale); + } + + /** + * Checks whether parsing is supported. + * + * @throws UnsupportedOperationException if parsing is not supported + */ + private void checkParser() { + if (iParser == null) { + throw new UnsupportedOperationException("Parsing not supported"); + } + } + + /** + * Checks whether printing is supported. + * + * @throws UnsupportedOperationException if printing is not supported + */ + private void checkPrinter() { + if (iPrinter == null) { + throw new UnsupportedOperationException("Printing not supported"); + } + } + + /** + * Checks whether the period is non-null. + * + * @throws IllegalArgumentException if the period is null + */ + private void checkPeriod(Period period) { + if (period == null) { + throw new IllegalArgumentException("Period must not be null"); + } + } + + /** + * Checks whether the period amount is non-null. + * + * @throws IllegalArgumentException if the period amount is null + */ + private void checkPeriodAmount(PeriodAmount period) { + if (period == null) { + throw new IllegalArgumentException("PeriodAmount must not be null"); + } + } +} diff --git a/src/main/java/org/xbib/time/format/PeriodFormatterBuilder.java b/src/main/java/org/xbib/time/format/PeriodFormatterBuilder.java new file mode 100644 index 0000000..59f1d4f --- /dev/null +++ b/src/main/java/org/xbib/time/format/PeriodFormatterBuilder.java @@ -0,0 +1,1778 @@ +package org.xbib.time.format; + +import java.io.IOException; +import java.io.Writer; +import java.time.Period; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Pattern; + + +/** + * Factory that creates complex instances of PeriodFormatter via method calls. + *

+ * Period formatting is performed by the {@link PeriodFormatter} class. + * Three classes provide factory methods to create formatters, and this is one. + * The others are {@link PeriodFormat} and {@link ISOPeriodFormat}. + *

+ * PeriodFormatterBuilder is used for constructing formatters which are then + * used to print or parse. The formatters are built by appending specific fields + * or other formatters to an instance of this builder. + *

+ * For example, a formatter that prints years and months, like "15 years and 8 months", + * can be constructed as follows: + *

+ * PeriodFormatter yearsAndMonths = new PeriodFormatterBuilder()
+ *     .appendYears()
+ *     .appendSuffix(" year", " years")
+ *     .appendSeparator(" and ")
+ *     .appendMonths()
+ *     .appendSuffix(" month", " months")
+ *     .toFormatter();
+ * 
+ *

+ * PeriodFormatterBuilder itself is mutable and not thread-safe, but the + * formatters that it builds are thread-safe and immutable. + */ +public class PeriodFormatterBuilder { + + private static final ConcurrentMap PATTERNS = new ConcurrentHashMap<>(); + + private int iMinPrintedDigits; + private int iMaxParsedDigits; + private boolean iRejectSignedValues; + + private PeriodFieldAffix iPrefix; + + // List of Printers and Parsers used to build a final formatter. + private List iElementPairs; + /** + * Set to true if the formatter is not a printer. + */ + private boolean iNotPrinter; + /** + * Set to true if the formatter is not a parser. + */ + private boolean iNotParser; + + // Last PeriodFormatter appended of each field type. + //private FieldFormatter[] iFieldFormatters; + + public PeriodFormatterBuilder() { + clear(); + } + + private static PeriodFormatter toFormatter(List elementPairs, boolean notPrinter, boolean notParser) { + if (notPrinter && notParser) { + throw new IllegalStateException("Builder has created neither a printer nor a parser"); + } + int size = elementPairs.size(); + if (size >= 2 && elementPairs.get(0) instanceof Separator) { + Separator sep = (Separator) elementPairs.get(0); + if (sep.iAfterParser == null && sep.iAfterPrinter == null) { + PeriodFormatter f = toFormatter(elementPairs.subList(2, size), notPrinter, notParser); + sep = sep.finish(f.getPrinter(), f.getParser()); + return new PeriodFormatter(sep, sep); + } + } + Object[] comp = createComposite(elementPairs); + if (notPrinter) { + return new PeriodFormatter(null, (PeriodParser) comp[1]); + } else if (notParser) { + return new PeriodFormatter((PeriodPrinter) comp[0], null); + } else { + return new PeriodFormatter((PeriodPrinter) comp[0], (PeriodParser) comp[1]); + } + } + + private static Object[] createComposite(List elementPairs) { + switch (elementPairs.size()) { + case 0: + return new Object[]{Literal.EMPTY, Literal.EMPTY}; + case 1: + return new Object[]{elementPairs.get(0), elementPairs.get(1)}; + default: + Composite comp = new Composite(elementPairs); + return new Object[]{comp, comp}; + } + } + + /** + * Constructs a PeriodFormatter using all the appended elements. + *

+ * This is the main method used by applications at the end of the build + * process to create a usable formatter. + *

+ * Once this method has been called, the builder is in an invalid state. + *

+ * The returned formatter may not support both printing and parsing. + * The methods {@link PeriodFormatter#isPrinter()} and + * {@link PeriodFormatter#isParser()} will help you determine the state + * of the formatter. + * + * @return the newly created formatter + * @throws IllegalStateException if the builder can produce neither a printer nor a parser + */ + public PeriodFormatter toFormatter() { + PeriodFormatter formatter = toFormatter(iElementPairs, iNotPrinter, iNotParser); + return formatter; + } + + /** + * Internal method to create a PeriodPrinter instance using all the + * appended elements. + *

+ * Most applications will not use this method. + * If you want a printer in an application, call {@link #toFormatter()} + * and just use the printing API. + *

+ * Subsequent changes to this builder do not affect the returned printer. + * + * @return the newly created printer, null if builder cannot create a printer + */ + public PeriodPrinter toPrinter() { + if (iNotPrinter) { + return null; + } + return toFormatter().getPrinter(); + } + + /** + * Internal method to create a PeriodParser instance using all the + * appended elements. + *

+ * Most applications will not use this method. + * If you want a printer in an application, call {@link #toFormatter()} + * and just use the printing API. + *

+ * Subsequent changes to this builder do not affect the returned parser. + * + * @return the newly created parser, null if builder cannot create a parser + */ + public PeriodParser toParser() { + if (iNotParser) { + return null; + } + return toFormatter().getParser(); + } + + /** + * Clears out all the appended elements, allowing this builder to be reused. + */ + public void clear() { + iMinPrintedDigits = 1; + //iPrintZeroSetting = PRINT_ZERO_RARELY_LAST; + iMaxParsedDigits = 10; + iRejectSignedValues = false; + iPrefix = null; + if (iElementPairs == null) { + iElementPairs = new ArrayList(); + } else { + iElementPairs.clear(); + } + iNotPrinter = false; + iNotParser = false; + } + + /** + * Appends another formatter. + * @param formatter formatter + * @return this PeriodFormatterBuilder + */ + public PeriodFormatterBuilder append(PeriodFormatter formatter) { + if (formatter == null) { + throw new IllegalArgumentException("No formatter supplied"); + } + clearPrefix(); + append0(formatter.getPrinter(), formatter.getParser()); + return this; + } + + /** + * Appends a printer parser pair. + *

+ * Either the printer or the parser may be null, in which case the builder will + * be unable to produce a parser or printer repectively. + * + * @param printer appends a printer to the builder, null if printing is not supported + * @param parser appends a parser to the builder, null if parsing is not supported + * @return this PeriodFormatterBuilder + * @throws IllegalArgumentException if both the printer and parser are null + */ + public PeriodFormatterBuilder append(PeriodPrinter printer, PeriodParser parser) { + if (printer == null && parser == null) { + throw new IllegalArgumentException("No printer or parser supplied"); + } + clearPrefix(); + append0(printer, parser); + return this; + } + + /** + * Instructs the printer to emit specific text, and the parser to expect it. + * The parser is case-insensitive. + * @param text text + * @return this PeriodFormatterBuilder + * @throws IllegalArgumentException if text is null + */ + public PeriodFormatterBuilder appendLiteral(String text) { + if (text == null) { + throw new IllegalArgumentException("Literal must not be null"); + } + clearPrefix(); + Literal literal = new Literal(text); + append0(literal, literal); + return this; + } + + /** + * Set the minimum digits printed for the next and following appended + * fields. By default, the minimum digits printed is one. If the field value + * is zero, it is not printed unless a printZero rule is applied. + * @param minDigits min digits + * @return this PeriodFormatterBuilder + */ + public PeriodFormatterBuilder minimumPrintedDigits(int minDigits) { + iMinPrintedDigits = minDigits; + return this; + } + + /** + * Set the maximum digits parsed for the next and following appended + * fields. By default, the maximum digits parsed is ten. + * @param maxDigits max digits + * @return this PeriodFormatterBuilder + */ + public PeriodFormatterBuilder maximumParsedDigits(int maxDigits) { + iMaxParsedDigits = maxDigits; + return this; + } + + /** + * Reject signed values when parsing the next and following appended fields. + * @param v flag + * @return this PeriodFormatterBuilder + */ + public PeriodFormatterBuilder rejectSignedValues(boolean v) { + iRejectSignedValues = v; + return this; + } + + /** + * Append a field prefix which applies only to the next appended field. If + * the field is not printed, neither is the prefix. + * + * @param text text to print before field only if field is printed + * @return this PeriodFormatterBuilder + * @see #appendSuffix + */ + public PeriodFormatterBuilder appendPrefix(String text) { + if (text == null) { + throw new IllegalArgumentException(); + } + return appendPrefix(new SimpleAffix(text)); + } + + /** + * Append a field prefix which applies only to the next appended field. If + * the field is not printed, neither is the prefix. + *

+ * During parsing, the singular and plural versions are accepted whether + * or not the actual value matches plurality. + * + * @param singularText text to print if field value is one + * @param pluralText text to print if field value is not one + * @return this PeriodFormatterBuilder + * @see #appendSuffix + */ + public PeriodFormatterBuilder appendPrefix(String singularText, + String pluralText) { + if (singularText == null || pluralText == null) { + throw new IllegalArgumentException(); + } + return appendPrefix(new PluralAffix(singularText, pluralText)); + } + + /** + * Append a field prefix which applies only to the next appended field. + * If the field is not printed, neither is the prefix. + *

+ * The value is converted to String. During parsing, the prefix is selected based + * on the match with the regular expression. The index of the first regular + * expression that matches value converted to String nominates the prefix. If + * none of the regular expressions match the value converted to String then the + * last prefix is selected. + *

+ * An example usage for English might look like this: + *

+     * appendPrefix(new String[] { "ˆ1$", ".*" },
+     * new String[] { " year", " years" })
+     * 
+ *

+ * Please note that for languages with simple mapping (singular and plural prefix + * only - like the one above) the {@link #appendPrefix(String, String)} method + * will produce in a slightly faster formatter and that + * {@link #appendPrefix(String[], String[])} method should be only used when the + * mapping between values and prefixes is more complicated than the difference between + * singular and plural. + * + * @param regularExpressions an array of regular expressions, at least one + * element, length has to match the length of prefixes parameter + * @param prefixes an array of prefixes, at least one element, length has to + * match the length of regularExpressions parameter + * @return this PeriodFormatterBuilder + * @throws IllegalStateException if no field exists to append to + * @see #appendPrefix + * @since 2.5 + */ + public PeriodFormatterBuilder appendPrefix(String[] regularExpressions, String[] prefixes) { + if (regularExpressions == null || prefixes == null || + regularExpressions.length < 1 || regularExpressions.length != prefixes.length) { + throw new IllegalArgumentException(); + } + return appendPrefix(new RegExAffix(regularExpressions, prefixes)); + } + + /** + * Append a field prefix which applies only to the next appended field. If + * the field is not printed, neither is the prefix. + * + * @param prefix custom prefix + * @return this PeriodFormatterBuilder + * @see #appendSuffix + */ + private PeriodFormatterBuilder appendPrefix(PeriodFieldAffix prefix) { + if (prefix == null) { + throw new IllegalArgumentException(); + } + if (iPrefix != null) { + prefix = new CompositeAffix(iPrefix, prefix); + } + iPrefix = prefix; + return this; + } + + /** + * Instruct the printer to emit an integer years field, if supported. + *

+ * The number of printed and parsed digits can be controlled using + * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}. + * + * @return this PeriodFormatterBuilder + */ + public PeriodFormatterBuilder appendYears() { + appendField(ChronoUnit.YEARS); + return this; + } + + /** + * Instruct the printer to emit an integer months field, if supported. + *

+ * The number of printed and parsed digits can be controlled using + * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}. + * + * @return this PeriodFormatterBuilder + */ + public PeriodFormatterBuilder appendMonths() { + appendField(ChronoUnit.MONTHS); + return this; + } + + /** + * Instruct the printer to emit an integer weeks field, if supported. + *

+ * The number of printed and parsed digits can be controlled using + * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}. + * + * @return this PeriodFormatterBuilder + */ + public PeriodFormatterBuilder appendWeeks() { + appendField(ChronoUnit.WEEKS); + return this; + } + + /** + * Instruct the printer to emit an integer days field, if supported. + *

+ * The number of printed and parsed digits can be controlled using + * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}. + * + * @return this PeriodFormatterBuilder + */ + public PeriodFormatterBuilder appendDays() { + appendField(ChronoUnit.DAYS); + return this; + } + + /** + * Instruct the printer to emit an integer hours field, if supported. + *

+ * The number of printed and parsed digits can be controlled using + * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}. + * + * @return this PeriodFormatterBuilder + */ + public PeriodFormatterBuilder appendHours() { + appendField(ChronoUnit.HOURS); + return this; + } + + /** + * Instruct the printer to emit an integer minutes field, if supported. + *

+ * The number of printed and parsed digits can be controlled using + * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}. + * + * @return this PeriodFormatterBuilder + */ + public PeriodFormatterBuilder appendMinutes() { + appendField(ChronoUnit.MINUTES); + return this; + } + + /** + * Instruct the printer to emit an integer seconds field, if supported. + *

+ * The number of printed and parsed digits can be controlled using + * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}. + * + * @return this PeriodFormatterBuilder + */ + public PeriodFormatterBuilder appendSeconds() { + appendField(ChronoUnit.SECONDS); + return this; + } + + /** + * Instruct the printer to emit an integer millis field, if supported. + *

+ * The number of printed and parsed digits can be controlled using + * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}. + * + * @return this PeriodFormatterBuilder + */ + public PeriodFormatterBuilder appendMillis() { + appendField(ChronoUnit.MILLIS); + return this; + } + + private void appendField(ChronoUnit unit) { + appendField(unit, iMinPrintedDigits); + } + + private void appendField(ChronoUnit unit, int minPrinted) { + FieldFormatter field = new FieldFormatter(minPrinted, + iMaxParsedDigits, iRejectSignedValues, unit, iPrefix, null); + append0(field, field); + iPrefix = null; + } + + /** + * Append a field suffix which applies only to the last appended field. If + * the field is not printed, neither is the suffix. + * + * @param text text to print after field only if field is printed + * @return this PeriodFormatterBuilder + * @throws IllegalStateException if no field exists to append to + * @see #appendPrefix + */ + public PeriodFormatterBuilder appendSuffix(String text) { + if (text == null) { + throw new IllegalArgumentException(); + } + return appendSuffix(new SimpleAffix(text)); + } + + /** + * Append a field suffix which applies only to the last appended field. If + * the field is not printed, neither is the suffix. + *

+ * During parsing, the singular and plural versions are accepted whether or + * not the actual value matches plurality. + * + * @param singularText text to print if field value is one + * @param pluralText text to print if field value is not one + * @return this PeriodFormatterBuilder + * @throws IllegalStateException if no field exists to append to + * @see #appendPrefix + */ + public PeriodFormatterBuilder appendSuffix(String singularText, + String pluralText) { + if (singularText == null || pluralText == null) { + throw new IllegalArgumentException(); + } + return appendSuffix(new PluralAffix(singularText, pluralText)); + } + + /** + * Append a field suffix which applies only to the last appended field. + * If the field is not printed, neither is the suffix. + *

+ * The value is converted to String. During parsing, the suffix is selected based + * on the match with the regular expression. The index of the first regular + * expression that matches value converted to String nominates the suffix. If + * none of the regular expressions match the value converted to String then the + * last suffix is selected. + *

+ * An example usage for English might look like this: + *

+     * appendSuffix(new String[] { "ˆ1$", ".*" },
+     * new String[] { " year", " years" })
+     * 
+ * Please note that for languages with simple mapping (singular and plural suffix + * only - like the one above) the {@link #appendSuffix(String, String)} method + * will result in a slightly faster formatter and that + * {@link #appendSuffix(String[], String[])} method should be only used when the + * mapping between values and prefixes is more complicated than the difference between + * singular and plural. + * + * @param regularExpressions an array of regular expressions, at least one + * element, length has to match the length of suffixes parameter + * @param suffixes an array of suffixes, at least one element, length has to + * match the length of regularExpressions parameter + * @return this PeriodFormatterBuilder + * @throws IllegalStateException if no field exists to append to + * @see #appendPrefix + * @since 2.5 + */ + public PeriodFormatterBuilder appendSuffix(String[] regularExpressions, String[] suffixes) { + if (regularExpressions == null || suffixes == null || + regularExpressions.length < 1 || regularExpressions.length != suffixes.length) { + throw new IllegalArgumentException(); + } + return appendSuffix(new RegExAffix(regularExpressions, suffixes)); + } + + /** + * Append a field suffix which applies only to the last appended field. If + * the field is not printed, neither is the suffix. + * + * @param suffix custom suffix + * @return this PeriodFormatterBuilder + * @throws IllegalStateException if no field exists to append to + * @see #appendPrefix + */ + private PeriodFormatterBuilder appendSuffix(PeriodFieldAffix suffix) { + final Object originalPrinter; + final Object originalParser; + if (iElementPairs.size() > 0) { + originalPrinter = iElementPairs.get(iElementPairs.size() - 2); + originalParser = iElementPairs.get(iElementPairs.size() - 1); + } else { + originalPrinter = null; + originalParser = null; + } + + if (originalPrinter == null || originalParser == null || + originalPrinter != originalParser || + !(originalPrinter instanceof FieldFormatter)) { + throw new IllegalStateException("No field to apply suffix to"); + } + + clearPrefix(); + FieldFormatter newField = new FieldFormatter((FieldFormatter) originalPrinter, suffix); + iElementPairs.set(iElementPairs.size() - 2, newField); + iElementPairs.set(iElementPairs.size() - 1, newField); + return this; + } + + /** + * Append a separator, which is output if fields are printed both before + * and after the separator. + *

+ * For example, builder.appendDays().appendSeparator(",").appendHours() + * will only output the comma if both the days and hours fields are output. + *

+ * The text will be parsed case-insensitively. + *

+ * Note: appending a separator discontinues any further work on the latest + * appended field. + * + * @param text the text to use as a separator + * @return this PeriodFormatterBuilder + * @throws IllegalStateException if this separator follows a previous one + */ + public PeriodFormatterBuilder appendSeparator(String text) { + return appendSeparator(text, text, null, true, true); + } + + /** + * Append a separator, which is output only if fields are printed after the separator. + *

+ * For example, + * builder.appendDays().appendSeparatorIfFieldsAfter(",").appendHours() + * will only output the comma if the hours fields is output. + *

+ * The text will be parsed case-insensitively. + *

+ * Note: appending a separator discontinues any further work on the latest + * appended field. + * + * @param text the text to use as a separator + * @return this PeriodFormatterBuilder + * @throws IllegalStateException if this separator follows a previous one + */ + public PeriodFormatterBuilder appendSeparatorIfFieldsAfter(String text) { + return appendSeparator(text, text, null, false, true); + } + + /** + * Append a separator, which is output only if fields are printed before the separator. + *

+ * For example, + * builder.appendDays().appendSeparatorIfFieldsBefore(",").appendHours() + * will only output the comma if the days fields is output. + *

+ * The text will be parsed case-insensitively. + *

+ * Note: appending a separator discontinues any further work on the latest + * appended field. + * + * @param text the text to use as a separator + * @return this PeriodFormatterBuilder + * @throws IllegalStateException if this separator follows a previous one + */ + public PeriodFormatterBuilder appendSeparatorIfFieldsBefore(String text) { + return appendSeparator(text, text, null, true, false); + } + + public PeriodFormatterBuilder appendSeparator(String text, String finalText) { + return appendSeparator(text, finalText, null, true, true); + } + + /** + * Append a separator, which is output if fields are printed both before + * and after the separator. + *

+ * This method changes the separator depending on whether it is the last separator + * to be output. + *

+ * For example, + * builder.appendDays().appendSeparator(",", "&").appendHours() + * .appendSeparator(",", "&").appendMinutes() + * will output '1,2&3' if all three fields are output, '1&2' if two fields are output + * and '1' if just one field is output. + *

+ * The text will be parsed case-insensitively. + *

+ * Note: appending a separator discontinues any further work on the latest + * appended field. + * + * @param text the text to use as a separator + * @param finalText the text used used if this is the final separator to be printed + * @param variants set of text values which are also acceptable when parsed + * @return this PeriodFormatterBuilder + * @throws IllegalStateException if this separator follows a previous one + */ + public PeriodFormatterBuilder appendSeparator(String text, String finalText, + String[] variants) { + return appendSeparator(text, finalText, variants, true, true); + } + + private PeriodFormatterBuilder appendSeparator(String text, String finalText, + String[] variants, + boolean useBefore, boolean useAfter) { + if (text == null || finalText == null) { + throw new IllegalArgumentException(); + } + + clearPrefix(); + + // optimise zero formatter case + List pairs = iElementPairs; + if (pairs.size() == 0) { + if (useAfter && !useBefore) { + Separator separator = new Separator(text, finalText, variants, + Literal.EMPTY, Literal.EMPTY, false, true); + append0(separator, separator); + } + return this; + } + + // find the last separator added + int i; + Separator lastSeparator = null; + for (i = pairs.size(); --i >= 0; ) { + if (pairs.get(i) instanceof Separator) { + lastSeparator = (Separator) pairs.get(i); + pairs = pairs.subList(i + 1, pairs.size()); + break; + } + i--; // element pairs + } + + // merge formatters + if (lastSeparator != null && pairs.size() == 0) { + throw new IllegalStateException("Cannot have two adjacent separators"); + } else { + Object[] comp = createComposite(pairs); + pairs.clear(); + Separator separator = new Separator( + text, finalText, variants, + (PeriodPrinter) comp[0], (PeriodParser) comp[1], + useBefore, useAfter); + pairs.add(separator); + pairs.add(separator); + } + + return this; + } + + private void clearPrefix() throws IllegalStateException { + if (iPrefix != null) { + throw new IllegalStateException("Prefix not followed by field"); + } + iPrefix = null; + } + + private PeriodFormatterBuilder append0(PeriodPrinter printer, PeriodParser parser) { + iElementPairs.add(printer); + iElementPairs.add(parser); + iNotPrinter |= (printer == null); + iNotParser |= (parser == null); + return this; + } + + /** + * Defines a formatted field's prefix or suffix text. + * This can be used for fields such as 'n hours' or 'nH' or 'Hour:n'. + */ + interface PeriodFieldAffix { + + int calculatePrintedLength(int value); + + void printTo(StringBuilder buf, int value); + + void printTo(Writer out, int value) throws IOException; + + /** + * @return new position after parsing affix, or ~position of failure + */ + int parse(String periodStr, int position); + + /** + * @return position where affix starts, or original ~position if not found + */ + int scan(String periodStr, int position); + + /** + * @return a copy of array of affixes + */ + String[] getAffixes(); + + /** + * This method should be called only once. + * After first call consecutive calls to this methods will have no effect. + * Causes this affix to ignore a match (parse and scan + * methods) if there is an affix in the passed list that holds + * affix text which satisfy both following conditions: + * - the affix text is also a match + * - the affix text is longer than the match from this object + * + * @param affixesToIgnore affixes to ignore + */ + void finish(Set affixesToIgnore); + } + + /** + * An affix that can be ignored. + */ + abstract static class IgnorableAffix implements PeriodFieldAffix { + private volatile String[] iOtherAffixes; + + public void finish(Set periodFieldAffixesToIgnore) { + if (iOtherAffixes == null) { + // Calculate the shortest affix in this instance. + int shortestAffixLength = Integer.MAX_VALUE; + String shortestAffix = null; + for (String affix : getAffixes()) { + if (affix.length() < shortestAffixLength) { + shortestAffixLength = affix.length(); + shortestAffix = affix; + } + } + + // Pick only affixes that are longer than the shortest affix in this instance. + // This will reduce the number of parse operations and thus speed up the PeriodFormatter. + // also need to pick affixes that differ only in case (but not those that are identical) + Set affixesToIgnore = new HashSet(); + for (PeriodFieldAffix periodFieldAffixToIgnore : periodFieldAffixesToIgnore) { + if (periodFieldAffixToIgnore != null) { + for (String affixToIgnore : periodFieldAffixToIgnore.getAffixes()) { + if (affixToIgnore.length() > shortestAffixLength || + (affixToIgnore.equalsIgnoreCase(shortestAffix) && + !affixToIgnore.equals(shortestAffix))) { + affixesToIgnore.add(affixToIgnore); + } + } + } + } + iOtherAffixes = affixesToIgnore.toArray(new String[affixesToIgnore.size()]); + } + } + + /** + * Checks if there is a match among the other affixes (stored internally) + * that is longer than the passed value (textLength). + * + * @param textLength the length of the match + * @param periodStr the Period string that will be parsed + * @param position the position in the Period string at which the parsing should be started. + * @return true if the other affixes (stored internally) contain a match + * that is longer than the textLength parameter, false otherwise + */ + protected boolean matchesOtherAffix(int textLength, String periodStr, int position) { + if (iOtherAffixes != null) { + // ignore case when affix length differs + // match case when affix length is same + for (String affixToIgnore : iOtherAffixes) { + int textToIgnoreLength = affixToIgnore.length(); + if ((textLength < textToIgnoreLength && + periodStr.regionMatches(true, position, affixToIgnore, 0, textToIgnoreLength)) || + (textLength == textToIgnoreLength && + periodStr.regionMatches(false, position, affixToIgnore, 0, textToIgnoreLength))) { + return true; + } + } + } + return false; + } + } + + /** + * Implements an affix where the text does not vary by the amount. + */ + static class SimpleAffix extends IgnorableAffix { + private final String iText; + + SimpleAffix(String text) { + iText = text; + } + + public int calculatePrintedLength(int value) { + return iText.length(); + } + + public void printTo(StringBuilder buf, int value) { + buf.append(iText); + } + + public void printTo(Writer out, int value) throws IOException { + out.write(iText); + } + + public int parse(String periodStr, int position) { + String text = iText; + int textLength = text.length(); + if (periodStr.regionMatches(true, position, text, 0, textLength) && + !matchesOtherAffix(textLength, periodStr, position)) { + return position + textLength; + } + return ~position; + } + + public int scan(String periodStr, final int position) { + String text = iText; + int textLength = text.length(); + int sourceLength = periodStr.length(); + search: + for (int pos = position; pos < sourceLength; pos++) { + if (periodStr.regionMatches(true, pos, text, 0, textLength) && + !matchesOtherAffix(textLength, periodStr, pos)) { + return pos; + } + // Only allow number characters to be skipped in search of suffix. + switch (periodStr.charAt(pos)) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + case ',': + case '+': + case '-': + break; + default: + break search; + } + } + return ~position; + } + + public String[] getAffixes() { + return new String[]{iText}; + } + } + + /** + * Implements an affix where the text varies by the amount of the field. + * Only singular (1) and plural (not 1) are supported. + */ + private static class PluralAffix extends IgnorableAffix { + private final String iSingularText; + private final String iPluralText; + + PluralAffix(String singularText, String pluralText) { + iSingularText = singularText; + iPluralText = pluralText; + } + + public int calculatePrintedLength(int value) { + return (value == 1 ? iSingularText : iPluralText).length(); + } + + public void printTo(StringBuilder buf, int value) { + buf.append(value == 1 ? iSingularText : iPluralText); + } + + public void printTo(Writer out, int value) throws IOException { + out.write(value == 1 ? iSingularText : iPluralText); + } + + public int parse(String periodStr, int position) { + String text1 = iPluralText; + String text2 = iSingularText; + if (text1.length() < text2.length()) { + // Swap in order to match longer one first. + String temp = text1; + text1 = text2; + text2 = temp; + } + if (periodStr.regionMatches(true, position, text1, 0, text1.length()) && + !matchesOtherAffix(text1.length(), periodStr, position)) { + return position + text1.length(); + } + if (periodStr.regionMatches(true, position, text2, 0, text2.length()) && + !matchesOtherAffix(text2.length(), periodStr, position)) { + return position + text2.length(); + } + return ~position; + } + + public int scan(String periodStr, final int position) { + String text1 = iPluralText; + String text2 = iSingularText; + + if (text1.length() < text2.length()) { + // Swap in order to match longer one first. + String temp = text1; + text1 = text2; + text2 = temp; + } + + int textLength1 = text1.length(); + int textLength2 = text2.length(); + + int sourceLength = periodStr.length(); + for (int pos = position; pos < sourceLength; pos++) { + if (periodStr.regionMatches(true, pos, text1, 0, textLength1) && + !matchesOtherAffix(text1.length(), periodStr, pos)) { + return pos; + } + if (periodStr.regionMatches(true, pos, text2, 0, textLength2) && + !matchesOtherAffix(text2.length(), periodStr, pos)) { + return pos; + } + } + return ~position; + } + + public String[] getAffixes() { + return new String[]{iSingularText, iPluralText}; + } + } + + /** + * Implements an affix where the text varies by the amount of the field. + * Different amounts are supported based on the provided parameters. + */ + private static class RegExAffix extends IgnorableAffix { + private static final Comparator LENGTH_DESC_COMPARATOR = new Comparator() { + public int compare(String o1, String o2) { + return o2.length() - o1.length(); + } + }; + + private final String[] iSuffixes; + private final Pattern[] iPatterns; + + // The parse method has to iterate over the suffixes from the longest one to the shortest one + // Otherwise it might consume not enough characters. + private final String[] iSuffixesSortedDescByLength; + + RegExAffix(String[] regExes, String[] texts) { + iSuffixes = texts.clone(); + iPatterns = new Pattern[regExes.length]; + for (int i = 0; i < regExes.length; i++) { + Pattern pattern = PATTERNS.get(regExes[i]); + if (pattern == null) { + pattern = Pattern.compile(regExes[i]); + Pattern p = PATTERNS.putIfAbsent(regExes[i], pattern); + } + iPatterns[i] = pattern; + } + iSuffixesSortedDescByLength = iSuffixes.clone(); + Arrays.sort(iSuffixesSortedDescByLength, LENGTH_DESC_COMPARATOR); + } + + private int selectSuffixIndex(int value) { + String valueString = String.valueOf(value); + for (int i = 0; i < iPatterns.length; i++) { + if (iPatterns[i].matcher(valueString).matches()) { + return i; + } + } + return iPatterns.length - 1; + } + + @Override + public int calculatePrintedLength(int value) { + return iSuffixes[selectSuffixIndex(value)].length(); + } + + @Override + public void printTo(StringBuilder buf, int value) { + buf.append(iSuffixes[selectSuffixIndex(value)]); + } + + @Override + public void printTo(Writer out, int value) throws IOException { + out.write(iSuffixes[selectSuffixIndex(value)]); + } + + @Override + public int parse(String periodStr, int position) { + for (String text : iSuffixesSortedDescByLength) { + if (periodStr.regionMatches(true, position, text, 0, text.length()) && + !matchesOtherAffix(text.length(), periodStr, position)) { + return position + text.length(); + } + } + return ~position; + } + + @Override + public int scan(String periodStr, final int position) { + int sourceLength = periodStr.length(); + for (int pos = position; pos < sourceLength; pos++) { + for (String text : iSuffixesSortedDescByLength) { + if (periodStr.regionMatches(true, pos, text, 0, text.length()) && + !matchesOtherAffix(text.length(), periodStr, pos)) { + return pos; + } + } + } + return ~position; + } + + @Override + public String[] getAffixes() { + return iSuffixes.clone(); + } + } + + /** + * Builds a composite affix by merging two other affix implementations. + */ + private static class CompositeAffix extends IgnorableAffix { + private final PeriodFieldAffix iLeft; + private final PeriodFieldAffix iRight; + private final String[] iLeftRightCombinations; + + CompositeAffix(PeriodFieldAffix left, PeriodFieldAffix right) { + iLeft = left; + iRight = right; + + // We need to construct all possible combinations of left and right. + // We are doing it once in constructor so that getAffixes() is quicker. + Set result = new HashSet(); + for (String leftText : iLeft.getAffixes()) { + for (String rightText : iRight.getAffixes()) { + result.add(leftText + rightText); + } + } + iLeftRightCombinations = result.toArray(new String[result.size()]); + } + + @Override + public int calculatePrintedLength(int value) { + return iLeft.calculatePrintedLength(value) + + iRight.calculatePrintedLength(value); + } + + @Override + public void printTo(StringBuilder buf, int value) { + iLeft.printTo(buf, value); + iRight.printTo(buf, value); + } + + @Override + public void printTo(Writer out, int value) throws IOException { + iLeft.printTo(out, value); + iRight.printTo(out, value); + } + + @Override + public int parse(String periodStr, int position) { + int pos = iLeft.parse(periodStr, position); + if (pos >= 0) { + pos = iRight.parse(periodStr, pos); + if (pos >= 0 && matchesOtherAffix(parse(periodStr, pos) - pos, periodStr, position)) { + return ~position; + } + } + return pos; + } + + @Override + public int scan(String periodStr, final int position) { + int leftPosition = iLeft.scan(periodStr, position); + if (leftPosition >= 0) { + int rightPosition = iRight.scan(periodStr, iLeft.parse(periodStr, leftPosition)); + if (!(rightPosition >= 0 && matchesOtherAffix(iRight.parse(periodStr, rightPosition) - + leftPosition, periodStr, position))) { + if (leftPosition > 0) { + return leftPosition; + } else { + return rightPosition; + } + } + } + return ~position; + } + + @Override + public String[] getAffixes() { + return iLeftRightCombinations.clone(); + } + } + + /** + * Formats the numeric value of a field, potentially with prefix/suffix. + */ + private static class FieldFormatter implements PeriodPrinter, PeriodParser { + private final int iMinPrintedDigits; + //private final int iPrintZeroSetting; + private final int iMaxParsedDigits; + private final boolean iRejectSignedValues; + + /** + * The index of the field type, 0=year, etc. + */ + private final ChronoUnit unit; + /** + * The array of the latest formatter added for each type. + * This is shared between all the field formatters in a formatter. + */ + //private final FieldFormatter[] iFieldFormatters; + + private final PeriodFieldAffix iPrefix; + private final PeriodFieldAffix iSuffix; + + FieldFormatter(int minPrintedDigits, + int maxParsedDigits, boolean rejectSignedValues, + ChronoUnit chronoUnit, + PeriodFieldAffix prefix, PeriodFieldAffix suffix) { + iMinPrintedDigits = minPrintedDigits; + iMaxParsedDigits = maxParsedDigits; + iRejectSignedValues = rejectSignedValues; + this.unit = chronoUnit; + iPrefix = prefix; + iSuffix = suffix; + } + + FieldFormatter(FieldFormatter field, PeriodFieldAffix suffix) { + iMinPrintedDigits = field.iMinPrintedDigits; + iMaxParsedDigits = field.iMaxParsedDigits; + iRejectSignedValues = field.iRejectSignedValues; + this.unit = field.unit; + iPrefix = field.iPrefix; + if (field.iSuffix != null) { + suffix = new CompositeAffix(field.iSuffix, suffix); + } + iSuffix = suffix; + } + + public void finish(FieldFormatter[] fieldFormatters) { + Set prefixesToIgnore = new HashSet<>(); + Set suffixesToIgnore = new HashSet<>(); + for (FieldFormatter fieldFormatter : fieldFormatters) { + if (fieldFormatter != null && !this.equals(fieldFormatter)) { + prefixesToIgnore.add(fieldFormatter.iPrefix); + suffixesToIgnore.add(fieldFormatter.iSuffix); + } + } + // if we have a prefix then allow ignore behaviour + if (iPrefix != null) { + iPrefix.finish(prefixesToIgnore); + } + // if we have a suffix then allow ignore behaviour + if (iSuffix != null) { + iSuffix.finish(suffixesToIgnore); + } + } + + public int countFieldsToPrint(Period period, int stopAt, Locale locale) { + if (stopAt <= 0) { + return 0; + } + if (getFieldValue(period) != Long.MAX_VALUE) { + return 1; + } + return 0; + } + + public int calculatePrintedLength(Period period, Locale locale) { + long valueLong = getFieldValue(period); + if (valueLong == Long.MAX_VALUE) { + return 0; + } + + int sum = Math.max(FormatUtils.calculateDigitCount(valueLong), iMinPrintedDigits); + int value = (int) valueLong; + + if (iPrefix != null) { + sum += iPrefix.calculatePrintedLength(value); + } + if (iSuffix != null) { + sum += iSuffix.calculatePrintedLength(value); + } + + return sum; + } + + public void printTo(StringBuilder buf, Period period, Locale locale) { + long valueLong = getFieldValue(period); + if (valueLong == Long.MAX_VALUE) { + return; + } + int value = (int) valueLong; + if (iPrefix != null) { + iPrefix.printTo(buf, value); + } + int minDigits = iMinPrintedDigits; + if (minDigits <= 1) { + FormatUtils.appendUnpaddedInteger(buf, value); + } else { + FormatUtils.appendPaddedInteger(buf, value, minDigits); + } + if (iSuffix != null) { + iSuffix.printTo(buf, value); + } + } + + public void printTo(Writer out, Period period, Locale locale) throws IOException { + long valueLong = getFieldValue(period); + if (valueLong == Long.MAX_VALUE) { + return; + } + int value = (int) valueLong; + if (iPrefix != null) { + iPrefix.printTo(out, value); + } + int minDigits = iMinPrintedDigits; + if (minDigits <= 1) { + FormatUtils.writeUnpaddedInteger(out, value); + } else { + FormatUtils.writePaddedInteger(out, value, minDigits); + } + if (iSuffix != null) { + iSuffix.printTo(out, value); + } + } + + public int parseInto(PeriodAmount period, String text, int position, Locale locale) { + + if (position >= text.length()) { + return ~position; + } + + if (iPrefix != null) { + position = iPrefix.parse(text, position); + if (position < 0) { + return position; + } + } + int suffixPos = -1; + int limit; + limit = Math.min(iMaxParsedDigits, text.length() - position); + int length = 0; + int fractPos = -1; + boolean hasDigits = false; + boolean negative = false; + while (length < limit) { + char c = text.charAt(position + length); + // leading sign + if (length == 0 && (c == '-' || c == '+') && !iRejectSignedValues) { + negative = c == '-'; + + // Next character must be a digit. + if (length + 1 >= limit || + (c = text.charAt(position + length + 1)) < '0' || c > '9') { + break; + } + + if (negative) { + length++; + } else { + // Skip the '+' for parseInt to succeed. + position++; + } + // Expand the limit to disregard the sign character. + limit = Math.min(limit + 1, text.length() - position); + continue; + } + // main number + if (c >= '0' && c <= '9') { + hasDigits = true; + } else { + break; + } + length++; + } + + if (!hasDigits) { + return ~position; + } + setFieldValue(period, unit, parseInt(text, position, length)); + position += length; + if (position >= 0 && iSuffix != null) { + position = iSuffix.parse(text, position); + } + return position; + } + + /** + * @param text text to parse + * @param position position in text + * @param length exact count of characters to parse + * @return parsed int value + */ + private int parseInt(String text, int position, int length) { + if (length >= 10) { + return Integer.parseInt(text.substring(position, position + length)); + } + if (length <= 0) { + return 0; + } + int value = text.charAt(position++); + length--; + boolean negative; + if (value == '-') { + if (--length < 0) { + return 0; + } + negative = true; + value = text.charAt(position++); + } else { + negative = false; + } + value -= '0'; + while (length-- > 0) { + value = ((value << 3) + (value << 1)) + text.charAt(position++) - '0'; + } + return negative ? -value : value; + } + + /** + * @return Long.MAX_VALUE if nothing to print, otherwise value + */ + long getFieldValue(Period period) { + long value; + switch (unit) { + default: + return Long.MAX_VALUE; + case YEARS: + value = period.get(ChronoUnit.YEARS); + break; + case MONTHS: + value = period.get(ChronoUnit.MONTHS); + break; + case WEEKS: + value = period.get(ChronoUnit.WEEKS); + break; + case DAYS: + value = period.get(ChronoUnit.DAYS); + break; + case HOURS: + value = period.get(ChronoUnit.HOURS); + break; + case MINUTES: + value = period.get(ChronoUnit.MINUTES); + break; + case SECONDS: + value = period.get(ChronoUnit.SECONDS); + break; + case MILLIS: + value = period.get(ChronoUnit.MILLIS); + break; + } + + return value; + } + + void setFieldValue(PeriodAmount period, ChronoUnit field, long value) { + switch (field) { + default: + break; + case YEARS: + period.set(ChronoUnit.YEARS, value); + break; + case MONTHS: + period.set(ChronoUnit.MONTHS, value); + break; + case WEEKS: + period.set(ChronoUnit.WEEKS, value); + break; + case DAYS: + period.set(ChronoUnit.DAYS, value); + break; + case HOURS: + period.set(ChronoUnit.HOURS, value); + break; + case MINUTES: + period.set(ChronoUnit.MINUTES, value); + break; + case SECONDS: + period.set(ChronoUnit.SECONDS, value); + break; + case MILLIS: + period.set(ChronoUnit.MILLIS, value); + break; + } + } + + ChronoUnit getFieldType() { + return unit; + } + } + + /** + * Handles a simple literal piece of text. + */ + private static class Literal implements PeriodPrinter, PeriodParser { + static final Literal EMPTY = new Literal(""); + private final String iText; + + Literal(String text) { + iText = text; + } + + public int countFieldsToPrint(Period period, int stopAt, Locale locale) { + return 0; + } + + public int calculatePrintedLength(Period period, Locale locale) { + return iText.length(); + } + + public void printTo(StringBuilder buf, Period period, Locale locale) { + buf.append(iText); + } + + public void printTo(Writer out, Period period, Locale locale) throws IOException { + out.write(iText); + } + + public int parseInto(PeriodAmount period, String periodStr, + int position, Locale locale) { + if (periodStr.regionMatches(true, position, iText, 0, iText.length())) { + return position + iText.length(); + } + return ~position; + } + } + + /** + * Handles a separator, that splits the fields into multiple parts. + * For example, the 'T' in the ISO8601 standard. + */ + private static class Separator implements PeriodPrinter, PeriodParser { + private final String iText; + private final String iFinalText; + private final String[] iParsedForms; + + private final boolean iUseBefore; + private final boolean iUseAfter; + + private final PeriodPrinter iBeforePrinter; + private final PeriodParser iBeforeParser; + private volatile PeriodPrinter iAfterPrinter; + private volatile PeriodParser iAfterParser; + + Separator(String text, String finalText, String[] variants, + PeriodPrinter beforePrinter, PeriodParser beforeParser, + boolean useBefore, boolean useAfter) { + iText = text; + iFinalText = finalText; + + if ((finalText == null || text.equals(finalText)) && + (variants == null || variants.length == 0)) { + + iParsedForms = new String[]{text}; + } else { + // Filter and reverse sort the parsed forms. + TreeSet parsedSet = new TreeSet(String.CASE_INSENSITIVE_ORDER); + parsedSet.add(text); + parsedSet.add(finalText); + if (variants != null) { + for (int i = variants.length; --i >= 0; ) { + parsedSet.add(variants[i]); + } + } + ArrayList parsedList = new ArrayList(parsedSet); + Collections.reverse(parsedList); + iParsedForms = parsedList.toArray(new String[parsedList.size()]); + } + + iBeforePrinter = beforePrinter; + iBeforeParser = beforeParser; + iUseBefore = useBefore; + iUseAfter = useAfter; + } + + public int countFieldsToPrint(Period period, int stopAt, Locale locale) { + int sum = iBeforePrinter.countFieldsToPrint(period, stopAt, locale); + if (sum < stopAt) { + sum += iAfterPrinter.countFieldsToPrint(period, stopAt, locale); + } + return sum; + } + + public int calculatePrintedLength(Period period, Locale locale) { + PeriodPrinter before = iBeforePrinter; + PeriodPrinter after = iAfterPrinter; + + int sum = before.calculatePrintedLength(period, locale) + + after.calculatePrintedLength(period, locale); + + if (iUseBefore) { + if (before.countFieldsToPrint(period, 1, locale) > 0) { + if (iUseAfter) { + int afterCount = after.countFieldsToPrint(period, 2, locale); + if (afterCount > 0) { + sum += (afterCount > 1 ? iText : iFinalText).length(); + } + } else { + sum += iText.length(); + } + } + } else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) { + sum += iText.length(); + } + + return sum; + } + + public void printTo(StringBuilder buf, Period period, Locale locale) { + PeriodPrinter before = iBeforePrinter; + PeriodPrinter after = iAfterPrinter; + + before.printTo(buf, period, locale); + if (iUseBefore) { + if (before.countFieldsToPrint(period, 1, locale) > 0) { + if (iUseAfter) { + int afterCount = after.countFieldsToPrint(period, 2, locale); + if (afterCount > 0) { + buf.append(afterCount > 1 ? iText : iFinalText); + } + } else { + buf.append(iText); + } + } + } else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) { + buf.append(iText); + } + after.printTo(buf, period, locale); + } + + public void printTo(Writer out, Period period, Locale locale) throws IOException { + PeriodPrinter before = iBeforePrinter; + PeriodPrinter after = iAfterPrinter; + + before.printTo(out, period, locale); + if (iUseBefore) { + if (before.countFieldsToPrint(period, 1, locale) > 0) { + if (iUseAfter) { + int afterCount = after.countFieldsToPrint(period, 2, locale); + if (afterCount > 0) { + out.write(afterCount > 1 ? iText : iFinalText); + } + } else { + out.write(iText); + } + } + } else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) { + out.write(iText); + } + after.printTo(out, period, locale); + } + + public int parseInto(PeriodAmount period, String periodStr, + int position, Locale locale) { + int oldPos = position; + position = iBeforeParser.parseInto(period, periodStr, position, locale); + + if (position < 0) { + return position; + } + + boolean found = false; + int parsedFormLength = -1; + if (position > oldPos) { + // Consume this separator. + for (String parsedForm : iParsedForms) { + if ((parsedForm == null || parsedForm.length() == 0) || + periodStr.regionMatches(true, position, parsedForm, 0, parsedForm.length())) { + parsedFormLength = (parsedForm == null ? 0 : parsedForm.length()); + position += parsedFormLength; + found = true; + break; + } + } + } + oldPos = position; + position = iAfterParser.parseInto(period, periodStr, position, locale); + if (position < 0) { + return position; + } + if (found && position == oldPos && parsedFormLength > 0) { + // Separator should not have been supplied. + return ~oldPos; + } + + if (position > oldPos && !found && !iUseBefore) { + // Separator was required. + return ~oldPos; + } + + return position; + } + + Separator finish(PeriodPrinter afterPrinter, PeriodParser afterParser) { + iAfterPrinter = afterPrinter; + iAfterParser = afterParser; + return this; + } + } + + /** + * Composite implementation that merges other fields to create a full pattern. + */ + private static class Composite implements PeriodPrinter, PeriodParser { + + private final PeriodPrinter[] iPrinters; + private final PeriodParser[] iParsers; + + Composite(List elementPairs) { + List printerList = new ArrayList<>(); + List parserList = new ArrayList<>(); + + decompose(elementPairs, printerList, parserList); + + if (printerList.size() <= 0) { + iPrinters = null; + } else { + iPrinters = printerList.toArray(new PeriodPrinter[printerList.size()]); + } + + if (parserList.size() <= 0) { + iParsers = null; + } else { + iParsers = parserList.toArray(new PeriodParser[parserList.size()]); + } + } + + public int countFieldsToPrint(Period period, int stopAt, Locale locale) { + int sum = 0; + PeriodPrinter[] printers = iPrinters; + for (int i = printers.length; sum < stopAt && --i >= 0; ) { + sum += printers[i].countFieldsToPrint(period, Integer.MAX_VALUE, locale); + } + return sum; + } + + public int calculatePrintedLength(Period period, Locale locale) { + int sum = 0; + PeriodPrinter[] printers = iPrinters; + for (int i = printers.length; --i >= 0; ) { + sum += printers[i].calculatePrintedLength(period, locale); + } + return sum; + } + + public void printTo(StringBuilder buf, Period period, Locale locale) { + PeriodPrinter[] printers = iPrinters; + int len = printers.length; + for (PeriodPrinter printer : printers) { + printer.printTo(buf, period, locale); + } + } + + public void printTo(Writer out, Period period, Locale locale) throws IOException { + PeriodPrinter[] printers = iPrinters; + int len = printers.length; + for (PeriodPrinter printer : printers) { + printer.printTo(out, period, locale); + } + } + + public int parseInto(PeriodAmount period, String periodStr, int position, Locale locale) { + PeriodParser[] parsers = iParsers; + if (parsers == null) { + throw new UnsupportedOperationException(); + } + + int len = parsers.length; + for (int i = 0; i < len && position >= 0; i++) { + position = parsers[i].parseInto(period, periodStr, position, locale); + } + return position; + } + + private void decompose(List elementPairs, List printerList, List parserList) { + int size = elementPairs.size(); + for (int i = 0; i < size; i += 2) { + Object element = elementPairs.get(i); + if (element instanceof PeriodPrinter) { + if (element instanceof Composite) { + addArrayToList(printerList, ((Composite) element).iPrinters); + } else { + printerList.add(element); + } + } + + element = elementPairs.get(i + 1); + if (element instanceof PeriodParser) { + if (element instanceof Composite) { + addArrayToList(parserList, ((Composite) element).iParsers); + } else { + parserList.add(element); + } + } + } + } + + private void addArrayToList(List list, Object[] array) { + if (array != null) { + Collections.addAll(list, array); + } + } + } +} diff --git a/src/main/java/org/xbib/time/format/PeriodParser.java b/src/main/java/org/xbib/time/format/PeriodParser.java new file mode 100644 index 0000000..86706b3 --- /dev/null +++ b/src/main/java/org/xbib/time/format/PeriodParser.java @@ -0,0 +1,32 @@ +package org.xbib.time.format; + +import java.util.Locale; + +/** + * Internal interface for parsing textual representations of time periods. + * Application users will rarely use this class directly. Instead, you + * will use one of the factory classes to create a {@link PeriodFormatter}. + * The factory classes are {@link PeriodFormat} and {@link ISOPeriodFormat}. + */ +public interface PeriodParser { + + /** + * Parses a period from the given text, at the given position, saving the + * result into the given PeriodAmount. If the parse + * succeeds, the return value is the new text position. Note that the parse + * may succeed without fully reading the text. + * If it fails, the return value is negative, but the period may still be + * modified. To determine the position where the parse failed, apply the + * one's complement operator (~) on the return value. + * + * @param amount the period amount + * @param periodStr text to parse + * @param position position to start parsing from + * @param locale the locale to use for parsing + * @return new position, if negative, parse failed. Apply complement + * operator (~) to get position of failure + * @throws IllegalArgumentException if any field is out of range + */ + int parseInto(PeriodAmount amount, String periodStr, int position, Locale locale); + +} diff --git a/src/main/java/org/xbib/time/format/PeriodPrinter.java b/src/main/java/org/xbib/time/format/PeriodPrinter.java new file mode 100644 index 0000000..ce689bf --- /dev/null +++ b/src/main/java/org/xbib/time/format/PeriodPrinter.java @@ -0,0 +1,55 @@ +package org.xbib.time.format; + +import java.io.IOException; +import java.io.Writer; +import java.time.Period; +import java.util.Locale; + +/** + * Internal interface for printing textual representations of time periods. + * Application users will rarely use this class directly. Instead, you + * will use one of the factory classes to create a {@link PeriodFormatter}. + * The factory classes are {@link PeriodFormat} and{@link ISOPeriodFormat}. + */ +public interface PeriodPrinter { + + /** + * Returns the exact number of characters produced for the given period. + * + * @param period the period to use + * @param locale the locale to use + * @return the estimated length + */ + int calculatePrintedLength(Period period, Locale locale); + + /** + * Returns the amount of fields from the given period that this printer + * will print. + * + * @param period the period to use + * @param stopAt stop counting at this value, enter a number ≥ 256 to count all + * @param locale the locale to use + * @return amount of fields printed + */ + int countFieldsToPrint(Period period, int stopAt, Locale locale); + + /** + * Prints a ReadablePeriod to a StringBuilder. + * + * @param buf the formatted period is appended to this buffer + * @param period the period to format + * @param locale the locale to use + */ + void printTo(StringBuilder buf, Period period, Locale locale); + + /** + * Prints a ReadablePeriod to a Writer. + * + * @param out the formatted period is written out + * @param period the period to format + * @param locale the locale to use + * @throws IOException exception + */ + void printTo(Writer out, Period period, Locale locale) throws IOException; + +} diff --git a/src/main/java/org/xbib/time/format/package-info.java b/src/main/java/org/xbib/time/format/package-info.java new file mode 100644 index 0000000..34bd2cb --- /dev/null +++ b/src/main/java/org/xbib/time/format/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for time formatting. + */ +package org.xbib.time.format; diff --git a/src/main/java/org/xbib/time/pretty/LocaleAware.java b/src/main/java/org/xbib/time/pretty/LocaleAware.java new file mode 100644 index 0000000..bea3202 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/LocaleAware.java @@ -0,0 +1,18 @@ +package org.xbib.time.pretty; + +import java.util.Locale; + +/** + * An object that behaves differently for various {@link Locale} settings. + * + * @param parameter type + */ +public interface LocaleAware { + /** + * Set the {@link Locale} for which this instance should behave in. + * @param locale locale + * @return the type + */ + T setLocale(Locale locale); + +} diff --git a/src/main/java/org/xbib/time/pretty/PrettyTime.java b/src/main/java/org/xbib/time/pretty/PrettyTime.java new file mode 100644 index 0000000..2af7ef6 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/PrettyTime.java @@ -0,0 +1,468 @@ +package org.xbib.time.pretty; + +import org.xbib.time.pretty.i18n.ResourcesTimeFormat; +import org.xbib.time.pretty.i18n.ResourcesTimeUnit; +import org.xbib.time.pretty.units.Century; +import org.xbib.time.pretty.units.Day; +import org.xbib.time.pretty.units.Decade; +import org.xbib.time.pretty.units.Hour; +import org.xbib.time.pretty.units.JustNow; +import org.xbib.time.pretty.units.Millennium; +import org.xbib.time.pretty.units.Millisecond; +import org.xbib.time.pretty.units.Minute; +import org.xbib.time.pretty.units.Month; +import org.xbib.time.pretty.units.Second; +import org.xbib.time.pretty.units.TimeUnitComparator; +import org.xbib.time.pretty.units.Week; +import org.xbib.time.pretty.units.Year; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * A utility for creating social-networking style timestamps. (e.g. "just now", "moments ago", "3 days ago", + * "within 2 months") + *

+ * Usage: + *

+ * + * PrettyTime t = new PrettyTime(); + * String timestamp = t.format(LocalDateTime.now()); + * //result: moments from now + * + */ +public class PrettyTime { + + /** + * The reference timestamp. + * If the timestamp formatted is before the reference timestamp, the format command will produce a String that is in the + * past tense. If the timestamp formatted is after the reference timestamp, the format command will produce a string + * thatis in the future tense. + */ + private LocalDateTime localDateTime; + + private Locale locale; + + private Map units = new LinkedHashMap<>(); + + public PrettyTime() { + this(LocalDateTime.now()); + } + + public PrettyTime(long l) { + this(LocalDateTime.ofInstant(Instant.ofEpochMilli(l), ZoneId.systemDefault())); + } + + /** + * Accept a {@link LocalDateTime} instant to represent the point of reference for comparison. + * This may be changed by theuser, after construction. + *

+ * See {@code PrettyTime.setReference(LocalDateTime timestamp)}. + * + * @param localDateTime reference date time + */ + public PrettyTime(LocalDateTime localDateTime) { + this.localDateTime = localDateTime; + setLocale(Locale.getDefault()); + initTimeUnits(); + } + + /** + * Construct a new instance using the given {@link Locale} instead of the system default. + * @param locale locale + */ + public PrettyTime(final Locale locale) { + setLocale(locale); + initTimeUnits(); + this.localDateTime = LocalDateTime.now(); + } + + /** + * Accept a {@link LocalDateTime} timestamp to represent the point of reference for comparison. + * This may be changed by the user, after construction. Use the given {@link Locale} + * instead of the system default. + *

+ * See {@code PrettyTime.setReference(LocalDateTime timestamp)}. + * @param localDateTime timestamp + * @param locale locale + */ + public PrettyTime(final LocalDateTime localDateTime, final Locale locale) { + setLocale(locale); + initTimeUnits(); + this.localDateTime = localDateTime; + } + + public PrettyTime(long l, final Locale locale) { + setLocale(locale); + initTimeUnits(); + this.localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(l), ZoneId.systemDefault()); + } + + /** + * Calculate the approximate duration. + * @param then time instance + * @return time unit quantity + */ + public TimeUnitQuantity approximateDuration(LocalDateTime then) { + if (then == null) { + then = LocalDateTime.now(); + } + long difference = ChronoUnit.MILLIS.between(localDateTime, then); + return calculateDuration(difference); + } + + public TimeUnitQuantity calculateDuration(final long difference) { + long absoluteDifference = Math.abs(difference); + List units = new ArrayList<>(); + units.addAll(getUnits()); + TimeUnitQuantity result = new TimeUnitQuantity(); + for (int i = 0; i < units.size(); i++) { + TimeUnit unit = units.get(i); + long millisPerUnit = Math.abs(unit.getMillisPerUnit()); + long quantity = Math.abs(unit.getMaxQuantity()); + boolean isLastUnit = (i == units.size() - 1); + if ((quantity == 0L) && !isLastUnit) { + quantity = units.get(i + 1).getMillisPerUnit() / unit.getMillisPerUnit(); + } + // does our unit encompass the time duration? + if ((millisPerUnit * quantity > absoluteDifference) || isLastUnit) { + result.setUnit(unit); + if (millisPerUnit > absoluteDifference) { + // we are rounding up: get 1 or -1 for past or future + result.setQuantity(difference < 0L ? -1L : 1L); + } else { + result.setQuantity(difference / millisPerUnit); + } + result.setDelta(difference - result.getQuantity() * millisPerUnit); + break; + } + } + return result; + } + + /** + * Calculate to the precision of the smallest provided {@link TimeUnit}, the exact duration represented by the + * difference between the reference timestamp. + *

+ * Note: Precision may be lost if no supplied {@link TimeUnit} is granular enough to represent one + * millisecond + * + * @param then The date to be compared against the reference timestamp, or now if no reference timestamp was + * provided + * @return A sorted {@link List} of {@link TimeUnitQuantity} objects, from largest to smallest. Each element in the list + * represents the approximate duration (number of times) that {@link TimeUnit} to fit into the previous + * element's delta. The first element is the largest {@link TimeUnit} to fit within the total difference + * between compared dates. + */ + public List calculatePreciseDuration(LocalDateTime then) { + if (then == null) { + then = LocalDateTime.now(); + } + List result = new ArrayList<>(); + long difference = ChronoUnit.MILLIS.between(localDateTime, then); + TimeUnitQuantity timeUnitQuantity = calculateDuration(difference); + result.add(timeUnitQuantity); + while (timeUnitQuantity.getDelta() != 0L) { + timeUnitQuantity = calculateDuration(timeUnitQuantity.getDelta()); + result.add(timeUnitQuantity); + } + return result; + } + + public String format(long then) { + return format(approximateDuration(LocalDateTime.ofInstant(Instant.ofEpochMilli(then), ZoneId.systemDefault()))); + } + + /** + * Format the given {@link LocalDateTime} object. This method applies the {@code PrettyTime.approximateDuration(date)} + * method + * to perform its calculation. If {@code then} is null, it will default to {@code new Date()}; also decorate for + * past/future tense. + * + * @param then the {@link LocalDateTime} to be formatted + * @return A formatted string representing {@code then} + */ + public String format(LocalDateTime then) { + if (then == null) { + then = LocalDateTime.now(); + } + return format(approximateDuration(then)); + } + + /** + * Format the given {@link LocalDateTime} object. This method applies the {@code PrettyTime.approximateDuration(date)} + * method + * to perform its calculation. If {@code then} is null, it will default to {@code new Date()}; also decorate for + * past/future tense. Rounding rules are ignored. + * + * @param then the {@link LocalDateTime} to be formatted + * @return A formatted string representing {@code then} + */ + public String formatUnrounded(final LocalDateTime then) { + return formatUnrounded(approximateDuration(then)); + } + + /** + * Format the given {@link TimeUnitQuantity} object, using the {@link TimeFormat} specified by the {@link TimeUnit} + * contained + * within; also decorate for past/future tense. + * + * @param timeUnitQuantity the {@link TimeUnitQuantity} to be formatted + * @return A formatted string representing {@code duration} + */ + public String format(final TimeUnitQuantity timeUnitQuantity) { + if (timeUnitQuantity == null) { + return format(LocalDateTime.now()); + } + TimeFormat format = getFormat(timeUnitQuantity.getUnit()); + String time = format.format(timeUnitQuantity); + return format.decorate(timeUnitQuantity, time); + } + + /** + * Format the given {@link TimeUnitQuantity} object, using the {@link TimeFormat} specified by the {@link TimeUnit} + * contained + * within; also decorate for past/future tense. Rounding rules are ignored. + * + * @param timeUnitQuantity the {@link TimeUnitQuantity} to be formatted + * @return A formatted string representing {@code duration} + */ + public String formatUnrounded(final TimeUnitQuantity timeUnitQuantity) { + if (timeUnitQuantity == null) { + throw new IllegalArgumentException("Duration to format must not be null."); + } + TimeFormat format = getFormat(timeUnitQuantity.getUnit()); + String time = format.formatUnrounded(timeUnitQuantity); + return format.decorateUnrounded(timeUnitQuantity, time); + } + + /** + * Format the given {@link TimeUnitQuantity} objects, using the {@link TimeFormat} specified by the {@link TimeUnit} + * contained within. Rounds only the last {@link TimeUnitQuantity} object. + * + * @param timeUnitQuantities the {@link TimeUnitQuantity}s to be formatted + * @return A list of formatted strings representing {@code durations} + */ + public String format(final List timeUnitQuantities) { + if (timeUnitQuantities == null) { + throw new IllegalArgumentException("Duration list must not be null."); + } + String result = null; + StringBuilder builder = new StringBuilder(); + TimeUnitQuantity timeUnitQuantity = null; + TimeFormat format = null; + for (int i = 0; i < timeUnitQuantities.size(); i++) { + timeUnitQuantity = timeUnitQuantities.get(i); + format = getFormat(timeUnitQuantity.getUnit()); + boolean isLast = (i == timeUnitQuantities.size() - 1); + if (!isLast) { + builder.append(format.formatUnrounded(timeUnitQuantity)); + builder.append(" "); + } else { + builder.append(format.format(timeUnitQuantity)); + } + } + if (format != null) { + result = format.decorateUnrounded(timeUnitQuantity, builder.toString()); + } + return result; + } + + /** + * Given a date, returns a non-relative format string for the + * approximate duration of the difference between the date and now. + * + * @param date the date to be formatted + * @return A formatted string of the approximate duration + */ + public String formatApproximateDuration(LocalDateTime date) { + TimeUnitQuantity timeUnitQuantity = approximateDuration(date); + return formatDuration(timeUnitQuantity); + } + + /** + * Given a duration, returns a non-relative format string. + * + * @param timeUnitQuantity the duration to be formatted + * @return A formatted string of the duration + */ + public String formatDuration(TimeUnitQuantity timeUnitQuantity) { + TimeFormat timeFormat = getFormat(timeUnitQuantity.getUnit()); + return timeFormat.format(timeUnitQuantity); + } + + /** + * Get the registered {@link TimeFormat} for the given {@link TimeUnit} or null if none exists. + * @param unit time unit + * @return time format + */ + public TimeFormat getFormat(TimeUnit unit) { + if (unit == null) { + throw new IllegalArgumentException("Time unit must not be null."); + } + return units.get(unit); + } + + public PrettyTime setReference(LocalDateTime localDateTime) { + this.localDateTime = localDateTime; + return this; + } + + /** + * Get a {@link List} of the current configured {@link TimeUnit} instances in calculations. + * + * @return list + */ + public List getUnits() { + List result = new ArrayList<>(units.keySet()); + Collections.sort(result, new TimeUnitComparator()); + return Collections.unmodifiableList(result); + } + + /** + * Get the registered {@link TimeUnit} for the given {@link TimeUnit} type or null if none exists. + * @param unitType unit type + * @param time unit type + * @return unit + */ + @SuppressWarnings("unchecked") + public U getUnit(final Class unitType) { + if (unitType == null) { + throw new IllegalArgumentException("Unit type to get must not be null."); + } + for (TimeUnit unit : units.keySet()) { + if (unitType.isAssignableFrom(unit.getClass())) { + return (U) unit; + } + } + return null; + } + + /** + * Register the given {@link TimeUnit} and corresponding {@link TimeFormat} instance to be used in calculations. If + * an entry already exists for the given {@link TimeUnit}, its format will be overwritten with the given + * {@link TimeFormat}. + * @param unit unit + * @param format format + * @return this object + */ + public PrettyTime registerUnit(final TimeUnit unit, TimeFormat format) { + if (unit == null) { + throw new IllegalArgumentException("Unit to register must not be null."); + } + if (format == null) { + throw new IllegalArgumentException("Format to register must not be null."); + } + units.put(unit, format); + if (unit instanceof LocaleAware) { + ((LocaleAware) unit).setLocale(locale); + } + if (format instanceof LocaleAware) { + ((LocaleAware) format).setLocale(locale); + } + return this; + } + + /** + * Removes the mapping for the given {@link TimeUnit} type. This effectively de-registers the unit so it will not + * be used in formatting. Returns the {@link TimeFormat} that was registered for the given {@link TimeUnit} type, or + * null if no unit of the given type was registered. + * @param unitType unit type + * @param time unit type + * @return time unit + */ + public TimeFormat removeUnit(final Class unitType) { + if (unitType == null) { + throw new IllegalArgumentException("Unit type to remove must not be null."); + } + + for (TimeUnit unit : units.keySet()) { + if (unitType.isAssignableFrom(unit.getClass())) { + return units.remove(unit); + } + } + return null; + } + + /** + * Removes the mapping for the given {@link TimeUnit}. This effectively de-registers the unit so it will not be + * used in formatting. Returns the {@link TimeFormat} that was registered for the given {@link TimeUnit}, + * or null if no such unit was registered. + * @param unit time unit + * @return time format + */ + public TimeFormat removeUnit(final TimeUnit unit) { + if (unit == null) { + throw new IllegalArgumentException("Unit to remove must not be null."); + } + + return units.remove(unit); + } + + /** + * Get the currently configured {@link Locale} for this {@link PrettyTime} object. + * @return locale + */ + public Locale getLocale() { + return locale; + } + + /** + * Set the the {@link Locale} for this {@link PrettyTime} object. This may be an expensive operation, since this + * operation calls {@link LocaleAware#setLocale(Locale)} for each {@link TimeUnit} in {@link #getUnits()}. + * @param locale locale + * @return this object + */ + public PrettyTime setLocale(final Locale locale) { + this.locale = locale; + units.keySet().stream().filter(unit -> unit instanceof LocaleAware) + .forEach(unit -> ((LocaleAware) unit).setLocale(locale)); + units.values().stream().filter(format -> format instanceof LocaleAware) + .forEach(format -> ((LocaleAware) format).setLocale(locale)); + return this; + } + + @Override + public String toString() { + return "PrettyTime [date=" + localDateTime + ", locale=" + locale + "]"; + } + + /** + * Remove all registered {@link TimeUnit} instances. + * + * @return The removed {@link TimeUnit} instances. + */ + public List clearUnits() { + List result = getUnits(); + units.clear(); + return result; + } + + private void initTimeUnits() { + addUnit(new JustNow()); + addUnit(new Millisecond()); + addUnit(new Second()); + addUnit(new Minute()); + addUnit(new Hour()); + addUnit(new Day()); + addUnit(new Week()); + addUnit(new Month()); + addUnit(new Year()); + addUnit(new Decade()); + addUnit(new Century()); + addUnit(new Millennium()); + } + + private void addUnit(ResourcesTimeUnit unit) { + registerUnit(unit, new ResourcesTimeFormat(unit)); + } + +} diff --git a/src/main/java/org/xbib/time/pretty/SimpleTimeFormat.java b/src/main/java/org/xbib/time/pretty/SimpleTimeFormat.java new file mode 100644 index 0000000..ba255c2 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/SimpleTimeFormat.java @@ -0,0 +1,189 @@ +package org.xbib.time.pretty; + +/** + * Represents a simple method of formatting a specific {@link TimeUnitQuantity} of time. + */ +public class SimpleTimeFormat implements TimeFormat { + private static final String SIGN = "%s"; + private static final String QUANTITY = "%n"; + private static final String UNIT = "%u"; + private static final String NEGATIVE = "-"; + private String singularName = ""; + private String pluralName = ""; + private String futureSingularName = ""; + private String futurePluralName = ""; + private String pastSingularName = ""; + private String pastPluralName = ""; + private String pattern = ""; + private String futurePrefix = ""; + private String futureSuffix = ""; + private String pastPrefix = ""; + private String pastSuffix = ""; + private int roundingTolerance = 50; + + @Override + public String format(final TimeUnitQuantity timeUnitQuantity) { + return format(timeUnitQuantity, true); + } + + @Override + public String formatUnrounded(TimeUnitQuantity timeUnitQuantity) { + return format(timeUnitQuantity, false); + } + + @Override + public String decorate(TimeUnitQuantity timeUnitQuantity, String time) { + StringBuilder result = new StringBuilder(); + if (timeUnitQuantity.isInPast()) { + result.append(pastPrefix).append(" ").append(time).append(" ").append(pastSuffix); + } else { + result.append(futurePrefix).append(" ").append(time).append(" ").append(futureSuffix); + } + return result.toString().replaceAll("\\s+", " ").trim(); + } + + @Override + public String decorateUnrounded(TimeUnitQuantity timeUnitQuantity, String time) { + // This format does not need to know about rounding during decoration. + return decorate(timeUnitQuantity, time); + } + + private String format(final TimeUnitQuantity timeUnitQuantity, final boolean round) { + String sign = getSign(timeUnitQuantity); + String unit = getGramaticallyCorrectName(timeUnitQuantity, round); + long quantity = getQuantity(timeUnitQuantity, round); + + return applyPattern(sign, unit, quantity); + } + + private String applyPattern(final String sign, final String unit, final long quantity) { + String result = getPattern(quantity).replaceAll(SIGN, sign); + result = result.replaceAll(QUANTITY, String.valueOf(quantity)); + result = result.replaceAll(UNIT, unit); + return result; + } + + protected String getPattern(final long quantity) { + return pattern; + } + + public String getPattern() { + return pattern; + } + + /* + * Builder Setters + */ + public SimpleTimeFormat setPattern(final String pattern) { + this.pattern = pattern; + return this; + } + + protected long getQuantity(TimeUnitQuantity timeUnitQuantity, boolean round) { + return Math.abs(round ? timeUnitQuantity.getQuantityRounded(roundingTolerance) : timeUnitQuantity.getQuantity()); + } + + protected String getGramaticallyCorrectName(final TimeUnitQuantity d, boolean round) { + String result = getSingularName(d); + if ((Math.abs(getQuantity(d, round)) == 0) || (Math.abs(getQuantity(d, round)) > 1)) { + result = getPluralName(d); + } + return result; + } + + private String getSign(final TimeUnitQuantity d) { + if (d.getQuantity() < 0) { + return NEGATIVE; + } + return ""; + } + + private String getSingularName(TimeUnitQuantity timeUnitQuantity) { + if (timeUnitQuantity.isInFuture() && futureSingularName != null && futureSingularName.length() > 0) { + return futureSingularName; + } else if (timeUnitQuantity.isInPast() && pastSingularName != null && pastSingularName.length() > 0) { + return pastSingularName; + } else { + return singularName; + } + } + + private String getPluralName(TimeUnitQuantity timeUnitQuantity) { + if (timeUnitQuantity.isInFuture() && futurePluralName != null && futureSingularName.length() > 0) { + return futurePluralName; + } else if (timeUnitQuantity.isInPast() && pastPluralName != null && pastSingularName.length() > 0) { + return pastPluralName; + } else { + return pluralName; + } + } + + public SimpleTimeFormat setFuturePrefix(final String futurePrefix) { + this.futurePrefix = futurePrefix.trim(); + return this; + } + + public SimpleTimeFormat setFutureSuffix(final String futureSuffix) { + this.futureSuffix = futureSuffix.trim(); + return this; + } + + public SimpleTimeFormat setPastPrefix(final String pastPrefix) { + this.pastPrefix = pastPrefix.trim(); + return this; + } + + public SimpleTimeFormat setPastSuffix(final String pastSuffix) { + this.pastSuffix = pastSuffix.trim(); + return this; + } + + /** + * The percentage of the current {@link TimeUnit}.getMillisPerUnit() for which the quantity may be rounded up by + * one. + * + * @param roundingTolerance tolerance + * @return time format + */ + public SimpleTimeFormat setRoundingTolerance(final int roundingTolerance) { + this.roundingTolerance = roundingTolerance; + return this; + } + + public SimpleTimeFormat setSingularName(String name) { + this.singularName = name; + return this; + } + + public SimpleTimeFormat setPluralName(String pluralName) { + this.pluralName = pluralName; + return this; + } + + public SimpleTimeFormat setFutureSingularName(String futureSingularName) { + this.futureSingularName = futureSingularName; + return this; + } + + public SimpleTimeFormat setFuturePluralName(String futurePluralName) { + this.futurePluralName = futurePluralName; + return this; + } + + public SimpleTimeFormat setPastSingularName(String pastSingularName) { + this.pastSingularName = pastSingularName; + return this; + } + + public SimpleTimeFormat setPastPluralName(String pastPluralName) { + this.pastPluralName = pastPluralName; + return this; + } + + @Override + public String toString() { + return "SimpleTimeFormat [pattern=" + pattern + ", futurePrefix=" + futurePrefix + ", futureSuffix=" + + futureSuffix + ", pastPrefix=" + pastPrefix + ", pastSuffix=" + pastSuffix + ", roundingTolerance=" + + roundingTolerance + "]"; + } +} diff --git a/src/main/java/org/xbib/time/pretty/TimeFormat.java b/src/main/java/org/xbib/time/pretty/TimeFormat.java new file mode 100644 index 0000000..ae59195 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/TimeFormat.java @@ -0,0 +1,40 @@ +package org.xbib.time.pretty; + +/** + */ +public interface TimeFormat { + /** + * Given a populated {@link TimeUnitQuantity} object. Apply formatting (with rounding) and output the result. + * + * @param timeUnitQuantity The original {@link TimeUnitQuantity} instance from which the time string should be decorated. + * @return the formatted string + */ + String format(final TimeUnitQuantity timeUnitQuantity); + + /** + * Given a populated {@link TimeUnitQuantity} object. Apply formatting (without rounding) and output the result. + * + * @param timeUnitQuantity The original {@link TimeUnitQuantity} instance from which the time string should be decorated. + * @return the formatted string + */ + String formatUnrounded(TimeUnitQuantity timeUnitQuantity); + + /** + * Decorate with past or future prefix/suffix (with rounding). + * + * @param timeUnitQuantity The original {@link TimeUnitQuantity} instance from which the time string should be decorated. + * @param time The formatted time string. + * @return the formatted string + */ + String decorate(TimeUnitQuantity timeUnitQuantity, String time); + + /** + * Decorate with past or future prefix/suffix (without rounding). + * + * @param timeUnitQuantity The original {@link TimeUnitQuantity} instance from which the time string should be decorated. + * @param time The formatted time string. + * @return the formatted string + */ + String decorateUnrounded(TimeUnitQuantity timeUnitQuantity, String time); + +} diff --git a/src/main/java/org/xbib/time/pretty/TimeFormatProvider.java b/src/main/java/org/xbib/time/pretty/TimeFormatProvider.java new file mode 100644 index 0000000..9a342b2 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/TimeFormatProvider.java @@ -0,0 +1,16 @@ +package org.xbib.time.pretty; + +import org.xbib.time.pretty.i18n.ResourcesTimeFormat; + +/** + * Produces time formats. Currently only to be used on Resource bundle implementations when used in + * {@link ResourcesTimeFormat} instances. + */ +public interface TimeFormatProvider { + /** + * Return the appropriate {@link TimeFormat} for the given {@link TimeUnit}. + * @param t time unit + * @return time format + */ + TimeFormat getFormatFor(TimeUnit t); +} diff --git a/src/main/java/org/xbib/time/pretty/TimeUnit.java b/src/main/java/org/xbib/time/pretty/TimeUnit.java new file mode 100644 index 0000000..7a95063 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/TimeUnit.java @@ -0,0 +1,32 @@ +package org.xbib.time.pretty; + +/** + * Defines a Unit of time (e.g. seconds, minutes, hours) and its conversion to milliseconds. + */ +public interface TimeUnit { + + /** + * The number of milliseconds represented by each instance of this TimeUnit. + * Must be a positive number greater than zero. + * @return millis + */ + long getMillisPerUnit(); + + /** + * The maximum quantity of this Unit to be used as a threshold for the next + * largest Unit (e.g. if one Second represents 1000ms, and + * Second has a maxQuantity of 5, then if the difference + * between compared timestamps is larger than 5000ms, PrettyTime will move + * on to the next smallest TimeUnit for calculation; Minute, by + * default) + *

+ * millisPerUnit * maxQuantity = maxAllowedMs + *

+ * If maxQuantity is zero, it will be equal to the next highest + * TimeUnit.getMillisPerUnit() / + * this.getMillisPerUnit() or infinity if there are no greater + * TimeUnits. + * @return quantity + */ + long getMaxQuantity(); +} diff --git a/src/main/java/org/xbib/time/pretty/TimeUnitQuantity.java b/src/main/java/org/xbib/time/pretty/TimeUnitQuantity.java new file mode 100644 index 0000000..f79e206 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/TimeUnitQuantity.java @@ -0,0 +1,56 @@ +package org.xbib.time.pretty; + +/** + * Represents a quantity of any given {@link TimeUnit}. + */ +public class TimeUnitQuantity { + + private long quantity; + + private long delta; + + private TimeUnit unit; + + public long getQuantity() { + return quantity; + } + + public void setQuantity(final long quantity) { + this.quantity = quantity; + } + + public TimeUnit getUnit() { + return unit; + } + + public void setUnit(final TimeUnit unit) { + this.unit = unit; + } + + public long getDelta() { + return delta; + } + + public void setDelta(final long delta) { + this.delta = delta; + } + + public boolean isInPast() { + return getQuantity() < 0; + } + + public boolean isInFuture() { + return !isInPast(); + } + + public long getQuantityRounded(int tolerance) { + long quantity = Math.abs(getQuantity()); + if (getDelta() != 0) { + double threshold = Math.abs(((double) getDelta() / (double) getUnit().getMillisPerUnit()) * 100); + if (threshold > tolerance) { + quantity = quantity + 1; + } + } + return quantity; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/MapResourceBundle.java b/src/main/java/org/xbib/time/pretty/i18n/MapResourceBundle.java new file mode 100644 index 0000000..7bc1775 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/MapResourceBundle.java @@ -0,0 +1,102 @@ +package org.xbib.time.pretty.i18n; + +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.ResourceBundle; +import java.util.Set; + +/** + * + */ +public abstract class MapResourceBundle extends ResourceBundle { + + private Map lookup; + + public MapResourceBundle() { + } + + public final Object handleGetObject(String key) { + if (lookup == null) { + loadLookup(); + } + return lookup.get(key); + } + + public Enumeration getKeys() { + if (lookup == null) { + loadLookup(); + } + ResourceBundle parent = this.parent; + return new ResourceBundleEnumeration(lookup.keySet(), parent != null ? parent.getKeys() : null); + } + + protected Set handleKeySet() { + if (lookup == null) { + loadLookup(); + } + return lookup.keySet(); + } + + protected abstract Map getContents(); + + private synchronized void loadLookup() { + if (lookup != null) { + return; + } + Map contents = getContents(); + Map temp = new HashMap<>(); + for (Map.Entry entry : contents.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (key == null || value == null) { + throw new NullPointerException(); + } + temp.put(key, value); + } + lookup = temp; + } + + private static class ResourceBundleEnumeration implements Enumeration { + private Set set; + private Iterator iterator; + private Enumeration enumeration; + private String next = null; + + ResourceBundleEnumeration(Set var1, Enumeration var2) { + this.set = var1; + this.iterator = var1.iterator(); + this.enumeration = var2; + } + + @Override + public boolean hasMoreElements() { + if (next == null) { + if (iterator.hasNext()) { + next = iterator.next(); + } else if (enumeration != null) { + while (next == null && enumeration.hasMoreElements()) { + next = enumeration.nextElement(); + if (set.contains(next)) { + next = null; + } + } + } + } + return next != null; + } + + @Override + public String nextElement() { + if (this.hasMoreElements()) { + String var1 = this.next; + this.next = null; + return var1; + } else { + throw new NoSuchElementException(); + } + } + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources.java b/src/main/java/org/xbib/time/pretty/i18n/Resources.java new file mode 100644 index 0000000..5edf989 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources.java @@ -0,0 +1,111 @@ +package org.xbib.time.pretty.i18n; + +import java.util.HashMap; +import java.util.Map; + +/** + * + */ +public class Resources extends MapResourceBundle { + private static final Map map = new HashMap<>(); + + static { + map.put("CenturyPattern", "%n %u"); + map.put("CenturyFuturePrefix", ""); + map.put("CenturyFutureSuffix", " from now"); + map.put("CenturyPastPrefix", ""); + map.put("CenturyPastSuffix", " ago"); + map.put("CenturySingularName", "century"); + map.put("CenturyPluralName", "centuries"); + map.put("DayPattern", "%n %u"); + map.put("DayFuturePrefix", ""); + map.put("DayFutureSuffix", " from now"); + map.put("DayPastPrefix", ""); + map.put("DayPastSuffix", " ago"); + map.put("DaySingularName", "day"); + map.put("DayPluralName", "days"); + map.put("DecadePattern", "%n %u"); + map.put("DecadeFuturePrefix", ""); + map.put("DecadeFutureSuffix", " from now"); + map.put("DecadePastPrefix", ""); + map.put("DecadePastSuffix", " ago"); + map.put("DecadeSingularName", "decade"); + map.put("DecadePluralName", "decades"); + map.put("HourPattern", "%n %u"); + map.put("HourFuturePrefix", ""); + map.put("HourFutureSuffix", " from now"); + map.put("HourPastPrefix", ""); + map.put("HourPastSuffix", " ago"); + map.put("HourSingularName", "hour"); + map.put("HourPluralName", "hours"); + map.put("JustNowPattern", "%u"); + map.put("JustNowFuturePrefix", ""); + map.put("JustNowFutureSuffix", "moments from now"); + map.put("JustNowPastPrefix", "moments ago"); + map.put("JustNowPastSuffix", ""); + map.put("JustNowSingularName", ""); + map.put("JustNowPluralName", ""); + map.put("MillenniumPattern", "%n %u"); + map.put("MillenniumFuturePrefix", ""); + map.put("MillenniumFutureSuffix", " from now"); + map.put("MillenniumPastPrefix", ""); + map.put("MillenniumPastSuffix", " ago"); + map.put("MillenniumSingularName", "millennium"); + map.put("MillenniumPluralName", "millennia"); + map.put("MillisecondPattern", "%n %u"); + map.put("MillisecondFuturePrefix", ""); + map.put("MillisecondFutureSuffix", " from now"); + map.put("MillisecondPastPrefix", ""); + map.put("MillisecondPastSuffix", " ago"); + map.put("MillisecondSingularName", "millisecond"); + map.put("MillisecondPluralName", "milliseconds"); + map.put("MinutePattern", "%n %u"); + map.put("MinuteFuturePrefix", ""); + map.put("MinuteFutureSuffix", " from now"); + map.put("MinutePastPrefix", ""); + map.put("MinutePastSuffix", " ago"); + map.put("MinuteSingularName", "minute"); + map.put("MinutePluralName", "minutes"); + map.put("MonthPattern", "%n %u"); + map.put("MonthFuturePrefix", ""); + map.put("MonthFutureSuffix", " from now"); + map.put("MonthPastPrefix", ""); + map.put("MonthPastSuffix", " ago"); + map.put("MonthSingularName", "month"); + map.put("MonthPluralName", "months"); + map.put("SecondPattern", "%n %u"); + map.put("SecondFuturePrefix", ""); + map.put("SecondFutureSuffix", " from now"); + map.put("SecondPastPrefix", ""); + map.put("SecondPastSuffix", " ago"); + map.put("SecondSingularName", "second"); + map.put("SecondPluralName", "seconds"); + map.put("WeekPattern", "%n %u"); + map.put("WeekFuturePrefix", ""); + map.put("WeekFutureSuffix", " from now"); + map.put("WeekPastPrefix", ""); + map.put("WeekPastSuffix", " ago"); + map.put("WeekSingularName", "week"); + map.put("WeekPluralName", "weeks"); + map.put("YearPattern", "%n %u"); + map.put("YearFuturePrefix", ""); + map.put("YearFutureSuffix", " from now"); + map.put("YearPastPrefix", ""); + map.put("YearPastSuffix", " ago"); + map.put("YearSingularName", "year"); + map.put("YearPluralName", "years"); + map.put("AbstractTimeUnitPattern", ""); + map.put("AbstractTimeUnitFuturePrefix", ""); + map.put("AbstractTimeUnitFutureSuffix", ""); + map.put("AbstractTimeUnitPastPrefix", ""); + map.put("AbstractTimeUnitPastSuffix", ""); + map.put("AbstractTimeUnitSingularName", ""); + map.put("AbstractTimeUnitPluralName", ""); + } + + @Override + public Map getContents() { + return map; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/ResourcesTimeFormat.java b/src/main/java/org/xbib/time/pretty/i18n/ResourcesTimeFormat.java new file mode 100644 index 0000000..513fbf8 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/ResourcesTimeFormat.java @@ -0,0 +1,85 @@ +package org.xbib.time.pretty.i18n; + +import org.xbib.time.pretty.LocaleAware; +import org.xbib.time.pretty.SimpleTimeFormat; +import org.xbib.time.pretty.TimeFormat; +import org.xbib.time.pretty.TimeFormatProvider; +import org.xbib.time.pretty.TimeUnitQuantity; + +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * Represents a simple method of formatting a specific {@link TimeUnitQuantity} of time. + */ +public class ResourcesTimeFormat extends SimpleTimeFormat implements TimeFormat, LocaleAware { + private final ResourcesTimeUnit unit; + private TimeFormat override; + + public ResourcesTimeFormat(ResourcesTimeUnit unit) { + this.unit = unit; + } + + @Override + public ResourcesTimeFormat setLocale(Locale locale) { + ResourceBundle bundle = ResourceBundle.getBundle(unit.getResourceBundleName(), locale); + if (bundle instanceof TimeFormatProvider) { + TimeFormat format = ((TimeFormatProvider) bundle).getFormatFor(unit); + if (format != null) { + this.override = format; + } + } else { + override = null; + } + + if (override == null) { + setPattern(bundle.getString(unit.getResourceKeyPrefix() + "Pattern")); + setFuturePrefix(bundle.getString(unit.getResourceKeyPrefix() + "FuturePrefix")); + setFutureSuffix(bundle.getString(unit.getResourceKeyPrefix() + "FutureSuffix")); + setPastPrefix(bundle.getString(unit.getResourceKeyPrefix() + "PastPrefix")); + setPastSuffix(bundle.getString(unit.getResourceKeyPrefix() + "PastSuffix")); + + setSingularName(bundle.getString(unit.getResourceKeyPrefix() + "SingularName")); + setPluralName(bundle.getString(unit.getResourceKeyPrefix() + "PluralName")); + + String key = unit.getResourceKeyPrefix() + "FuturePluralName"; + if (bundle.containsKey(key)) { + setFuturePluralName(bundle.getString(key)); + } + key = unit.getResourceKeyPrefix() + "FutureSingularName"; + if (bundle.containsKey(key)) { + setFutureSingularName((bundle.getString(key))); + } + key = unit.getResourceKeyPrefix() + "PastPluralName"; + if (bundle.containsKey(key)) { + setPastPluralName((bundle.getString(key))); + } + key = unit.getResourceKeyPrefix() + "PastSingularName"; + if (bundle.containsKey(key)) { + setPastSingularName((bundle.getString(key))); + } + } + return this; + } + + @Override + public String decorate(TimeUnitQuantity timeUnitQuantity, String time) { + return override == null ? super.decorate(timeUnitQuantity, time) : override.decorate(timeUnitQuantity, time); + } + + @Override + public String decorateUnrounded(TimeUnitQuantity timeUnitQuantity, String time) { + return override == null ? super.decorateUnrounded(timeUnitQuantity, time) : + override.decorateUnrounded(timeUnitQuantity, time); + } + + @Override + public String format(TimeUnitQuantity timeUnitQuantity) { + return override == null ? super.format(timeUnitQuantity) : override.format(timeUnitQuantity); + } + + @Override + public String formatUnrounded(TimeUnitQuantity timeUnitQuantity) { + return override == null ? super.formatUnrounded(timeUnitQuantity) : override.formatUnrounded(timeUnitQuantity); + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/ResourcesTimeUnit.java b/src/main/java/org/xbib/time/pretty/i18n/ResourcesTimeUnit.java new file mode 100644 index 0000000..ae9d3a7 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/ResourcesTimeUnit.java @@ -0,0 +1,36 @@ +package org.xbib.time.pretty.i18n; + +import org.xbib.time.pretty.TimeUnit; + +/** + * + */ +public abstract class ResourcesTimeUnit implements TimeUnit { + private long maxQuantity = 0; + private long millisPerUnit = 1; + + protected abstract String getResourceKeyPrefix(); + + protected String getResourceBundleName() { + return Resources.class.getName(); + } + + @Override + public long getMaxQuantity() { + return maxQuantity; + } + + public void setMaxQuantity(long maxQuantity) { + this.maxQuantity = maxQuantity; + } + + @Override + public long getMillisPerUnit() { + return millisPerUnit; + } + + public void setMillisPerUnit(long millisPerUnit) { + this.millisPerUnit = millisPerUnit; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_ar.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_ar.java new file mode 100644 index 0000000..1bdadc5 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_ar.java @@ -0,0 +1,110 @@ +package org.xbib.time.pretty.i18n; + +import java.util.HashMap; +import java.util.Map; + +/** + * + */ +public class Resources_ar extends MapResourceBundle { + private static final Map map = new HashMap<>(); + + static { + map.put("CenturyPattern", "%n %u"); + map.put("CenturyFuturePrefix", ""); + map.put("CenturyFutureSuffix", " من الآن"); + map.put("CenturyPastPrefix", ""); + map.put("CenturyPastSuffix", " مضت"); + map.put("CenturySingularName", "قرن"); + map.put("CenturyPluralName", "قرون"); + map.put("DayPattern", "%n %u"); + map.put("DayFuturePrefix", ""); + map.put("DayFutureSuffix", " من الآن"); + map.put("DayPastPrefix", ""); + map.put("DayPastSuffix", " مضت"); + map.put("DaySingularName", "يوم"); + map.put("DayPluralName", "ايام"); + map.put("DecadePattern", "%n %u"); + map.put("DecadeFuturePrefix", ""); + map.put("DecadeFutureSuffix", " من الآن"); + map.put("DecadePastPrefix", ""); + map.put("DecadePastSuffix", " مضت"); + map.put("DecadeSingularName", "عقد"); + map.put("DecadePluralName", "عقود"); + map.put("HourPattern", "%n %u"); + map.put("HourFuturePrefix", ""); + map.put("HourFutureSuffix", " من الآن"); + map.put("HourPastPrefix", ""); + map.put("HourPastSuffix", " مضت"); + map.put("HourSingularName", "ساعة"); + map.put("HourPluralName", "ساعات"); + map.put("JustNowPattern", "%u"); + map.put("JustNowFuturePrefix", ""); + map.put("JustNowFutureSuffix", "بعد لحظات"); + map.put("JustNowPastPrefix", "منذ لحظات"); + map.put("JustNowPastSuffix", ""); + map.put("JustNowSingularName", ""); + map.put("JustNowPluralName", ""); + map.put("MillenniumPattern", "%n %u"); + map.put("MillenniumFuturePrefix", ""); + map.put("MillenniumFutureSuffix", " من الآن"); + map.put("MillenniumPastPrefix", ""); + map.put("MillenniumPastSuffix", " مضت"); + map.put("MillenniumSingularName", "جيل"); + map.put("MillenniumPluralName", "اجيال"); + map.put("MillisecondPattern", "%n %u"); + map.put("MillisecondFuturePrefix", ""); + map.put("MillisecondFutureSuffix", " من الآن"); + map.put("MillisecondPastPrefix", ""); + map.put("MillisecondPastSuffix", " مضت"); + map.put("MillisecondSingularName", "جزء من الثانية"); + map.put("MillisecondPluralName", "اجزاء من الثانية"); + map.put("MinutePattern", "%n %u"); + map.put("MinuteFuturePrefix", ""); + map.put("MinuteFutureSuffix", " من الآن"); + map.put("MinutePastPrefix", ""); + map.put("MinutePastSuffix", " مضت"); + map.put("MinuteSingularName", "دقيقة"); + map.put("MinutePluralName", "دقائق"); + map.put("MonthPattern", "%n %u"); + map.put("MonthFuturePrefix", ""); + map.put("MonthFutureSuffix", " من الآن"); + map.put("MonthPastPrefix", ""); + map.put("MonthPastSuffix", " مضت"); + map.put("MonthSingularName", "شهر"); + map.put("MonthPluralName", "أشهر"); + map.put("SecondPattern", "%n %u"); + map.put("SecondFuturePrefix", ""); + map.put("SecondFutureSuffix", " من الآن"); + map.put("SecondPastPrefix", ""); + map.put("SecondPastSuffix", " مضت"); + map.put("SecondSingularName", "ثانية"); + map.put("SecondPluralName", "ثوان"); + map.put("WeekPattern", "%n %u"); + map.put("WeekFuturePrefix", ""); + map.put("WeekFutureSuffix", " من الآن"); + map.put("WeekPastPrefix", ""); + map.put("WeekPastSuffix", " مضت"); + map.put("WeekSingularName", "أسبوع"); + map.put("WeekPluralName", "أسابيع"); + map.put("YearPattern", "%n %u"); + map.put("YearFuturePrefix", ""); + map.put("YearFutureSuffix", " من الآن"); + map.put("YearPastPrefix", ""); + map.put("YearPastSuffix", " مضت"); + map.put("YearSingularName", "سنة"); + map.put("YearPluralName", "سنوات"); + map.put("AbstractTimeUnitPattern", ""); + map.put("AbstractTimeUnitFuturePrefix", ""); + map.put("AbstractTimeUnitFutureSuffix", ""); + map.put("AbstractTimeUnitPastPrefix", ""); + map.put("AbstractTimeUnitPastSuffix", ""); + map.put("AbstractTimeUnitSingularName", ""); + map.put("AbstractTimeUnitPluralName", ""); + } + + @Override + public Map getContents() { + return map; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_bg.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_bg.java new file mode 100644 index 0000000..a1c76ee --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_bg.java @@ -0,0 +1,112 @@ +package org.xbib.time.pretty.i18n; + +import java.util.HashMap; +import java.util.Map; + +/** + * + */ +public class Resources_bg extends MapResourceBundle { + + private static final Map map = new HashMap<>(); + + static { + map.put("CenturyPattern", "%n %u"); + map.put("CenturyFuturePrefix", "след "); + map.put("CenturyFutureSuffix", ""); + map.put("CenturyPastPrefix", "преди "); + map.put("CenturyPastSuffix", ""); + map.put("CenturySingularName", "век"); + map.put("CenturyPluralName", "века"); + map.put("DayPattern", "%n %u"); + map.put("DayFuturePrefix", "след "); + map.put("DayFutureSuffix", ""); + map.put("DayPastPrefix", "преди "); + map.put("DayPastSuffix", ""); + map.put("DaySingularName", "ден"); + map.put("DayPluralName", "дни"); + map.put("DecadePattern", "%n %u"); + map.put("DecadeFuturePrefix", "след "); + map.put("DecadeFutureSuffix", ""); + map.put("DecadePastPrefix", "преди "); + map.put("DecadePastSuffix", ""); + map.put("DecadeSingularName", "десетилетие"); + map.put("DecadePluralName", "десетилетия"); + map.put("HourPattern", "%n %u"); + map.put("HourFuturePrefix", "след "); + map.put("HourFutureSuffix", ""); + map.put("HourPastPrefix", "преди "); + map.put("HourPastSuffix", ""); + map.put("HourSingularName", "час"); + map.put("HourPluralName", "часа"); + map.put("JustNowPattern", "%u"); + map.put("JustNowFuturePrefix", ""); + map.put("JustNowFutureSuffix", "в момента"); + map.put("JustNowPastPrefix", "току що"); + map.put("JustNowPastSuffix", ""); + map.put("JustNowSingularName", ""); + map.put("JustNowPluralName", ""); + map.put("MillenniumPattern", "%n %u"); + map.put("MillenniumFuturePrefix", "след "); + map.put("MillenniumFutureSuffix", ""); + map.put("MillenniumPastPrefix", "преди "); + map.put("MillenniumPastSuffix", ""); + map.put("MillenniumSingularName", "хилядолетие"); + map.put("MillenniumPluralName", "хилядолетия"); + map.put("MillisecondPattern", "%n %u"); + map.put("MillisecondFuturePrefix", "след "); + map.put("MillisecondFutureSuffix", ""); + map.put("MillisecondPastPrefix", "преди "); + map.put("MillisecondPastSuffix", ""); + map.put("MillisecondSingularName", "милисекунда"); + map.put("MillisecondPluralName", "милисекунди"); + map.put("MinutePattern", "%n %u"); + map.put("MinuteFuturePrefix", "след "); + map.put("MinuteFutureSuffix", ""); + map.put("MinutePastPrefix", "преди "); + map.put("MinutePastSuffix", ""); + map.put("MinuteSingularName", "минута"); + map.put("MinutePluralName", "минути"); + map.put("MonthPattern", "%n %u"); + map.put("MonthFuturePrefix", "след "); + map.put("MonthFutureSuffix", ""); + map.put("MonthPastPrefix", "преди "); + map.put("MonthPastSuffix", ""); + map.put("MonthSingularName", "месец"); + map.put("MonthPluralName", "месеца"); + map.put("SecondPattern", "%n %u"); + map.put("SecondFuturePrefix", "след "); + map.put("SecondFutureSuffix", ""); + map.put("SecondPastPrefix", "преди "); + map.put("SecondPastSuffix", ""); + map.put("SecondSingularName", "секунда"); + map.put("SecondPluralName", "секунди"); + map.put("WeekPattern", "%n %u"); + map.put("WeekFuturePrefix", "след "); + map.put("WeekFutureSuffix", ""); + map.put("WeekPastPrefix", "преди "); + map.put("WeekPastSuffix", ""); + map.put("WeekSingularName", "седмица"); + map.put("WeekPluralName", "седмици"); + map.put("YearPattern", "%n %u"); + map.put("YearFuturePrefix", "след "); + map.put("YearFutureSuffix", ""); + map.put("YearPastPrefix", "преди "); + map.put("YearPastSuffix", ""); + map.put("YearSingularName", "година"); + map.put("YearPluralName", "години"); + map.put("AbstractTimeUnitPattern", ""); + map.put("AbstractTimeUnitFuturePrefix", ""); + map.put("AbstractTimeUnitFutureSuffix", ""); + map.put("AbstractTimeUnitPastPrefix", ""); + map.put("AbstractTimeUnitPastSuffix", ""); + map.put("AbstractTimeUnitSingularName", ""); + map.put("AbstractTimeUnitPluralName", ""); + } + + @Override + public Map getContents() { + return map; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_ca.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_ca.java new file mode 100644 index 0000000..d5118a8 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_ca.java @@ -0,0 +1,108 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_ca extends ListResourceBundle { + + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", "dintre de "}, + {"CenturyFutureSuffix", ""}, + {"CenturyPastPrefix", "fa "}, + {"CenturyPastSuffix", ""}, + {"CenturySingularName", "segle"}, + {"CenturyPluralName", "segles"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", "dintre de "}, + {"DayFutureSuffix", ""}, + {"DayPastPrefix", "fa "}, + {"DayPastSuffix", ""}, + {"DaySingularName", "dia"}, + {"DayPluralName", "dies"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", "dintre de "}, + {"DecadeFutureSuffix", ""}, + {"DecadePastPrefix", "fa "}, + {"DecadePastSuffix", ""}, + {"DecadeSingularName", "deseni"}, + {"DecadePluralName", "desenis"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", "dintre de "}, + {"HourFutureSuffix", ""}, + {"HourPastPrefix", "fa "}, + {"HourPastSuffix", ""}, + {"HourSingularName", "hora"}, + {"HourPluralName", "hores"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "en un instant"}, + {"JustNowPastPrefix", "fa uns instants"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", "dintre de "}, + {"MillenniumFutureSuffix", ""}, + {"MillenniumPastPrefix", "fa "}, + {"MillenniumPastSuffix", ""}, + {"MillenniumSingularName", "mileni"}, + {"MillenniumPluralName", "milenis"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", "dintre de "}, + {"MillisecondFutureSuffix", ""}, + {"MillisecondPastPrefix", "fa "}, + {"MillisecondPastSuffix", ""}, + {"MillisecondSingularName", "mil·lisegon"}, + {"MillisecondPluralName", "mil·lisegons"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", "dintre de "}, + {"MinuteFutureSuffix", ""}, + {"MinutePastPrefix", "fa "}, + {"MinutePastSuffix", ""}, + {"MinuteSingularName", "minut"}, + {"MinutePluralName", "minuts"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", "dintre de "}, + {"MonthFutureSuffix", ""}, + {"MonthPastPrefix", "fa "}, + {"MonthPastSuffix", ""}, + {"MonthSingularName", "mes"}, + {"MonthPluralName", "mesos"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", "dintre de "}, + {"SecondFutureSuffix", ""}, + {"SecondPastPrefix", "fa "}, + {"SecondPastSuffix", ""}, + {"SecondSingularName", "segon"}, + {"SecondPluralName", "segons"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", "dintre de "}, + {"WeekFutureSuffix", ""}, + {"WeekPastPrefix", "fa "}, + {"WeekPastSuffix", ""}, + {"WeekSingularName", "setmana"}, + {"WeekPluralName", "setmanes"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", "dintre de "}, + {"YearFutureSuffix", ""}, + {"YearPastPrefix", "fa "}, + {"YearPastSuffix", ""}, + {"YearSingularName", "any"}, + {"YearPluralName", "anys"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + + @Override + protected Object[][] getContents() { + return OBJECTS; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_cs.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_cs.java new file mode 100644 index 0000000..7c885fe --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_cs.java @@ -0,0 +1,341 @@ +package org.xbib.time.pretty.i18n; + +import org.xbib.time.pretty.SimpleTimeFormat; +import org.xbib.time.pretty.TimeFormat; +import org.xbib.time.pretty.TimeFormatProvider; +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.TimeUnitQuantity; +import org.xbib.time.pretty.units.Day; +import org.xbib.time.pretty.units.Hour; +import org.xbib.time.pretty.units.Minute; +import org.xbib.time.pretty.units.Month; +import org.xbib.time.pretty.units.Week; +import org.xbib.time.pretty.units.Year; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.ListResourceBundle; +import java.util.ResourceBundle; + +/** + * + */ +public class Resources_cs extends ListResourceBundle implements TimeFormatProvider { + private static final Object[][] OBJECTS = new Object[][]{ + + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", "za "}, + {"CenturyFutureSuffix", ""}, + {"CenturyPastPrefix", "před "}, + {"CenturyPastSuffix", ""}, + {"CenturySingularName", "století"}, + {"CenturyPluralName", "století"}, + {"CenturyPastSingularName", "stoletím"}, + {"CenturyPastPluralName", "stoletími"}, + {"CenturyFutureSingularName", "století"}, + {"CenturyFuturePluralName", "století"}, + + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", "za "}, + {"DayFutureSuffix", ""}, + {"DayPastPrefix", "před "}, + {"DayPastSuffix", ""}, + {"DaySingularName", "den"}, + {"DayPluralName", "dny"}, + + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", "za "}, + {"DecadeFutureSuffix", ""}, + {"DecadePastPrefix", "před "}, + {"DecadePastSuffix", ""}, + {"DecadeSingularName", "desetiletí"}, + {"DecadePluralName", "desetiletí"}, + {"DecadePastSingularName", "desetiletím"}, + {"DecadePastPluralName", "desetiletími"}, + {"DecadeFutureSingularName", "desetiletí"}, + {"DecadeFuturePluralName", "desetiletí"}, + + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", "za "}, + {"HourFutureSuffix", ""}, + {"HourPastPrefix", "před"}, + {"HourPastSuffix", ""}, + {"HourSingularName", "hodina"}, + {"HourPluralName", "hodiny"}, + + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "za chvíli"}, + {"JustNowPastPrefix", "před chvílí"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", "za "}, + {"MillenniumFutureSuffix", ""}, + {"MillenniumPastPrefix", "před "}, + {"MillenniumPastSuffix", ""}, + {"MillenniumSingularName", "tisíciletí"}, + {"MillenniumPluralName", "tisíciletí"}, + + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", "za "}, + {"MillisecondFutureSuffix", ""}, + {"MillisecondPastPrefix", "před "}, + {"MillisecondPastSuffix", ""}, + {"MillisecondSingularName", "milisekunda"}, + {"MillisecondPluralName", "milisekundy"}, + {"MillisecondPastSingularName", "milisekundou"}, + {"MillisecondPastPluralName", "milisekundami"}, + {"MillisecondFutureSingularName", "milisekundu"}, + {"MillisecondFuturePluralName", "milisekund"}, + + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", "za "}, + {"MinuteFutureSuffix", ""}, + {"MinutePastPrefix", "před "}, + {"MinutePastSuffix", ""}, + {"MinuteSingularName", "minuta"}, + {"MinutePluralName", "minuty"}, + + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", "za "}, + {"MonthFutureSuffix", ""}, + {"MonthPastPrefix", "před "}, + {"MonthPastSuffix", ""}, + {"MonthSingularName", "měsíc"}, + {"MonthPluralName", "měsíce"}, + + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", "za "}, + {"SecondFutureSuffix", ""}, + {"SecondPastPrefix", "před "}, + {"SecondPastSuffix", ""}, + {"SecondSingularName", "sekunda"}, + {"SecondPluralName", "sekundy"}, + + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", "za "}, + {"WeekFutureSuffix", ""}, + {"WeekPastPrefix", "před "}, + {"WeekPastSuffix", ""}, + {"WeekSingularName", "týden"}, + {"WeekPluralName", "týdny"}, + + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", "za "}, + {"YearFutureSuffix", ""}, + {"YearPastPrefix", "před "}, + {"YearPastSuffix", ""}, + {"YearSingularName", "rok"}, + {"YearPluralName", "roky"}, + + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } + + @Override + public TimeFormat getFormatFor(TimeUnit t) { + if (t instanceof Minute) { + return new CsTimeFormatBuilder("Minute") + .addFutureName("minutu", 1) + .addFutureName("minuty", 4) + .addFutureName("minut", Long.MAX_VALUE) + .addPastName("minutou", 1) + .addPastName("minutami", Long.MAX_VALUE) + .build(this); + } else if (t instanceof Hour) { + return new CsTimeFormatBuilder("Hour") + .addFutureName("hodinu", 1) + .addFutureName("hodiny", 4) + .addFutureName("hodin", Long.MAX_VALUE) + .addPastName("hodinou", 1) + .addPastName("hodinami", Long.MAX_VALUE) + .build(this); + } else if (t instanceof Day) { + return new CsTimeFormatBuilder("Day") + .addFutureName("den", 1) + .addFutureName("dny", 4) + .addFutureName("dní", Long.MAX_VALUE) + .addPastName("dnem", 1) + .addPastName("dny", Long.MAX_VALUE) + .build(this); + } else if (t instanceof Week) { + return new CsTimeFormatBuilder("Week") + .addFutureName("týden", 1) + .addFutureName("týdny", 4) + .addFutureName("týdnů", Long.MAX_VALUE) + .addPastName("týdnem", 1) + .addPastName("týdny", Long.MAX_VALUE) + .build(this); + } else if (t instanceof Month) { + return new CsTimeFormatBuilder("Month") + .addFutureName("měsíc", 1) + .addFutureName("měsíce", 4) + .addFutureName("měsíců", Long.MAX_VALUE) + .addPastName("měsícem", 1) + .addPastName("měsíci", Long.MAX_VALUE) + .build(this); + } else if (t instanceof Year) { + return new CsTimeFormatBuilder("Year") + .addFutureName("rok", 1) + .addFutureName("roky", 4) + .addFutureName("let", Long.MAX_VALUE) + .addPastName("rokem", 1) + .addPastName("roky", Long.MAX_VALUE) + .build(this); + } + // Don't override format for other time units + return null; + } + + private static class CsTimeFormatBuilder { + + private List names = new ArrayList(); + + private String resourceKeyPrefix; + + CsTimeFormatBuilder(String resourceKeyPrefix) { + this.resourceKeyPrefix = resourceKeyPrefix; + } + + CsTimeFormatBuilder addFutureName(String name, long limit) { + return addName(true, name, limit); + } + + CsTimeFormatBuilder addPastName(String name, long limit) { + return addName(false, name, limit); + } + + private CsTimeFormatBuilder addName(boolean isFuture, String name, long limit) { + if (name == null) { + throw new IllegalArgumentException(); + } + names.add(new CsName(isFuture, name, limit)); + return this; + } + + CsTimeFormat build(final ResourceBundle bundle) { + return new CsTimeFormat(resourceKeyPrefix, bundle, names); + } + + } + + private static class CsTimeFormat extends SimpleTimeFormat implements TimeFormat { + + private final List futureNames = new ArrayList(); + + private final List pastNames = new ArrayList(); + + public CsTimeFormat(String resourceKeyPrefix, ResourceBundle bundle, Collection names) { + setPattern(bundle.getString(resourceKeyPrefix + "Pattern")); + setFuturePrefix(bundle.getString(resourceKeyPrefix + "FuturePrefix")); + setFutureSuffix(bundle.getString(resourceKeyPrefix + "FutureSuffix")); + setPastPrefix(bundle.getString(resourceKeyPrefix + "PastPrefix")); + setPastSuffix(bundle.getString(resourceKeyPrefix + "PastSuffix")); + setSingularName(bundle.getString(resourceKeyPrefix + "SingularName")); + setPluralName(bundle.getString(resourceKeyPrefix + "PluralName")); + + String key = resourceKeyPrefix + "FuturePluralName"; + if (bundle.containsKey(key)) { + setFuturePluralName(bundle.getString(key)); + } + key = resourceKeyPrefix + "FutureSingularName"; + if (bundle.containsKey(key)) { + setFutureSingularName((bundle.getString(key))); + } + key = resourceKeyPrefix + "PastPluralName"; + if (bundle.containsKey(key)) { + setPastPluralName((bundle.getString(key))); + } + key = resourceKeyPrefix + "PastSingularName"; + if (bundle.containsKey(key)) { + setPastSingularName((bundle.getString(key))); + } + + for (CsName name : names) { + if (name.isFuture()) { + futureNames.add(name); + } else { + pastNames.add(name); + } + } + Collections.sort(futureNames); + Collections.sort(pastNames); + } + + @Override + protected String getGramaticallyCorrectName(TimeUnitQuantity d, boolean round) { + long quantity = Math.abs(getQuantity(d, round)); + if (d.isInFuture()) { + return getGramaticallyCorrectName(quantity, futureNames); + } + return getGramaticallyCorrectName(quantity, pastNames); + } + + private String getGramaticallyCorrectName(long quantity, List names) { + for (CsName name : names) { + if (name.getThreshold() >= quantity) { + return name.get(); + } + } + throw new IllegalStateException("Invalid resource bundle configuration"); + } + + } + + private static class CsName implements Comparable { + + private final boolean isFuture; + + private final String value; + + private final Long threshold; + + public CsName(boolean isFuture, String value, Long threshold) { + this.isFuture = isFuture; + this.value = value; + this.threshold = threshold; + } + + public boolean isFuture() { + return isFuture; + } + + public String get() { + return value; + } + + public long getThreshold() { + return threshold; + } + + @Override + public int compareTo(CsName o) { + return threshold.compareTo(o.getThreshold()); + } + + @Override + public boolean equals(Object o) { + return o instanceof CsName && threshold.equals(((CsName) o).getThreshold()); + } + + @Override + public int hashCode() { + return threshold.hashCode(); + } + } + +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_da.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_da.java new file mode 100644 index 0000000..99046dd --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_da.java @@ -0,0 +1,106 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_da extends ListResourceBundle { + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", ""}, + {"CenturyFutureSuffix", " fra nu"}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", " siden"}, + {"CenturySingularName", "århundrede"}, + {"CenturyPluralName", "århundreder"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", "om "}, + {"DayFutureSuffix", ""}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", " siden"}, + {"DaySingularName", "dag"}, + {"DayPluralName", "dage"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", ""}, + {"DecadeFutureSuffix", " fra nu"}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", " siden"}, + {"DecadeSingularName", "årti"}, + {"DecadePluralName", "årtier"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", "om "}, + {"HourFutureSuffix", ""}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", " siden"}, + {"HourSingularName", "time"}, + {"HourPluralName", "timer"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", "straks"}, + {"JustNowFutureSuffix", ""}, + {"JustNowPastPrefix", "et øjeblik siden"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", ""}, + {"MillenniumFutureSuffix", " fra nu"}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", " siden"}, + {"MillenniumSingularName", "millennium"}, + {"MillenniumPluralName", "millennier"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", "om "}, + {"MillisecondFutureSuffix", ""}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", " siden"}, + {"MillisecondSingularName", "millisekund"}, + {"MillisecondPluralName", "millisekunder"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", "om "}, + {"MinuteFutureSuffix", ""}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", " siden"}, + {"MinuteSingularName", "minut"}, + {"MinutePluralName", "minutter"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", "om "}, + {"MonthFutureSuffix", ""}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", " siden"}, + {"MonthSingularName", "måned"}, + {"MonthPluralName", "måneder"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", "om "}, + {"SecondFutureSuffix", ""}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", " siden"}, + {"SecondSingularName", "sekund"}, + {"SecondPluralName", "sekunder"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", "om "}, + {"WeekFutureSuffix", ""}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", " siden"}, + {"WeekSingularName", "uge"}, + {"WeekPluralName", "uger"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", "om "}, + {"YearFutureSuffix", ""}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", " siden"}, + {"YearSingularName", "år"}, + {"YearPluralName", "år"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_de.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_de.java new file mode 100644 index 0000000..c644fe3 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_de.java @@ -0,0 +1,111 @@ +package org.xbib.time.pretty.i18n; + +import java.util.HashMap; +import java.util.Map; + +/** + * + */ +public class Resources_de extends MapResourceBundle { + + private static final Map map = new HashMap<>(); + + static { + map.put("CenturyPattern", "%n %u"); + map.put("CenturyFuturePrefix", "in "); + map.put("CenturyFutureSuffix", ""); + map.put("CenturyPastPrefix", "vor "); + map.put("CenturyPastSuffix", ""); + map.put("CenturySingularName", "Jahrhundert"); + map.put("CenturyPluralName", "Jahrhunderten"); + map.put("DayPattern", "%n %u"); + map.put("DayFuturePrefix", "in "); + map.put("DayFutureSuffix", ""); + map.put("DayPastPrefix", "vor "); + map.put("DayPastSuffix", ""); + map.put("DaySingularName", "Tag"); + map.put("DayPluralName", "Tagen"); + map.put("DecadePattern", "%n %u"); + map.put("DecadeFuturePrefix", "in "); + map.put("DecadeFutureSuffix", ""); + map.put("DecadePastPrefix", "vor "); + map.put("DecadePastSuffix", ""); + map.put("DecadeSingularName", "Jahrzehnt"); + map.put("DecadePluralName", "Jahrzehnten"); + map.put("HourPattern", "%n %u"); + map.put("HourFuturePrefix", "in "); + map.put("HourFutureSuffix", ""); + map.put("HourPastPrefix", "vor "); + map.put("HourPastSuffix", ""); + map.put("HourSingularName", "Stunde"); + map.put("HourPluralName", "Stunden"); + map.put("JustNowPattern", "%u"); + map.put("JustNowFuturePrefix", "Jetzt"); + map.put("JustNowFutureSuffix", ""); + map.put("JustNowPastPrefix", "vor einem Augenblick"); + map.put("JustNowPastSuffix", ""); + map.put("JustNowSingularName", ""); + map.put("JustNowPluralName", ""); + map.put("MillenniumPattern", "%n %u"); + map.put("MillenniumFuturePrefix", "in "); + map.put("MillenniumFutureSuffix", ""); + map.put("MillenniumPastPrefix", "vor "); + map.put("MillenniumPastSuffix", ""); + map.put("MillenniumSingularName", "Jahrtausend"); + map.put("MillenniumPluralName", "Jahrtausenden"); + map.put("MillisecondPattern", "%n %u"); + map.put("MillisecondFuturePrefix", "in "); + map.put("MillisecondFutureSuffix", ""); + map.put("MillisecondPastPrefix", "vor "); + map.put("MillisecondPastSuffix", ""); + map.put("MillisecondSingularName", "Millisekunde"); + map.put("MillisecondPluralName", "Millisekunden"); + map.put("MinutePattern", "%n %u"); + map.put("MinuteFuturePrefix", "in "); + map.put("MinuteFutureSuffix", ""); + map.put("MinutePastPrefix", "vor "); + map.put("MinutePastSuffix", ""); + map.put("MinuteSingularName", "Minute"); + map.put("MinutePluralName", "Minuten"); + map.put("MonthPattern", "%n %u"); + map.put("MonthFuturePrefix", "in "); + map.put("MonthFutureSuffix", ""); + map.put("MonthPastPrefix", "vor "); + map.put("MonthPastSuffix", ""); + map.put("MonthSingularName", "Monat"); + map.put("MonthPluralName", "Monaten"); + map.put("SecondPattern", "%n %u"); + map.put("SecondFuturePrefix", "in "); + map.put("SecondFutureSuffix", ""); + map.put("SecondPastPrefix", "vor "); + map.put("SecondPastSuffix", ""); + map.put("SecondSingularName", "Sekunde"); + map.put("SecondPluralName", "Sekunden"); + map.put("WeekPattern", "%n %u"); + map.put("WeekFuturePrefix", "in "); + map.put("WeekFutureSuffix", ""); + map.put("WeekPastPrefix", "vor "); + map.put("WeekPastSuffix", ""); + map.put("WeekSingularName", "Woche"); + map.put("WeekPluralName", "Wochen"); + map.put("YearPattern", "%n %u"); + map.put("YearFuturePrefix", "in "); + map.put("YearFutureSuffix", ""); + map.put("YearPastPrefix", "vor "); + map.put("YearPastSuffix", ""); + map.put("YearSingularName", "Jahr"); + map.put("YearPluralName", "Jahren"); + map.put("AbstractTimeUnitPattern", ""); + map.put("AbstractTimeUnitFuturePrefix", ""); + map.put("AbstractTimeUnitFutureSuffix", ""); + map.put("AbstractTimeUnitPastPrefix", ""); + map.put("AbstractTimeUnitPastSuffix", ""); + map.put("AbstractTimeUnitSingularName", ""); + map.put("AbstractTimeUnitPluralName", ""); + } + + @Override + public Map getContents() { + return map; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_en.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_en.java new file mode 100644 index 0000000..01514c0 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_en.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_en extends ListResourceBundle { + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", ""}, + {"CenturyFutureSuffix", " from now"}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", " ago"}, + {"CenturySingularName", "century"}, + {"CenturyPluralName", "centuries"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", ""}, + {"DayFutureSuffix", " from now"}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", " ago"}, + {"DaySingularName", "day"}, + {"DayPluralName", "days"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", ""}, + {"DecadeFutureSuffix", " from now"}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", " ago"}, + {"DecadeSingularName", "decade"}, + {"DecadePluralName", "decades"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", ""}, + {"HourFutureSuffix", " from now"}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", " ago"}, + {"HourSingularName", "hour"}, + {"HourPluralName", "hours"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "moments from now"}, + {"JustNowPastPrefix", "moments ago"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", ""}, + {"MillenniumFutureSuffix", " from now"}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", " ago"}, + {"MillenniumSingularName", "millennium"}, + {"MillenniumPluralName", "millennia"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", ""}, + {"MillisecondFutureSuffix", " from now"}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", " ago"}, + {"MillisecondSingularName", "millisecond"}, + {"MillisecondPluralName", "milliseconds"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", ""}, + {"MinuteFutureSuffix", " from now"}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", " ago"}, + {"MinuteSingularName", "minute"}, + {"MinutePluralName", "minutes"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", ""}, + {"MonthFutureSuffix", " from now"}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", " ago"}, + {"MonthSingularName", "month"}, + {"MonthPluralName", "months"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", ""}, + {"SecondFutureSuffix", " from now"}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", " ago"}, + {"SecondSingularName", "second"}, + {"SecondPluralName", "seconds"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", ""}, + {"WeekFutureSuffix", " from now"}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", " ago"}, + {"WeekSingularName", "week"}, + {"WeekPluralName", "weeks"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", ""}, + {"YearFutureSuffix", " from now"}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", " ago"}, + {"YearSingularName", "year"}, + {"YearPluralName", "years"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_es.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_es.java new file mode 100644 index 0000000..731eac3 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_es.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_es extends ListResourceBundle { + + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", "dentro de "}, + {"CenturyFutureSuffix", ""}, + {"CenturyPastPrefix", "hace "}, + {"CenturyPastSuffix", ""}, + {"CenturySingularName", "siglo"}, + {"CenturyPluralName", "siglos"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", "dentro de "}, + {"DayFutureSuffix", ""}, + {"DayPastPrefix", "hace "}, + {"DayPastSuffix", ""}, + {"DaySingularName", "día "}, + {"DayPluralName", "días"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", "dentro de "}, + {"DecadeFutureSuffix", ""}, + {"DecadePastPrefix", "hace "}, + {"DecadePastSuffix", ""}, + {"DecadeSingularName", "decenio"}, + {"DecadePluralName", "decenios"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", "dentro de "}, + {"HourFutureSuffix", ""}, + {"HourPastPrefix", "hace "}, + {"HourPastSuffix", ""}, + {"HourSingularName", "hora"}, + {"HourPluralName", "horas"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "en un instante"}, + {"JustNowPastPrefix", "hace instantes"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", "dentro de "}, + {"MillenniumFutureSuffix", ""}, + {"MillenniumPastPrefix", "hace "}, + {"MillenniumPastSuffix", ""}, + {"MillenniumSingularName", "milenario"}, + {"MillenniumPluralName", "milenarios"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", "dentro de "}, + {"MillisecondFutureSuffix", ""}, + {"MillisecondPastPrefix", "hace "}, + {"MillisecondPastSuffix", ""}, + {"MillisecondSingularName", "milisegundo"}, + {"MillisecondPluralName", "milisegundo"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", "dentro de "}, + {"MinuteFutureSuffix", ""}, + {"MinutePastPrefix", "hace "}, + {"MinutePastSuffix", ""}, + {"MinuteSingularName", "minuto"}, + {"MinutePluralName", "minutos"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", "dentro de "}, + {"MonthFutureSuffix", ""}, + {"MonthPastPrefix", "hace "}, + {"MonthPastSuffix", ""}, + {"MonthSingularName", "mes"}, + {"MonthPluralName", "meses"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", "dentro de "}, + {"SecondFutureSuffix", ""}, + {"SecondPastPrefix", "hace "}, + {"SecondPastSuffix", ""}, + {"SecondSingularName", "segundo"}, + {"SecondPluralName", "segundos"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", "dentro de "}, + {"WeekFutureSuffix", ""}, + {"WeekPastPrefix", "hace "}, + {"WeekPastSuffix", ""}, + {"WeekSingularName", "semana"}, + {"WeekPluralName", "semanas"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", "dentro de "}, + {"YearFutureSuffix", ""}, + {"YearPastPrefix", "hace "}, + {"YearPastSuffix", ""}, + {"YearSingularName", "año"}, + {"YearPluralName", "años"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + protected Object[][] getContents() { + return OBJECTS; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_et.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_et.java new file mode 100644 index 0000000..509ca03 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_et.java @@ -0,0 +1,108 @@ +package org.xbib.time.pretty.i18n; + +/** + * + */ +public class Resources_et extends Resources_fi { + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%u"}, + {"CenturyPluralPattern", "%n %u"}, + {"CenturyPastSingularName", "sajand"}, + {"CenturyPastPluralName", "sajandit"}, + {"CenturyFutureSingularName", "sajandi"}, + {"CenturyPastSuffix", "tagasi"}, + {"CenturyFutureSuffix", "pärast"}, + {"DayPattern", "%u"}, + {"DayPluralPattern", "%n %u"}, + {"DayPastSingularName", "eile"}, + {"DayPastPluralName", "päeva"}, + {"DayFutureSingularName", "homme"}, + {"DayFuturePluralName", "päeva"}, + {"DayPastSuffix", "tagasi"}, + {"DayFutureSuffix", "pärast"}, + {"DecadePattern", "%u"}, + {"DecadePluralPattern", "%n %u"}, + {"DecadePastSingularName", "aastakümme"}, + {"DecadePastPluralName", "aastakümmet"}, + {"DecadeFutureSingularName", "aastakümne"}, + {"DecadePastSuffix", "tagasi"}, + {"DecadeFutureSuffix", "pärast"}, + {"HourPattern", "%u"}, + {"HourPluralPattern", "%n %u"}, + {"HourPastSingularName", "tund"}, + {"HourPastPluralName", "tundi"}, + {"HourFutureSingularName", "tunni"}, + {"HourPastSuffix", "tagasi"}, + {"HourFutureSuffix", "pärast"}, + {"JustNowPattern", "%u"}, + {"JustNowPastSingularName", "hetk"}, + {"JustNowFutureSingularName", "hetke"}, + {"JustNowPastSuffix", "tagasi"}, + {"JustNowFutureSuffix", "pärast"}, + {"MillenniumPattern", "%u"}, + {"MillenniumPluralPattern", "%n %u"}, + {"MillenniumPastSingularName", "aastatuhat"}, + {"MillenniumPastPluralName", "aastatuhandet"}, + {"MillenniumFutureSingularName", "aastatuhande"}, + {"MillenniumPastSuffix", "tagasi"}, + {"MillenniumFutureSuffix", "pärast"}, + {"MillisecondPattern", "%u"}, + {"MillisecondPluralPattern", "%n %u"}, + {"MillisecondPastSingularName", "millisekund"}, + {"MillisecondPastPluralName", "millisekundit"}, + {"MillisecondFutureSingularName", "millisekundi"}, + {"MillisecondFuturePluralName", "millisekundi"}, + {"MillisecondPastSuffix", "tagasi"}, + {"MillisecondFutureSuffix", "pärast"}, + {"MinutePattern", "%u"}, + {"MinutePluralPattern", "%n %u"}, + {"MinutePastSingularName", "minut"}, + {"MinutePastPluralName", "minutit"}, + {"MinuteFutureSingularName", "minuti"}, + {"MinuteFuturePluralName", "minuti"}, + {"MinutePastSuffix", "tagasi"}, + {"MinuteFutureSuffix", "pärast"}, + {"MonthPattern", "%u"}, + {"MonthPluralPattern", "%n %u"}, + {"MonthPastSingularName", "kuu"}, + {"MonthPastPluralName", "kuud"}, + {"MonthFutureSingularName", "kuu"}, + {"MonthPastSuffix", "tagasi"}, + {"MonthFutureSuffix", "pärast"}, + {"SecondPattern", "%u"}, + {"SecondPluralPattern", "%n %u"}, + {"SecondPastSingularName", "sekund"}, + {"SecondPastPluralName", "sekundit"}, + {"SecondFutureSingularName", "sekundi"}, + {"SecondFuturePluralName", "sekundi"}, + {"SecondPastSuffix", "tagasi"}, + {"SecondFutureSuffix", "pärast"}, + {"WeekPattern", "%u"}, + {"WeekPluralPattern", "%n %u"}, + {"WeekPastSingularName", "nädal"}, + {"WeekPastPluralName", "nädalat"}, + {"WeekFutureSingularName", "nädala"}, + {"WeekFuturePluralName", "nädala"}, + {"WeekPastSuffix", "tagasi"}, + {"WeekFutureSuffix", "pärast"}, + {"YearPattern", "%u"}, + {"YearPluralPattern", "%n %u"}, + {"YearPastSingularName", "aasta"}, + {"YearPastPluralName", "aastat"}, + {"YearFutureSingularName", "aasta"}, + {"YearFuturePluralName", "aasta"}, + {"YearPastSuffix", "tagasi"}, + {"YearFutureSuffix", "pärast"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + protected Object[][] getContents() { + return OBJECTS; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_fa.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_fa.java new file mode 100644 index 0000000..4e6b0aa --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_fa.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_fa extends ListResourceBundle { + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", ""}, + {"CenturyFutureSuffix", " دیگر"}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", " پیش"}, + {"CenturySingularName", "قرن"}, + {"CenturyPluralName", "قرن"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", ""}, + {"DayFutureSuffix", " دیگر"}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", " پیش"}, + {"DaySingularName", "روز"}, + {"DayPluralName", "روز"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", ""}, + {"DecadeFutureSuffix", " دیگر"}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", " پیش"}, + {"DecadeSingularName", "دهه"}, + {"DecadePluralName", "دهه"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", ""}, + {"HourFutureSuffix", " دیگر"}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", " پیش"}, + {"HourSingularName", "ساعت"}, + {"HourPluralName", "ساعت"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "چند لحظه دیگر"}, + {"JustNowPastPrefix", "چند لحظه پیش"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", ""}, + {"MillenniumFutureSuffix", " دیگر"}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", " پیش"}, + {"MillenniumSingularName", "هزاره"}, + {"MillenniumPluralName", "هزار سال"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", ""}, + {"MillisecondFutureSuffix", " دیگر"}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", " پیش"}, + {"MillisecondSingularName", "میلی ثانیه"}, + {"MillisecondPluralName", "میلی ثانیه"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", ""}, + {"MinuteFutureSuffix", " دیگر"}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", " پیش"}, + {"MinuteSingularName", "دقیقه"}, + {"MinutePluralName", "دقیقه"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", ""}, + {"MonthFutureSuffix", " دیگر"}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", " پیش"}, + {"MonthSingularName", "ماه"}, + {"MonthPluralName", "ماه"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", ""}, + {"SecondFutureSuffix", " دیگر"}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", " پیش"}, + {"SecondSingularName", "ثانیه"}, + {"SecondPluralName", "ثانیه"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", ""}, + {"WeekFutureSuffix", " دیگر"}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", " پیش"}, + {"WeekSingularName", "هفته"}, + {"WeekPluralName", "هفته"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", ""}, + {"YearFutureSuffix", " دیگر"}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", " پیش"}, + {"YearSingularName", "سال"}, + {"YearPluralName", "سال"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_fi.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_fi.java new file mode 100644 index 0000000..a65f4d3 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_fi.java @@ -0,0 +1,246 @@ +package org.xbib.time.pretty.i18n; + +import org.xbib.time.pretty.SimpleTimeFormat; +import org.xbib.time.pretty.TimeFormat; +import org.xbib.time.pretty.TimeFormatProvider; +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.TimeUnitQuantity; +import org.xbib.time.pretty.units.Day; + +import java.util.ListResourceBundle; +import java.util.ResourceBundle; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * + */ +public class Resources_fi extends ListResourceBundle implements TimeFormatProvider { + + private static Object[][] contents = new Object[][]{ + {"JustNowPattern", "%u"}, + {"JustNowPastSingularName", "hetki"}, + {"JustNowFutureSingularName", "hetken"}, + {"JustNowPastSuffix", "sitten"}, + {"JustNowFutureSuffix", "päästä"}, + {"MillisecondPattern", "%u"}, + {"MillisecondPluralPattern", "%n %u"}, + {"MillisecondPastSingularName", "millisekunti"}, + {"MillisecondPastPluralName", "millisekuntia"}, + {"MillisecondFutureSingularName", "millisekunnin"}, + {"MillisecondPastSuffix", "sitten"}, + {"MillisecondFutureSuffix", "päästä"}, + {"SecondPattern", "%u"}, + {"SecondPluralPattern", "%n %u"}, + {"SecondPastSingularName", "sekunti"}, + {"SecondPastPluralName", "sekuntia"}, + {"SecondFutureSingularName", "sekunnin"}, + {"SecondPastSuffix", "sitten"}, + {"SecondFutureSuffix", "päästä"}, + {"MinutePattern", "%u"}, + {"MinutePluralPattern", "%n %u"}, + {"MinutePastSingularName", "minuutti"}, + {"MinutePastPluralName", "minuuttia"}, + {"MinuteFutureSingularName", "minuutin"}, + {"MinutePastSuffix", "sitten"}, + {"MinuteFutureSuffix", "päästä"}, + {"HourPattern", "%u"}, + {"HourPluralPattern", "%n %u"}, + {"HourPastSingularName", "tunti"}, + {"HourPastPluralName", "tuntia"}, + {"HourFutureSingularName", "tunnin"}, + {"HourPastSuffix", "sitten"}, + {"HourFutureSuffix", "päästä"}, + {"DayPattern", "%u"}, + {"DayPluralPattern", "%n %u"}, + {"DayPastSingularName", "eilen"}, + {"DayPastPluralName", "päivää"}, + {"DayFutureSingularName", "huomenna"}, + {"DayFuturePluralName", "päivän"}, + {"DayPastSuffix", "sitten"}, + {"DayFutureSuffix", "päästä"}, + {"WeekPattern", "%u"}, + {"WeekPluralPattern", "%n %u"}, + {"WeekPastSingularName", "viikko"}, + {"WeekPastPluralName", "viikkoa"}, + {"WeekFutureSingularName", "viikon"}, + {"WeekFuturePluralName", "viikon"}, + {"WeekPastSuffix", "sitten"}, + {"WeekFutureSuffix", "päästä"}, + {"MonthPattern", "%u"}, + {"MonthPluralPattern", "%n %u"}, + {"MonthPastSingularName", "kuukausi"}, + {"MonthPastPluralName", "kuukautta"}, + {"MonthFutureSingularName", "kuukauden"}, + {"MonthPastSuffix", "sitten"}, + {"MonthFutureSuffix", "päästä"}, + {"YearPattern", "%u"}, + {"YearPluralPattern", "%n %u"}, + {"YearPastSingularName", "vuosi"}, + {"YearPastPluralName", "vuotta"}, + {"YearFutureSingularName", "vuoden"}, + {"YearPastSuffix", "sitten"}, + {"YearFutureSuffix", "päästä"}, + {"DecadePattern", "%u"}, + {"DecadePluralPattern", "%n %u"}, + {"DecadePastSingularName", "vuosikymmen"}, + {"DecadePastPluralName", "vuosikymmentä"}, + {"DecadeFutureSingularName", "vuosikymmenen"}, + {"DecadePastSuffix", "sitten"}, + {"DecadeFutureSuffix", "päästä"}, + {"CenturyPattern", "%u"}, + {"CenturyPluralPattern", "%n %u"}, + {"CenturyPastSingularName", "vuosisata"}, + {"CenturyPastPluralName", "vuosisataa"}, + {"CenturyFutureSingularName", "vuosisadan"}, + {"CenturyPastSuffix", "sitten"}, + {"CenturyFutureSuffix", "päästä"}, + {"MillenniumPattern", "%u"}, + {"MillenniumPluralPattern", "%n %u"}, + {"MillenniumPastSingularName", "vuosituhat"}, + {"MillenniumPastPluralName", "vuosituhatta"}, + {"MillenniumFutureSingularName", "vuosituhannen"}, + {"MillenniumPastSuffix", "sitten"}, + {"MillenniumFutureSuffix", "päästä"}, + }; + private volatile ConcurrentMap formatMap = new ConcurrentHashMap(); + + public Resources_fi() { + } + + @Override + public TimeFormat getFormatFor(TimeUnit t) { + if (!formatMap.containsKey(t)) { + formatMap.putIfAbsent(t, new FiTimeFormat(this, t)); + } + return formatMap.get(t); + } + + @Override + protected Object[][] getContents() { + return contents; + } + + + private static class FiTimeFormat extends SimpleTimeFormat { + private final ResourceBundle bundle; + private String pastName = ""; + private String futureName = ""; + private String pastPluralName = ""; + private String futurePluralName = ""; + private String pluralPattern = ""; + + + public FiTimeFormat(final ResourceBundle rb, final TimeUnit unit) { + super(); + this.bundle = rb; + + if (bundle.containsKey(getUnitName(unit) + "PastSingularName")) { + this.setPastName(bundle.getString(getUnitName(unit) + "PastSingularName")) + .setFutureName(bundle.getString(getUnitName(unit) + "FutureSingularName")) + .setPastPluralName(bundle.getString(getUnitName(unit) + "PastSingularName")) + .setFuturePluralName(bundle.getString(getUnitName(unit) + "FutureSingularName")) + .setPluralPattern(bundle.getString(getUnitName(unit) + "Pattern")); + + if (bundle.containsKey(getUnitName(unit) + "PastPluralName")) { + this.setPastPluralName(bundle.getString(getUnitName(unit) + "PastPluralName")); + } + + if (bundle.containsKey(getUnitName(unit) + "FuturePluralName")) { + this.setFuturePluralName(bundle.getString(getUnitName(unit) + "FuturePluralName")); + } + + if (bundle.containsKey(getUnitName(unit) + "PluralPattern")) { + this.setPluralPattern(bundle.getString(getUnitName(unit) + "PluralPattern")); + } + + this.setPattern(bundle.getString(getUnitName(unit) + "Pattern")) + .setPastSuffix(bundle.getString(getUnitName(unit) + "PastSuffix")) + .setFutureSuffix(bundle.getString(getUnitName(unit) + "FutureSuffix")) + .setFuturePrefix("") + .setPastPrefix("") + .setSingularName("") + .setPluralName(""); + } + } + + public String getPastName() { + return pastName; + } + + public FiTimeFormat setPastName(String pastName) { + this.pastName = pastName; + return this; + } + + public String getFutureName() { + return futureName; + } + + public FiTimeFormat setFutureName(String futureName) { + this.futureName = futureName; + return this; + } + + public String getPastPluralName() { + return pastPluralName; + } + + public FiTimeFormat setPastPluralName(String pastName) { + this.pastPluralName = pastName; + return this; + } + + public String getFuturePluralName() { + return futurePluralName; + } + + public FiTimeFormat setFuturePluralName(String futureName) { + this.futurePluralName = futureName; + return this; + } + + public String getPluralPattern() { + return pluralPattern; + } + + public FiTimeFormat setPluralPattern(String pattern) { + this.pluralPattern = pattern; + return this; + } + + @Override + protected String getGramaticallyCorrectName(TimeUnitQuantity d, boolean round) { + String result = d.isInPast() ? getPastName() : getFutureName(); + if ((Math.abs(getQuantity(d, round)) == 0) || (Math.abs(getQuantity(d, round)) > 1)) { + result = d.isInPast() ? getPastPluralName() : getFuturePluralName(); + } + return result; + } + + @Override + protected String getPattern(long quantity) { + if (Math.abs(quantity) == 1) { + return getPattern(); + } + return getPluralPattern(); + + } + + @Override + public String decorate(TimeUnitQuantity timeUnitQuantity, String time) { + String result = ""; + if (timeUnitQuantity.getUnit() instanceof Day && Math.abs(timeUnitQuantity.getQuantity()) == 1) { + result = time; + } else { + result = super.decorate(timeUnitQuantity, time); + } + return result; + } + + private String getUnitName(TimeUnit unit) { + return unit.getClass().getSimpleName(); + } + + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_fr.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_fr.java new file mode 100644 index 0000000..e075772 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_fr.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_fr extends ListResourceBundle { + + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", "dans "}, + {"CenturyFutureSuffix", ""}, + {"CenturyPastPrefix", "il y a "}, + {"CenturyPastSuffix", ""}, + {"CenturySingularName", "siècle"}, + {"CenturyPluralName", "siècles"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", "dans "}, + {"DayFutureSuffix", ""}, + {"DayPastPrefix", "il y a "}, + {"DayPastSuffix", ""}, + {"DaySingularName", "jour"}, + {"DayPluralName", "jours"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", "dans "}, + {"DecadeFutureSuffix", ""}, + {"DecadePastPrefix", "il y a "}, + {"DecadePastSuffix", ""}, + {"DecadeSingularName", "décennie"}, + {"DecadePluralName", "décennies"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", "dans "}, + {"HourFutureSuffix", ""}, + {"HourPastPrefix", "il y a "}, + {"HourPastSuffix", ""}, + {"HourSingularName", "heure"}, + {"HourPluralName", "heures"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "à l'instant"}, + {"JustNowPastPrefix", "à l'instant"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", "dans "}, + {"MillenniumFutureSuffix", ""}, + {"MillenniumPastPrefix", "il y a "}, + {"MillenniumPastSuffix", ""}, + {"MillenniumSingularName", "millénaire"}, + {"MillenniumPluralName", "millénaires"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", "dans "}, + {"MillisecondFutureSuffix", ""}, + {"MillisecondPastPrefix", "il y a "}, + {"MillisecondPastSuffix", ""}, + {"MillisecondSingularName", "milliseconde"}, + {"MillisecondPluralName", "millisecondes"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", "dans "}, + {"MinuteFutureSuffix", ""}, + {"MinutePastPrefix", "il y a "}, + {"MinutePastSuffix", ""}, + {"MinuteSingularName", "minute"}, + {"MinutePluralName", "minutes"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", "dans "}, + {"MonthFutureSuffix", ""}, + {"MonthPastPrefix", "il y a "}, + {"MonthPastSuffix", ""}, + {"MonthSingularName", "mois"}, + {"MonthPluralName", "mois"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", "dans "}, + {"SecondFutureSuffix", ""}, + {"SecondPastPrefix", "il y a "}, + {"SecondPastSuffix", ""}, + {"SecondSingularName", "seconde"}, + {"SecondPluralName", "secondes"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", "dans "}, + {"WeekFutureSuffix", ""}, + {"WeekPastPrefix", "il y a "}, + {"WeekPastSuffix", ""}, + {"WeekSingularName", "semaine"}, + {"WeekPluralName", "semaines"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", "dans "}, + {"YearFutureSuffix", ""}, + {"YearPastPrefix", "il y a "}, + {"YearPastSuffix", ""}, + {"YearSingularName", "an"}, + {"YearPluralName", "ans"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + protected Object[][] getContents() { + return OBJECTS; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_hi.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_hi.java new file mode 100644 index 0000000..564d82d --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_hi.java @@ -0,0 +1,108 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_hi extends ListResourceBundle { + + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", ""}, + {"CenturyFutureSuffix", " बाद"}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", " पहले"}, + {"CenturySingularName", "सदी"}, + {"CenturyPluralName", "सदियों"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", ""}, + {"DayFutureSuffix", " बाद"}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", " पहले"}, + {"DaySingularName", "दिन"}, + {"DayPluralName", "दिन"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", ""}, + {"DecadeFutureSuffix", " बाद"}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", " पहले"}, + {"DecadeSingularName", "दशक"}, + {"DecadePluralName", "दशक"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", ""}, + {"HourFutureSuffix", " बाद"}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", " पहले"}, + {"HourSingularName", "घंटा"}, + {"HourPluralName", "घंटे"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "अभी"}, + {"JustNowPastPrefix", "अभी"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", ""}, + {"MillenniumFutureSuffix", " बाद"}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", " पहले"}, + {"MillenniumSingularName", "सहस्राब्दी"}, + {"MillenniumPluralName", "सदियों"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", ""}, + {"MillisecondFutureSuffix", " बाद"}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", " पहले"}, + {"MillisecondSingularName", "मिलीसेकंड"}, + {"MillisecondPluralName", "मिलीसेकंड"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", ""}, + {"MinuteFutureSuffix", " बाद"}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", " पहले"}, + {"MinuteSingularName", "मिनट"}, + {"MinutePluralName", "मिनट"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", ""}, + {"MonthFutureSuffix", " बाद"}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", " पहले"}, + {"MonthSingularName", "महीना"}, + {"MonthPluralName", "महीने"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", ""}, + {"SecondFutureSuffix", " बाद"}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", " पहले"}, + {"SecondSingularName", "सेकण्ड"}, + {"SecondPluralName", "सेकंड्स"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", ""}, + {"WeekFutureSuffix", " बाद"}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", " पहले"}, + {"WeekSingularName", "सप्ताह"}, + {"WeekPluralName", "सप्ताह"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", ""}, + {"YearFutureSuffix", " बाद"}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", " पहले"}, + {"YearSingularName", "वर्ष"}, + {"YearPluralName", "वर्ष"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_hr.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_hr.java new file mode 100644 index 0000000..bff36db --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_hr.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_hr extends ListResourceBundle { + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", "za "}, + {"CenturyFutureSuffix", ""}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", " unatrag"}, + {"CenturySingularName", "stoljeće"}, + {"CenturyPluralName", "stoljeća"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", "za "}, + {"DayFutureSuffix", ""}, + {"DayPastPrefix", "prije "}, + {"DayPastSuffix", ""}, + {"DaySingularName", "dan"}, + {"DayPluralName", "dana"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", "za "}, + {"DecadeFutureSuffix", ""}, + {"DecadePastPrefix", "prije "}, + {"DecadePastSuffix", ""}, + {"DecadeSingularName", "desetljeće"}, + {"DecadePluralName", "desetljeća"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", "za "}, + {"HourFutureSuffix", ""}, + {"HourPastPrefix", "prije "}, + {"HourPastSuffix", ""}, + {"HourSingularName", "sat"}, + {"HourPluralName", "sati"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", "za nekoliko trenutaka"}, + {"JustNowFutureSuffix", ""}, + {"JustNowPastPrefix", "prije nekoliko trenutaka"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", "za "}, + {"MillenniumFutureSuffix", ""}, + {"MillenniumPastPrefix", "prije "}, + {"MillenniumPastSuffix", ""}, + {"MillenniumSingularName", "tisućljeće"}, + {"MillenniumPluralName", "tisućljeća"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", "za "}, + {"MillisecondFutureSuffix", ""}, + {"MillisecondPastPrefix", "prije "}, + {"MillisecondPastSuffix", ""}, + {"MillisecondSingularName", "milisekunda"}, + {"MillisecondPluralName", "milisekunda"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", "za "}, + {"MinuteFutureSuffix", ""}, + {"MinutePastPrefix", "prije "}, + {"MinutePastSuffix", ""}, + {"MinuteSingularName", "minuta"}, + {"MinutePluralName", "minuta"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", "za "}, + {"MonthFutureSuffix", ""}, + {"MonthPastPrefix", "prije "}, + {"MonthPastSuffix", ""}, + {"MonthSingularName", "mjesec"}, + {"MonthPluralName", "mjeseca"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", "za "}, + {"SecondFutureSuffix", ""}, + {"SecondPastPrefix", "prije "}, + {"SecondPastSuffix", ""}, + {"SecondSingularName", "sekunda"}, + {"SecondPluralName", "sekundi"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", "za "}, + {"WeekFutureSuffix", ""}, + {"WeekPastPrefix", "prije "}, + {"WeekPastSuffix", ""}, + {"WeekSingularName", "tjedan"}, + {"WeekPluralName", "tjedna"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", "za "}, + {"YearFutureSuffix", ""}, + {"YearPastPrefix", "prije "}, + {"YearPastSuffix", ""}, + {"YearSingularName", "godina"}, + {"YearPluralName", "godina"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_hu.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_hu.java new file mode 100644 index 0000000..f8a5428 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_hu.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_hu extends ListResourceBundle { + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", ""}, + {"CenturyFutureSuffix", "század múlva"}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", "százada"}, + {"CenturySingularName", ""}, + {"CenturyPluralName", ""}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", ""}, + {"DayFutureSuffix", "nap múlva"}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", "napja"}, + {"DaySingularName", ""}, + {"DayPluralName", ""}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", ""}, + {"DecadeFutureSuffix", "évtized múlva"}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", "évtizede"}, + {"DecadeSingularName", ""}, + {"DecadePluralName", ""}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", ""}, + {"HourFutureSuffix", "óra múlva"}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", "órája"}, + {"HourSingularName", ""}, + {"HourPluralName", ""}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "rögtön"}, + {"JustNowPastPrefix", "nemrég"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", ""}, + {"MillenniumFutureSuffix", "évezred múlva"}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", "évezrede"}, + {"MillenniumSingularName", ""}, + {"MillenniumPluralName", ""}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", ""}, + {"MillisecondFutureSuffix", "milliszekundum múlva"}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", "milliszekundummal ezelõtt"}, + {"MillisecondSingularName", ""}, + {"MillisecondPluralName", ""}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", ""}, + {"MinuteFutureSuffix", "perc múlva"}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", "perce"}, + {"MinuteSingularName", ""}, + {"MinutePluralName", ""}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", ""}, + {"MonthFutureSuffix", "hónap múlva"}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", "hónapja"}, + {"MonthSingularName", ""}, + {"MonthPluralName", ""}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", ""}, + {"SecondFutureSuffix", "másodperc múlva"}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", "másodperce"}, + {"SecondSingularName", ""}, + {"SecondPluralName", ""}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", ""}, + {"WeekFutureSuffix", "hét múlva"}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", "hete"}, + {"WeekSingularName", ""}, + {"WeekPluralName", ""}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", ""}, + {"YearFutureSuffix", "év múlva"}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", "éve"}, + {"YearSingularName", ""}, + {"YearPluralName", ""}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_in.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_in.java new file mode 100644 index 0000000..71f7054 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_in.java @@ -0,0 +1,109 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_in extends ListResourceBundle { + + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", ""}, + {"CenturyFutureSuffix", " dari sekarang"}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", " yang lalu"}, + {"CenturySingularName", "abad"}, + {"CenturyPluralName", "abad"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", ""}, + {"DayFutureSuffix", " dari sekarang"}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", " yang lalu"}, + {"DaySingularName", "hari"}, + {"DayPluralName", "hari"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", ""}, + {"DecadeFutureSuffix", " dari sekarang"}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", " yang lalu"}, + {"DecadeSingularName", "dekade"}, + {"DecadePluralName", "dekade"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", ""}, + {"HourFutureSuffix", " dari sekarang"}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", " yang lalu"}, + {"HourSingularName", "jam"}, + {"HourPluralName", "jam"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "dari sekarang"}, + {"JustNowPastPrefix", "yang lalu"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", ""}, + {"MillenniumFutureSuffix", " dari sekarang"}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", " yang lalu"}, + {"MillenniumSingularName", "ribuan tahun"}, + {"MillenniumPluralName", "ribuan tahun"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", ""}, + {"MillisecondFutureSuffix", " dari sekarang"}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", " yang lalu"}, + {"MillisecondSingularName", "mili detik"}, + {"MillisecondPluralName", "mili detik"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", ""}, + {"MinuteFutureSuffix", " dari sekarang"}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", " yang lalu"}, + {"MinuteSingularName", "menit"}, + {"MinutePluralName", "menit"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", ""}, + {"MonthFutureSuffix", " dari sekarang"}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", " yang lalu"}, + {"MonthSingularName", "bulan"}, + {"MonthPluralName", "bulan"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", ""}, + {"SecondFutureSuffix", " dari sekarang"}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", " yang lalu"}, + {"SecondSingularName", "detik"}, + {"SecondPluralName", "detik"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", ""}, + {"WeekFutureSuffix", " dari sekarang"}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", " yang lalu"}, + {"WeekSingularName", "minggu"}, + {"WeekPluralName", "minggu"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", ""}, + {"YearFutureSuffix", " dari sekarang"}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", " yang lalu"}, + {"YearSingularName", "tahun"}, + {"YearPluralName", "tahun"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""} + }; + + @Override + protected Object[][] getContents() { + return OBJECTS; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_it.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_it.java new file mode 100644 index 0000000..2a82f36 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_it.java @@ -0,0 +1,110 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_it extends ListResourceBundle { + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", "fra"}, + {"CenturyFutureSuffix", ""}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", "fa"}, + {"CenturySingularName", "secolo"}, + {"CenturyPluralName", "secoli"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", "fra"}, + {"DayFutureSuffix", ""}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", "fa"}, + {"DaySingularName", "giorno"}, + {"DayPluralName", "giorni"}, + {"DayFutureSingularName", "giorno"}, + {"DayFuturePluralName", "giorni"}, + {"DayPastSingularName", "giorno"}, + {"DayPastPluralName", "giorni"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", "fra"}, + {"DecadeFutureSuffix", ""}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", "fa"}, + {"DecadeSingularName", "decennio"}, + {"DecadePluralName", "decenni"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", "fra"}, + {"HourFutureSuffix", ""}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", "fa"}, + {"HourSingularName", "ora"}, + {"HourPluralName", "ore"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "fra poco"}, + {"JustNowPastPrefix", "poco fa"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", "fra"}, + {"MillenniumFutureSuffix", ""}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", "fa"}, + {"MillenniumSingularName", "millennio"}, + {"MillenniumPluralName", "millenni"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", "fra"}, + {"MillisecondFutureSuffix", ""}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", "fa"}, + {"MillisecondSingularName", "millisecondo"}, + {"MillisecondPluralName", "millisecondi"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", "fra"}, + {"MinuteFutureSuffix", ""}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", "fa"}, + {"MinuteSingularName", "minuto"}, + {"MinutePluralName", "minuti"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", "fra"}, + {"MonthFutureSuffix", ""}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", "fa"}, + {"MonthSingularName", "mese"}, + {"MonthPluralName", "mesi"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", "fra"}, + {"SecondFutureSuffix", ""}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", "fa"}, + {"SecondSingularName", "secondo"}, + {"SecondPluralName", "secondi"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", "fra"}, + {"WeekFutureSuffix", ""}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", "fa"}, + {"WeekSingularName", "settimana"}, + {"WeekPluralName", "settimane"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", "fra"}, + {"YearFutureSuffix", ""}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", "fa"}, + {"YearSingularName", "anno"}, + {"YearPluralName", "anni"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + protected Object[][] getContents() { + return OBJECTS; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_ja.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_ja.java new file mode 100644 index 0000000..12a4b80 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_ja.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_ja extends ListResourceBundle { + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", ""}, + {"CenturyFutureSuffix", "今から"}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", "前"}, + {"CenturySingularName", "世紀"}, + {"CenturyPluralName", "世紀"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", ""}, + {"DayFutureSuffix", "今から"}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", "前"}, + {"DaySingularName", "日"}, + {"DayPluralName", "日"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", ""}, + {"DecadeFutureSuffix", "今から"}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", "前"}, + {"DecadeSingularName", "10年"}, + {"DecadePluralName", "10年"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", ""}, + {"HourFutureSuffix", "今から"}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", "前"}, + {"HourSingularName", "時間"}, + {"HourPluralName", "時間"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "少し後"}, + {"JustNowPastPrefix", "少し前"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", "たった今"}, + {"JustNowPluralName", "たった今"}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", ""}, + {"MillenniumFutureSuffix", "今から"}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", "前"}, + {"MillenniumSingularName", "千年"}, + {"MillenniumPluralName", "千年"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", ""}, + {"MillisecondFutureSuffix", "今から"}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", "前"}, + {"MillisecondSingularName", "ミリ秒"}, + {"MillisecondPluralName", "ミリ秒"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", ""}, + {"MinuteFutureSuffix", "今から"}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", "前"}, + {"MinuteSingularName", "分"}, + {"MinutePluralName", "分"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", ""}, + {"MonthFutureSuffix", "今から"}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", "前"}, + {"MonthSingularName", "月"}, + {"MonthPluralName", "月"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", ""}, + {"SecondFutureSuffix", "今から"}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", "前"}, + {"SecondSingularName", "秒"}, + {"SecondPluralName", "秒"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", ""}, + {"WeekFutureSuffix", "今から"}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", "前"}, + {"WeekSingularName", "週"}, + {"WeekPluralName", "週"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", ""}, + {"YearFutureSuffix", "今から"}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", "前"}, + {"YearSingularName", "年"}, + {"YearPluralName", "年"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_ko.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_ko.java new file mode 100644 index 0000000..1b2458c --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_ko.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_ko extends ListResourceBundle { + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n%u"}, + {"CenturyFuturePrefix", ""}, + {"CenturyFutureSuffix", "후"}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", "전"}, + {"CenturySingularName", "세기"}, + {"CenturyPluralName", "세기"}, + {"DayPattern", "%n%u"}, + {"DayFuturePrefix", ""}, + {"DayFutureSuffix", "후"}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", "전"}, + {"DaySingularName", "일"}, + {"DayPluralName", "일"}, + {"DecadePattern", "%n%u"}, + {"DecadeFuturePrefix", ""}, + {"DecadeFutureSuffix", "후"}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", "전"}, + {"DecadeSingularName", "0년"}, + {"DecadePluralName", "0년"}, + {"HourPattern", "%n%u"}, + {"HourFuturePrefix", ""}, + {"HourFutureSuffix", "후"}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", "전"}, + {"HourSingularName", "시간"}, + {"HourPluralName", "시간"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "지금"}, + {"JustNowPastPrefix", "방금"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n%u"}, + {"MillenniumFuturePrefix", ""}, + {"MillenniumFutureSuffix", "후"}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", "전"}, + {"MillenniumSingularName", "세기"}, + {"MillenniumPluralName", "세기"}, + {"MillisecondPattern", "%n%u"}, + {"MillisecondFuturePrefix", ""}, + {"MillisecondFutureSuffix", "후"}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", "전"}, + {"MillisecondSingularName", "밀리초"}, + {"MillisecondPluralName", "밀리초"}, + {"MinutePattern", "%n%u"}, + {"MinuteFuturePrefix", ""}, + {"MinuteFutureSuffix", "후"}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", "전"}, + {"MinuteSingularName", "분"}, + {"MinutePluralName", "분"}, + {"MonthPattern", "%n%u"}, + {"MonthFuturePrefix", ""}, + {"MonthFutureSuffix", " 후"}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", " 전"}, + {"MonthSingularName", "개월"}, + {"MonthPluralName", "개월"}, + {"SecondPattern", "%n%u"}, + {"SecondFuturePrefix", ""}, + {"SecondFutureSuffix", "후"}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", "전"}, + {"SecondSingularName", "초"}, + {"SecondPluralName", "초"}, + {"WeekPattern", "%n%u"}, + {"WeekFuturePrefix", ""}, + {"WeekFutureSuffix", "후"}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", "전"}, + {"WeekSingularName", "주"}, + {"WeekPluralName", "주"}, + {"YearPattern", "%n%u"}, + {"YearFuturePrefix", ""}, + {"YearFutureSuffix", "후"}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", "전"}, + {"YearSingularName", "년"}, + {"YearPluralName", "년"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_nl.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_nl.java new file mode 100644 index 0000000..0e73df6 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_nl.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_nl extends ListResourceBundle { + + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", "over "}, + {"CenturyFutureSuffix", ""}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", "geleden"}, + {"CenturySingularName", "eeuw"}, + {"CenturyPluralName", "eeuwen"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", "over "}, + {"DayFutureSuffix", ""}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", "geleden"}, + {"DaySingularName", "dag"}, + {"DayPluralName", "dagen"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", "over "}, + {"DecadeFutureSuffix", ""}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", "geleden"}, + {"DecadeSingularName", "decennium"}, + {"DecadePluralName", "decennia"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", "over "}, + {"HourFutureSuffix", ""}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", "geleden"}, + {"HourSingularName", "uur"}, + {"HourPluralName", "uur"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", "op dit moment"}, + {"JustNowFutureSuffix", ""}, + {"JustNowPastPrefix", "een ogenblik geleden"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", "over "}, + {"MillenniumFutureSuffix", ""}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", "geleden"}, + {"MillenniumSingularName", "millennium"}, + {"MillenniumPluralName", "millennia"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", "over "}, + {"MillisecondFutureSuffix", ""}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", "geleden"}, + {"MillisecondSingularName", "milliseconde"}, + {"MillisecondPluralName", "milliseconden"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", "over "}, + {"MinuteFutureSuffix", ""}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", "geleden"}, + {"MinuteSingularName", "minuut"}, + {"MinutePluralName", "minuten"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", "over "}, + {"MonthFutureSuffix", ""}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", "geleden"}, + {"MonthSingularName", "maand"}, + {"MonthPluralName", "maanden"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", "over "}, + {"SecondFutureSuffix", ""}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", "geleden"}, + {"SecondSingularName", "seconde"}, + {"SecondPluralName", "seconden"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", "over "}, + {"WeekFutureSuffix", ""}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", "geleden"}, + {"WeekSingularName", "week"}, + {"WeekPluralName", "weken"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", "over "}, + {"YearFutureSuffix", ""}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", "geleden"}, + {"YearSingularName", "jaar"}, + {"YearPluralName", "jaar"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + protected Object[][] getContents() { + return OBJECTS; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_no.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_no.java new file mode 100644 index 0000000..934f618 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_no.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_no extends ListResourceBundle { + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", ""}, + {"CenturyFutureSuffix", " fra nå"}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", " siden"}, + {"CenturySingularName", "århundre"}, + {"CenturyPluralName", "århundre"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", "om "}, + {"DayFutureSuffix", ""}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", " siden"}, + {"DaySingularName", "dag"}, + {"DayPluralName", "dager"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", ""}, + {"DecadeFutureSuffix", " fra nå"}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", " siden"}, + {"DecadeSingularName", "tiår"}, + {"DecadePluralName", "tiår"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", "om "}, + {"HourFutureSuffix", ""}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", " siden"}, + {"HourSingularName", "time"}, + {"HourPluralName", "timer"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "straks"}, + {"JustNowPastPrefix", "et øyeblikk siden"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", ""}, + {"MillenniumFutureSuffix", " fra nå"}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", " siden"}, + {"MillenniumSingularName", "millennium"}, + {"MillenniumPluralName", "millennier"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", ""}, + {"MillisecondFutureSuffix", " fra nå"}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", " siden"}, + {"MillisecondSingularName", "millisekund"}, + {"MillisecondPluralName", "millisekunder"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", "om "}, + {"MinuteFutureSuffix", ""}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", " siden"}, + {"MinuteSingularName", "minutt"}, + {"MinutePluralName", "minutter"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", "om "}, + {"MonthFutureSuffix", ""}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", " siden"}, + {"MonthSingularName", "måned"}, + {"MonthPluralName", "måneder"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", ""}, + {"SecondFutureSuffix", " fra nå"}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", " siden"}, + {"SecondSingularName", "sekund"}, + {"SecondPluralName", "sekunder"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", "om "}, + {"WeekFutureSuffix", ""}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", " siden"}, + {"WeekSingularName", "uke"}, + {"WeekPluralName", "uker"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", "om "}, + {"YearFutureSuffix", ""}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", " siden"}, + {"YearSingularName", "år"}, + {"YearPluralName", "år"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_pl.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_pl.java new file mode 100644 index 0000000..100ac39 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_pl.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_pl extends ListResourceBundle { + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", "za "}, + {"CenturyFutureSuffix", ""}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", " temu"}, + {"CenturySingularName", "wiek"}, + {"CenturyPluralName", "wiek(i/ów)"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", "za "}, + {"DayFutureSuffix", ""}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", " temu"}, + {"DaySingularName", "dzień"}, + {"DayPluralName", "dni"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", "za "}, + {"DecadeFutureSuffix", ""}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", " temu"}, + {"DecadeSingularName", "dekadę"}, + {"DecadePluralName", "dekad"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", "za "}, + {"HourFutureSuffix", ""}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", " temu"}, + {"HourSingularName", "godz."}, + {"HourPluralName", "godz."}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "za chwilę"}, + {"JustNowPastPrefix", "przed chwilą"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", "za "}, + {"MillenniumFutureSuffix", ""}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", " temu"}, + {"MillenniumSingularName", "milenium"}, + {"MillenniumPluralName", "milenia"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", "za "}, + {"MillisecondFutureSuffix", ""}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", " temu"}, + {"MillisecondSingularName", "milisek."}, + {"MillisecondPluralName", "milisek."}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", "za "}, + {"MinuteFutureSuffix", ""}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", " temu"}, + {"MinuteSingularName", "min."}, + {"MinutePluralName", "min."}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", "za "}, + {"MonthFutureSuffix", ""}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", " temu"}, + {"MonthSingularName", "mies."}, + {"MonthPluralName", "mies."}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", "za "}, + {"SecondFutureSuffix", ""}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", " temu"}, + {"SecondSingularName", "sek."}, + {"SecondPluralName", "sek."}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", "za "}, + {"WeekFutureSuffix", ""}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", " temu"}, + {"WeekSingularName", "tydzień"}, + {"WeekPluralName", "tygodni(e)"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", "za "}, + {"YearFutureSuffix", ""}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", " temu"}, + {"YearSingularName", "rok"}, + {"YearPluralName", "lat(a)"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_pt.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_pt.java new file mode 100644 index 0000000..8dada31 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_pt.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_pt extends ListResourceBundle { + + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", "daqui a "}, + {"CenturyFutureSuffix", ""}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", "atrás"}, + {"CenturySingularName", "século"}, + {"CenturyPluralName", "séculos"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", "daqui a "}, + {"DayFutureSuffix", ""}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", "atrás"}, + {"DaySingularName", "dia"}, + {"DayPluralName", "dias"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", "daqui a "}, + {"DecadeFutureSuffix", ""}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", "atrás"}, + {"DecadeSingularName", "década"}, + {"DecadePluralName", "décadas"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", "daqui a "}, + {"HourFutureSuffix", ""}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", "atrás"}, + {"HourSingularName", "hora"}, + {"HourPluralName", "horas"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", "agora mesmo"}, + {"JustNowFutureSuffix", ""}, + {"JustNowPastPrefix", "agora há pouco"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", "daqui a "}, + {"MillenniumFutureSuffix", ""}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", "atrás"}, + {"MillenniumSingularName", "milênio"}, + {"MillenniumPluralName", "milênios"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", "daqui a "}, + {"MillisecondFutureSuffix", ""}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", "atrás"}, + {"MillisecondSingularName", "millisegundo"}, + {"MillisecondPluralName", "millisegundos"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", "daqui a "}, + {"MinuteFutureSuffix", ""}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", "atrás"}, + {"MinuteSingularName", "minuto"}, + {"MinutePluralName", "minutos"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", "daqui a "}, + {"MonthFutureSuffix", ""}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", "atrás"}, + {"MonthSingularName", "mês"}, + {"MonthPluralName", "meses"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", "daqui a "}, + {"SecondFutureSuffix", ""}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", "atrás"}, + {"SecondSingularName", "segundo"}, + {"SecondPluralName", "segundos"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", "daqui a "}, + {"WeekFutureSuffix", ""}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", "atrás"}, + {"WeekSingularName", "semana"}, + {"WeekPluralName", "semanas"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", "daqui a "}, + {"YearFutureSuffix", ""}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", "atrás"}, + {"YearSingularName", "ano"}, + {"YearPluralName", "anos"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + protected Object[][] getContents() { + return OBJECTS; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_ro.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_ro.java new file mode 100644 index 0000000..0665d71 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_ro.java @@ -0,0 +1,106 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_ro extends ListResourceBundle { + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", ""}, + {"CenturyFutureSuffix", " de acum"}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", " in urma"}, + {"CenturySingularName", "secol"}, + {"CenturyPluralName", "secole"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", ""}, + {"DayFutureSuffix", " de acum"}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", " in urma"}, + {"DaySingularName", "zi"}, + {"DayPluralName", "zile"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", ""}, + {"DecadeFutureSuffix", " de acum"}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", " in urma"}, + {"DecadeSingularName", "deceniu"}, + {"DecadePluralName", "decenii"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", ""}, + {"HourFutureSuffix", " de acum"}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", " in urma"}, + {"HourSingularName", "ora"}, + {"HourPluralName", "ore"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "in cateva clipe"}, + {"JustNowPastPrefix", "cateva clipe in urma"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", ""}, + {"MillenniumFutureSuffix", " de acum"}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", " in urma"}, + {"MillenniumSingularName", "mileniu"}, + {"MillenniumPluralName", "milenii"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", ""}, + {"MillisecondFutureSuffix", " de acum"}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", " in urma"}, + {"MillisecondSingularName", "milisecunda"}, + {"MillisecondPluralName", "milisecunde"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", ""}, + {"MinuteFutureSuffix", " de acum"}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", " in urma"}, + {"MinuteSingularName", "minuta"}, + {"MinutePluralName", "minute"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", ""}, + {"MonthFutureSuffix", " de acum"}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", " in urma"}, + {"MonthSingularName", "luna"}, + {"MonthPluralName", "luni"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", ""}, + {"SecondFutureSuffix", " de acum"}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", " in urma"}, + {"SecondSingularName", "secunda"}, + {"SecondPluralName", "secunde"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", ""}, + {"WeekFutureSuffix", " de acum"}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", " in urma"}, + {"WeekSingularName", "saptamana"}, + {"WeekPluralName", "saptamani"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", ""}, + {"YearFutureSuffix", " de acum"}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", " in urma"}, + {"YearSingularName", "an"}, + {"YearPluralName", "ani"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_ru.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_ru.java new file mode 100644 index 0000000..c954d10 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_ru.java @@ -0,0 +1,163 @@ +package org.xbib.time.pretty.i18n; + +import org.xbib.time.pretty.TimeFormat; +import org.xbib.time.pretty.TimeFormatProvider; +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.TimeUnitQuantity; +import org.xbib.time.pretty.units.Century; +import org.xbib.time.pretty.units.Day; +import org.xbib.time.pretty.units.Decade; +import org.xbib.time.pretty.units.Hour; +import org.xbib.time.pretty.units.JustNow; +import org.xbib.time.pretty.units.Millennium; +import org.xbib.time.pretty.units.Millisecond; +import org.xbib.time.pretty.units.Minute; +import org.xbib.time.pretty.units.Month; +import org.xbib.time.pretty.units.Second; +import org.xbib.time.pretty.units.Week; +import org.xbib.time.pretty.units.Year; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_ru extends ListResourceBundle implements TimeFormatProvider { + private static final Object[][] OBJECTS = new Object[0][0]; + + private static final int tolerance = 50; + + // see http://translate.sourceforge.net/wiki/l10n/pluralforms + private static final int russianPluralForms = 3; + + @Override + public Object[][] getContents() { + return OBJECTS; + } + + @Override + public TimeFormat getFormatFor(TimeUnit t) { + if (t instanceof JustNow) { + return new TimeFormat() { + @Override + public String format(TimeUnitQuantity timeUnitQuantity) { + return performFormat(timeUnitQuantity); + } + + @Override + public String formatUnrounded(TimeUnitQuantity timeUnitQuantity) { + return performFormat(timeUnitQuantity); + } + + private String performFormat(TimeUnitQuantity timeUnitQuantity) { + if (timeUnitQuantity.isInFuture()) { + return "сейчас"; + } + if (timeUnitQuantity.isInPast()) { + return "только что"; + } + return null; + } + + @Override + public String decorate(TimeUnitQuantity timeUnitQuantity, String time) { + return time; + } + + @Override + public String decorateUnrounded(TimeUnitQuantity timeUnitQuantity, String time) { + return time; + } + }; + } else if (t instanceof Century) { + return new TimeFormatAided("век", "века", "веков"); + } else if (t instanceof Day) { + return new TimeFormatAided("день", "дня", "дней"); + } else if (t instanceof Decade) { + return new TimeFormatAided("десятилетие", "десятилетия", "десятилетий"); + } else if (t instanceof Hour) { + return new TimeFormatAided("час", "часа", "часов"); + } else if (t instanceof Millennium) { + return new TimeFormatAided("тысячелетие", "тысячелетия", "тысячелетий"); + } else if (t instanceof Millisecond) { + return new TimeFormatAided("миллисекунду", "миллисекунды", "миллисекунд"); + } else if (t instanceof Minute) { + return new TimeFormatAided("минуту", "минуты", "минут"); + } else if (t instanceof Month) { + return new TimeFormatAided("месяц", "месяца", "месяцев"); + } else if (t instanceof Second) { + return new TimeFormatAided("секунду", "секунды", "секунд"); + } else if (t instanceof Week) { + return new TimeFormatAided("неделю", "недели", "недель"); + } else if (t instanceof Year) { + return new TimeFormatAided("год", "года", "лет"); + } + return null; // error + } + + private static class TimeFormatAided implements TimeFormat { + private final String[] pluarls; + + public TimeFormatAided(String... plurals) { + if (plurals.length != russianPluralForms) { + throw new IllegalArgumentException("Wrong plural forms number for russian language!"); + } + this.pluarls = plurals; + } + + @Override + public String format(TimeUnitQuantity timeUnitQuantity) { + long quantity = timeUnitQuantity.getQuantityRounded(tolerance); + return String.valueOf(quantity); + } + + @Override + public String formatUnrounded(TimeUnitQuantity timeUnitQuantity) { + long quantity = timeUnitQuantity.getQuantity(); + return String.valueOf(quantity); + } + + @Override + public String decorate(TimeUnitQuantity timeUnitQuantity, String time) { + return performDecoration( + timeUnitQuantity.isInPast(), + timeUnitQuantity.isInFuture(), + timeUnitQuantity.getQuantityRounded(tolerance), + time + ); + } + + @Override + public String decorateUnrounded(TimeUnitQuantity timeUnitQuantity, String time) { + return performDecoration( + timeUnitQuantity.isInPast(), + timeUnitQuantity.isInFuture(), + timeUnitQuantity.getQuantity(), + time + ); + } + + private String performDecoration(boolean past, boolean future, long n, String time) { + // a bit cryptic, yet well-tested + // consider http://translate.sourceforge.net/wiki/l10n/pluralforms + int pluralIdx = (n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && + (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); + + StringBuilder result = new StringBuilder(); + + if (future) { + result.append("через "); + } + + result.append(time); + result.append(' '); + result.append(pluarls[pluralIdx]); + + if (past) { + result.append(" назад"); + } + + return result.toString(); + } + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_sl.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_sl.java new file mode 100644 index 0000000..47b0dee --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_sl.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_sl extends ListResourceBundle { + + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", "čez "}, + {"CenturyFutureSuffix", ""}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", "nazaj"}, + {"CenturySingularName", "stoletje"}, + {"CenturyPluralName", "stoletij"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", "čez "}, + {"DayFutureSuffix", ""}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", "nazaj"}, + {"DaySingularName", "dan"}, + {"DayPluralName", "dni"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", "čez "}, + {"DecadeFutureSuffix", ""}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", "nazaj"}, + {"DecadeSingularName", "desetletje"}, + {"DecadePluralName", "desetletij"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", "čez "}, + {"HourFutureSuffix", ""}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", "nazaj"}, + {"HourSingularName", "uro"}, + {"HourPluralName", "ur"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", "čez "}, + {"JustNowFutureSuffix", "pravkar"}, + {"JustNowPastPrefix", "trenutkov nazaj"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", "čez "}, + {"MillenniumFutureSuffix", ""}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", "nazaj"}, + {"MillenniumSingularName", "tisočletje"}, + {"MillenniumPluralName", "tisočletij"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", "čez "}, + {"MillisecondFutureSuffix", ""}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", "nazaj"}, + {"MillisecondSingularName", "milisekundo"}, + {"MillisecondPluralName", "milisekund"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", "čez "}, + {"MinuteFutureSuffix", ""}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", "nazaj"}, + {"MinuteSingularName", "minuto"}, + {"MinutePluralName", "minut"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", "čez "}, + {"MonthFutureSuffix", ""}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", "nazaj"}, + {"MonthSingularName", "mesec"}, + {"MonthPluralName", "mesecev"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", "čez "}, + {"SecondFutureSuffix", ""}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", "nazaj"}, + {"SecondSingularName", "sekundo"}, + {"SecondPluralName", "sekund"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", "čez "}, + {"WeekFutureSuffix", ""}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", "nazaj"}, + {"WeekSingularName", "teden"}, + {"WeekPluralName", "tednov"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", "čez "}, + {"YearFutureSuffix", ""}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", "nazaj"}, + {"YearSingularName", "leto"}, + {"YearPluralName", "let"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + protected Object[][] getContents() { + return OBJECTS; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_sv.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_sv.java new file mode 100644 index 0000000..d74cb1d --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_sv.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_sv extends ListResourceBundle { + + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", "om "}, + {"CenturyFutureSuffix", ""}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", " sedan"}, + {"CenturySingularName", "århundrade"}, + {"CenturyPluralName", "århundraden"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", "om "}, + {"DayFutureSuffix", ""}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", " sedan"}, + {"DaySingularName", "dag"}, + {"DayPluralName", "dagar"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", "om "}, + {"DecadeFutureSuffix", ""}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", " sedan"}, + {"DecadeSingularName", "årtionde"}, + {"DecadePluralName", "årtionden"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", "om "}, + {"HourFutureSuffix", ""}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", " sedan"}, + {"HourSingularName", "timme"}, + {"HourPluralName", "timmar"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", "om "}, + {"JustNowFutureSuffix", "en stund"}, + {"JustNowPastPrefix", "en stund"}, + {"JustNowPastSuffix", " sedan"}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", "om "}, + {"MillenniumFutureSuffix", ""}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", " sedan"}, + {"MillenniumSingularName", "årtusende"}, + {"MillenniumPluralName", "årtusenden"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", "om "}, + {"MillisecondFutureSuffix", ""}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", " sedan"}, + {"MillisecondSingularName", "millisekund"}, + {"MillisecondPluralName", "millisekunder"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", "om "}, + {"MinuteFutureSuffix", ""}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", " sedan"}, + {"MinuteSingularName", "minut"}, + {"MinutePluralName", "minuter"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", "om "}, + {"MonthFutureSuffix", ""}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", " sedan"}, + {"MonthSingularName", "månad"}, + {"MonthPluralName", "månader"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", "om "}, + {"SecondFutureSuffix", ""}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", " sedan"}, + {"SecondSingularName", "sekund"}, + {"SecondPluralName", "sekunder"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", "om "}, + {"WeekFutureSuffix", ""}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", " sedan"}, + {"WeekSingularName", "vecka"}, + {"WeekPluralName", "veckor"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", "om "}, + {"YearFutureSuffix", ""}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", " sedan"}, + {"YearSingularName", "år"}, + {"YearPluralName", "år"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_tr.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_tr.java new file mode 100644 index 0000000..c2ed01d --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_tr.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_tr extends ListResourceBundle { + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", ""}, + {"CenturyFutureSuffix", " sonra"}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", " önce"}, + {"CenturySingularName", "yüzyıl"}, + {"CenturyPluralName", "yüzyıl"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", ""}, + {"DayFutureSuffix", " sonra"}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", " önce"}, + {"DaySingularName", "gün"}, + {"DayPluralName", "gün"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", ""}, + {"DecadeFutureSuffix", " sonra"}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", " önce"}, + {"DecadeSingularName", "on yıl"}, + {"DecadePluralName", "on yıl"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", ""}, + {"HourFutureSuffix", " sonra"}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", " önce"}, + {"HourSingularName", "saat"}, + {"HourPluralName", "saat"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "biraz sonra"}, + {"JustNowPastPrefix", "biraz önce"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", ""}, + {"MillenniumFutureSuffix", " sonra"}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", " önce"}, + {"MillenniumSingularName", "milenyum"}, + {"MillenniumPluralName", "milenyum"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", ""}, + {"MillisecondFutureSuffix", " sonra"}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", " önce"}, + {"MillisecondSingularName", "milisaniye"}, + {"MillisecondPluralName", "milisaniye"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", ""}, + {"MinuteFutureSuffix", " sonra"}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", " önce"}, + {"MinuteSingularName", "dakika"}, + {"MinutePluralName", "dakika"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", ""}, + {"MonthFutureSuffix", " sonra"}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", " önce"}, + {"MonthSingularName", "ay"}, + {"MonthPluralName", "ay"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", ""}, + {"SecondFutureSuffix", " sonra"}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", " önce"}, + {"SecondSingularName", "saniye"}, + {"SecondPluralName", "saniye"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", ""}, + {"WeekFutureSuffix", " sonra"}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", " önce"}, + {"WeekSingularName", "hafta"}, + {"WeekPluralName", "hafta"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", ""}, + {"YearFutureSuffix", " sonra"}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", " önce"}, + {"YearSingularName", "yıl"}, + {"YearPluralName", "yıl"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_ua.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_ua.java new file mode 100644 index 0000000..0c69aef --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_ua.java @@ -0,0 +1,162 @@ +package org.xbib.time.pretty.i18n; + +import org.xbib.time.pretty.TimeFormat; +import org.xbib.time.pretty.TimeFormatProvider; +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.TimeUnitQuantity; +import org.xbib.time.pretty.units.Century; +import org.xbib.time.pretty.units.Day; +import org.xbib.time.pretty.units.Decade; +import org.xbib.time.pretty.units.Hour; +import org.xbib.time.pretty.units.JustNow; +import org.xbib.time.pretty.units.Millennium; +import org.xbib.time.pretty.units.Millisecond; +import org.xbib.time.pretty.units.Minute; +import org.xbib.time.pretty.units.Month; +import org.xbib.time.pretty.units.Second; +import org.xbib.time.pretty.units.Week; +import org.xbib.time.pretty.units.Year; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_ua extends ListResourceBundle implements TimeFormatProvider { + private static final Object[][] OBJECTS = new Object[0][0]; + + private static final int tolerance = 50; + + private static final int slavicPluralForms = 3; + + @Override + public Object[][] getContents() { + return OBJECTS; + } + + @Override + public TimeFormat getFormatFor(TimeUnit t) { + if (t instanceof JustNow) { + return new TimeFormat() { + @Override + public String format(TimeUnitQuantity timeUnitQuantity) { + return performFormat(timeUnitQuantity); + } + + @Override + public String formatUnrounded(TimeUnitQuantity timeUnitQuantity) { + return performFormat(timeUnitQuantity); + } + + private String performFormat(TimeUnitQuantity timeUnitQuantity) { + if (timeUnitQuantity.isInFuture()) { + return "зараз"; + } + if (timeUnitQuantity.isInPast()) { + return "тільки що"; + } + return null; + } + + @Override + public String decorate(TimeUnitQuantity timeUnitQuantity, String time) { + return time; + } + + @Override + public String decorateUnrounded(TimeUnitQuantity timeUnitQuantity, String time) { + return time; + } + }; + } else if (t instanceof Century) { + return new TimeFormatAided("століття", "століття", "столітть"); + } else if (t instanceof Day) { + return new TimeFormatAided("день", "дні", "днів"); + } else if (t instanceof Decade) { + return new TimeFormatAided("десятиліття", "десятиліття", "десятиліть"); + } else if (t instanceof Hour) { + return new TimeFormatAided("годину", "години", "годин"); + } else if (t instanceof Millennium) { + return new TimeFormatAided("тисячоліття", "тисячоліття", "тисячоліть"); + } else if (t instanceof Millisecond) { + return new TimeFormatAided("мілісекунду", "мілісекунди", "мілісекунд"); + } else if (t instanceof Minute) { + return new TimeFormatAided("хвилину", "хвилини", "хвилин"); + } else if (t instanceof Month) { + return new TimeFormatAided("місяць", "місяці", "місяців"); + } else if (t instanceof Second) { + return new TimeFormatAided("секунду", "секунди", "секунд"); + } else if (t instanceof Week) { + return new TimeFormatAided("тиждень", "тижні", "тижнів"); + } else if (t instanceof Year) { + return new TimeFormatAided("рік", "роки", "років"); + } + return null; + } + + private static class TimeFormatAided implements TimeFormat { + private final String[] pluarls; + + public TimeFormatAided(String... plurals) { + if (plurals.length != slavicPluralForms) { + throw new IllegalArgumentException("Wrong plural forms number for slavic language!"); + } + this.pluarls = plurals; + } + + @Override + public String format(TimeUnitQuantity timeUnitQuantity) { + long quantity = timeUnitQuantity.getQuantityRounded(tolerance); + return String.valueOf(quantity); + } + + @Override + public String formatUnrounded(TimeUnitQuantity timeUnitQuantity) { + long quantity = timeUnitQuantity.getQuantity(); + return String.valueOf(quantity); + } + + @Override + public String decorate(TimeUnitQuantity timeUnitQuantity, String time) { + return performDecoration( + timeUnitQuantity.isInPast(), + timeUnitQuantity.isInFuture(), + timeUnitQuantity.getQuantityRounded(tolerance), + time + ); + } + + @Override + public String decorateUnrounded(TimeUnitQuantity timeUnitQuantity, String time) { + return performDecoration( + timeUnitQuantity.isInPast(), + timeUnitQuantity.isInFuture(), + timeUnitQuantity.getQuantity(), + time + ); + } + + private String performDecoration(boolean past, boolean future, long n, String time) { + // a bit cryptic, yet well-tested + // consider http://translate.sourceforge.net/wiki/l10n/pluralforms + int pluralIdx = (n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && + (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); + + StringBuilder result = new StringBuilder(); + + if (future) { + result.append("через "); + } + + result.append(time); + result.append(' '); + result.append(pluarls[pluralIdx]); + + if (past) { + result.append(" тому"); + } + + return result.toString(); + } + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_vi.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_vi.java new file mode 100644 index 0000000..1f3b343 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_vi.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_vi extends ListResourceBundle { + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", ""}, + {"CenturyFutureSuffix", " sau"}, + {"CenturyPastPrefix", "cách đây "}, + {"CenturyPastSuffix", ""}, + {"CenturySingularName", "thế kỷ"}, + {"CenturyPluralName", "thế kỷ"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", ""}, + {"DayFutureSuffix", " sau"}, + {"DayPastPrefix", "cách đây "}, + {"DayPastSuffix", ""}, + {"DaySingularName", "ngày"}, + {"DayPluralName", "ngày"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", ""}, + {"DecadeFutureSuffix", " sau"}, + {"DecadePastPrefix", "cách đây "}, + {"DecadePastSuffix", ""}, + {"DecadeSingularName", "thập kỷ"}, + {"DecadePluralName", "thập kỷ"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", ""}, + {"HourFutureSuffix", " sau"}, + {"HourPastPrefix", "cách đây "}, + {"HourPastSuffix", ""}, + {"HourSingularName", "giờ"}, + {"HourPluralName", "giờ"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", " khắc sau"}, + {"JustNowPastPrefix", "cách đây "}, + {"JustNowPastSuffix", " khắc"}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", ""}, + {"MillenniumFutureSuffix", " sau"}, + {"MillenniumPastPrefix", "cách đây "}, + {"MillenniumPastSuffix", ""}, + {"MillenniumSingularName", "thiên niên kỷ"}, + {"MillenniumPluralName", "thiên niên kỷ"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", ""}, + {"MillisecondFutureSuffix", " sau"}, + {"MillisecondPastPrefix", "cách đây "}, + {"MillisecondPastSuffix", ""}, + {"MillisecondSingularName", "mili giây"}, + {"MillisecondPluralName", "mili giây"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", ""}, + {"MinuteFutureSuffix", " sau"}, + {"MinutePastPrefix", "cách đây "}, + {"MinutePastSuffix", ""}, + {"MinuteSingularName", "phút"}, + {"MinutePluralName", "phút"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", ""}, + {"MonthFutureSuffix", " sau"}, + {"MonthPastPrefix", "cách đây "}, + {"MonthPastSuffix", ""}, + {"MonthSingularName", "tháng"}, + {"MonthPluralName", "tháng"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", ""}, + {"SecondFutureSuffix", " sau"}, + {"SecondPastPrefix", "cách đây "}, + {"SecondPastSuffix", ""}, + {"SecondSingularName", "giây"}, + {"SecondPluralName", "giây"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", ""}, + {"WeekFutureSuffix", " sau"}, + {"WeekPastPrefix", "cách đây "}, + {"WeekPastSuffix", ""}, + {"WeekSingularName", "tuần"}, + {"WeekPluralName", "tuần"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", ""}, + {"YearFutureSuffix", " sau"}, + {"YearPastPrefix", "cách đay "}, + {"YearPastSuffix", ""}, + {"YearSingularName", "năm"}, + {"YearPluralName", "năm"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_zh.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_zh.java new file mode 100644 index 0000000..620e975 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_zh.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_zh extends ListResourceBundle { + + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", ""}, + {"CenturyFutureSuffix", "后"}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", "前"}, + {"CenturySingularName", "世纪"}, + {"CenturyPluralName", "世纪"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", ""}, + {"DayFutureSuffix", "后"}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", "前"}, + {"DaySingularName", "天"}, + {"DayPluralName", "天"}, + {"DecadePattern", "%n%u"}, + {"DecadeFuturePrefix", ""}, + {"DecadeFutureSuffix", "后"}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", "前"}, + {"DecadeSingularName", "0 年"}, + {"DecadePluralName", "0 年"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", ""}, + {"HourFutureSuffix", "后"}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", "前"}, + {"HourSingularName", "小时"}, + {"HourPluralName", "小时"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "刚刚"}, + {"JustNowPastPrefix", "片刻之前"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", ""}, + {"MillenniumFutureSuffix", "后"}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", "前"}, + {"MillenniumSingularName", "千年"}, + {"MillenniumPluralName", "千年"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", ""}, + {"MillisecondFutureSuffix", "后"}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", "前"}, + {"MillisecondSingularName", "毫秒"}, + {"MillisecondPluralName", "毫秒"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", ""}, + {"MinuteFutureSuffix", "后"}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", "前"}, + {"MinuteSingularName", "分钟"}, + {"MinutePluralName", "分钟"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", ""}, + {"MonthFutureSuffix", "后"}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", "前"}, + {"MonthSingularName", "个月"}, + {"MonthPluralName", "个月"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", ""}, + {"SecondFutureSuffix", "后"}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", "前"}, + {"SecondSingularName", "秒"}, + {"SecondPluralName", "秒"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", ""}, + {"WeekFutureSuffix", "后"}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", "前"}, + {"WeekSingularName", "周"}, + {"WeekPluralName", "周"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", ""}, + {"YearFutureSuffix", "后"}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", "前"}, + {"YearSingularName", "年"}, + {"YearPluralName", "年"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + protected Object[][] getContents() { + return OBJECTS; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_zh_HK.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_zh_HK.java new file mode 100644 index 0000000..5e93bd1 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_zh_HK.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_zh_HK extends ListResourceBundle { + + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", ""}, + {"CenturyFutureSuffix", "後"}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", "前"}, + {"CenturySingularName", "世紀"}, + {"CenturyPluralName", "世紀"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", ""}, + {"DayFutureSuffix", "後"}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", "前"}, + {"DaySingularName", "日"}, + {"DayPluralName", "日"}, + {"DecadePattern", "%n%u"}, + {"DecadeFuturePrefix", ""}, + {"DecadeFutureSuffix", "後"}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", "前"}, + {"DecadeSingularName", "0 年"}, + {"DecadePluralName", "0 年"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", ""}, + {"HourFutureSuffix", "後"}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", "前"}, + {"HourSingularName", "小時"}, + {"HourPluralName", "小時"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "剛剛"}, + {"JustNowPastPrefix", "片刻之前"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", ""}, + {"MillenniumFutureSuffix", "後"}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", "前"}, + {"MillenniumSingularName", "千年"}, + {"MillenniumPluralName", "千年"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", ""}, + {"MillisecondFutureSuffix", "後"}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", "前"}, + {"MillisecondSingularName", "毫秒"}, + {"MillisecondPluralName", "毫秒"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", ""}, + {"MinuteFutureSuffix", "後"}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", "前"}, + {"MinuteSingularName", "分鐘"}, + {"MinutePluralName", "分鐘"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", ""}, + {"MonthFutureSuffix", "後"}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", "前"}, + {"MonthSingularName", "個月"}, + {"MonthPluralName", "個月"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", ""}, + {"SecondFutureSuffix", "後"}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", "前"}, + {"SecondSingularName", "秒"}, + {"SecondPluralName", "秒"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", ""}, + {"WeekFutureSuffix", "後"}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", "前"}, + {"WeekSingularName", "星期"}, + {"WeekPluralName", "星期"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", ""}, + {"YearFutureSuffix", "後"}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", "前"}, + {"YearSingularName", "年"}, + {"YearPluralName", "年"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + protected Object[][] getContents() { + return OBJECTS; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/Resources_zh_TW.java b/src/main/java/org/xbib/time/pretty/i18n/Resources_zh_TW.java new file mode 100644 index 0000000..b9973d0 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/Resources_zh_TW.java @@ -0,0 +1,107 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +/** + * + */ +public class Resources_zh_TW extends ListResourceBundle { + + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", ""}, + {"CenturyFutureSuffix", "後"}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", "前"}, + {"CenturySingularName", "世紀"}, + {"CenturyPluralName", "世紀"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", ""}, + {"DayFutureSuffix", "後"}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", "前"}, + {"DaySingularName", "天"}, + {"DayPluralName", "天"}, + {"DecadePattern", "%n%u"}, + {"DecadeFuturePrefix", ""}, + {"DecadeFutureSuffix", "後"}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", "前"}, + {"DecadeSingularName", "0 年"}, + {"DecadePluralName", "0 年"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", ""}, + {"HourFutureSuffix", "後"}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", "前"}, + {"HourSingularName", "小時"}, + {"HourPluralName", "小時"}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "剛剛"}, + {"JustNowPastPrefix", "片刻之前"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", ""}, + {"MillenniumFutureSuffix", "後"}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", "前"}, + {"MillenniumSingularName", "千年"}, + {"MillenniumPluralName", "千年"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", ""}, + {"MillisecondFutureSuffix", "後"}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", "前"}, + {"MillisecondSingularName", "毫秒"}, + {"MillisecondPluralName", "毫秒"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", ""}, + {"MinuteFutureSuffix", "後"}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", "前"}, + {"MinuteSingularName", "分鐘"}, + {"MinutePluralName", "分鐘"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", ""}, + {"MonthFutureSuffix", "後"}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", "前"}, + {"MonthSingularName", "個月"}, + {"MonthPluralName", "個月"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", ""}, + {"SecondFutureSuffix", "後"}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", "前"}, + {"SecondSingularName", "秒"}, + {"SecondPluralName", "秒"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", ""}, + {"WeekFutureSuffix", "後"}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", "前"}, + {"WeekSingularName", "週"}, + {"WeekPluralName", "週"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", ""}, + {"YearFutureSuffix", "後"}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", "前"}, + {"YearSingularName", "年"}, + {"YearPluralName", "年"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + protected Object[][] getContents() { + return OBJECTS; + } +} diff --git a/src/main/java/org/xbib/time/pretty/i18n/package-info.java b/src/main/java/org/xbib/time/pretty/i18n/package-info.java new file mode 100644 index 0000000..fb7de41 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/i18n/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for internationalization of pretty-printing times. + */ +package org.xbib.time.pretty.i18n; diff --git a/src/main/java/org/xbib/time/pretty/package-info.java b/src/main/java/org/xbib/time/pretty/package-info.java new file mode 100644 index 0000000..84a1a7d --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for pretty-printing times. + */ +package org.xbib.time.pretty; diff --git a/src/main/java/org/xbib/time/pretty/units/Century.java b/src/main/java/org/xbib/time/pretty/units/Century.java new file mode 100644 index 0000000..26253d4 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/units/Century.java @@ -0,0 +1,19 @@ +package org.xbib.time.pretty.units; + +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.i18n.ResourcesTimeUnit; + +/** + * + */ +public class Century extends ResourcesTimeUnit implements TimeUnit { + + public Century() { + setMillisPerUnit(3155692597470L); + } + + @Override + protected String getResourceKeyPrefix() { + return "Century"; + } +} diff --git a/src/main/java/org/xbib/time/pretty/units/Day.java b/src/main/java/org/xbib/time/pretty/units/Day.java new file mode 100644 index 0000000..d171b6e --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/units/Day.java @@ -0,0 +1,20 @@ +package org.xbib.time.pretty.units; + +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.i18n.ResourcesTimeUnit; + +/** + * + */ +public class Day extends ResourcesTimeUnit implements TimeUnit { + + public Day() { + setMillisPerUnit(1000L * 60L * 60L * 24L); + } + + @Override + protected String getResourceKeyPrefix() { + return "Day"; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/units/Decade.java b/src/main/java/org/xbib/time/pretty/units/Decade.java new file mode 100644 index 0000000..8a493cd --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/units/Decade.java @@ -0,0 +1,20 @@ +package org.xbib.time.pretty.units; + +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.i18n.ResourcesTimeUnit; + +/** + * + */ +public class Decade extends ResourcesTimeUnit implements TimeUnit { + + public Decade() { + setMillisPerUnit(315569259747L); + } + + @Override + protected String getResourceKeyPrefix() { + return "Decade"; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/units/Hour.java b/src/main/java/org/xbib/time/pretty/units/Hour.java new file mode 100644 index 0000000..c059807 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/units/Hour.java @@ -0,0 +1,20 @@ +package org.xbib.time.pretty.units; + +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.i18n.ResourcesTimeUnit; + +/** + * + */ +public class Hour extends ResourcesTimeUnit implements TimeUnit { + + public Hour() { + setMillisPerUnit(1000L * 60L * 60L); + } + + @Override + protected String getResourceKeyPrefix() { + return "Hour"; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/units/JustNow.java b/src/main/java/org/xbib/time/pretty/units/JustNow.java new file mode 100644 index 0000000..f4b245e --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/units/JustNow.java @@ -0,0 +1,20 @@ +package org.xbib.time.pretty.units; + +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.i18n.ResourcesTimeUnit; + +/** + * + */ +public class JustNow extends ResourcesTimeUnit implements TimeUnit { + + public JustNow() { + setMaxQuantity(1000L * 60L * 5L); + } + + @Override + protected String getResourceKeyPrefix() { + return "JustNow"; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/units/Millennium.java b/src/main/java/org/xbib/time/pretty/units/Millennium.java new file mode 100644 index 0000000..05e991d --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/units/Millennium.java @@ -0,0 +1,20 @@ +package org.xbib.time.pretty.units; + +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.i18n.ResourcesTimeUnit; + +/** + * + */ +public class Millennium extends ResourcesTimeUnit implements TimeUnit { + + public Millennium() { + setMillisPerUnit(31556926000000L); + } + + @Override + protected String getResourceKeyPrefix() { + return "Millennium"; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/units/Millisecond.java b/src/main/java/org/xbib/time/pretty/units/Millisecond.java new file mode 100644 index 0000000..df0c094 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/units/Millisecond.java @@ -0,0 +1,20 @@ +package org.xbib.time.pretty.units; + +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.i18n.ResourcesTimeUnit; + +/** + * + */ +public class Millisecond extends ResourcesTimeUnit implements TimeUnit { + + public Millisecond() { + setMillisPerUnit(1); + } + + @Override + protected String getResourceKeyPrefix() { + return "Millisecond"; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/units/Minute.java b/src/main/java/org/xbib/time/pretty/units/Minute.java new file mode 100644 index 0000000..41fdad6 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/units/Minute.java @@ -0,0 +1,20 @@ +package org.xbib.time.pretty.units; + +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.i18n.ResourcesTimeUnit; + +/** + * + */ +public class Minute extends ResourcesTimeUnit implements TimeUnit { + + public Minute() { + setMillisPerUnit(1000L * 60L); + } + + @Override + protected String getResourceKeyPrefix() { + return "Minute"; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/units/Month.java b/src/main/java/org/xbib/time/pretty/units/Month.java new file mode 100644 index 0000000..942d08c --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/units/Month.java @@ -0,0 +1,20 @@ +package org.xbib.time.pretty.units; + +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.i18n.ResourcesTimeUnit; + +/** + * + */ +public class Month extends ResourcesTimeUnit implements TimeUnit { + + public Month() { + setMillisPerUnit(2629743830L); + } + + @Override + protected String getResourceKeyPrefix() { + return "Month"; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/units/Second.java b/src/main/java/org/xbib/time/pretty/units/Second.java new file mode 100644 index 0000000..b2f5c83 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/units/Second.java @@ -0,0 +1,20 @@ +package org.xbib.time.pretty.units; + +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.i18n.ResourcesTimeUnit; + +/** + * + */ +public class Second extends ResourcesTimeUnit implements TimeUnit { + + public Second() { + setMillisPerUnit(1000L); + } + + @Override + protected String getResourceKeyPrefix() { + return "Second"; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/units/TimeUnitComparator.java b/src/main/java/org/xbib/time/pretty/units/TimeUnitComparator.java new file mode 100644 index 0000000..808fb41 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/units/TimeUnitComparator.java @@ -0,0 +1,20 @@ +package org.xbib.time.pretty.units; + +import org.xbib.time.pretty.TimeUnit; + +import java.util.Comparator; + +/** + * + */ +public class TimeUnitComparator implements Comparator { + + public int compare(final TimeUnit left, final TimeUnit right) { + if (left.getMillisPerUnit() < right.getMillisPerUnit()) { + return -1; + } else if (left.getMillisPerUnit() > right.getMillisPerUnit()) { + return 1; + } + return 0; + } +} diff --git a/src/main/java/org/xbib/time/pretty/units/Week.java b/src/main/java/org/xbib/time/pretty/units/Week.java new file mode 100644 index 0000000..5bf8134 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/units/Week.java @@ -0,0 +1,20 @@ +package org.xbib.time.pretty.units; + +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.i18n.ResourcesTimeUnit; + +/** + * + */ +public class Week extends ResourcesTimeUnit implements TimeUnit { + + public Week() { + setMillisPerUnit(1000L * 60L * 60L * 24L * 7L); + } + + @Override + protected String getResourceKeyPrefix() { + return "Week"; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/units/Year.java b/src/main/java/org/xbib/time/pretty/units/Year.java new file mode 100644 index 0000000..9e69dff --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/units/Year.java @@ -0,0 +1,20 @@ +package org.xbib.time.pretty.units; + +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.i18n.ResourcesTimeUnit; + +/** + * + */ +public class Year extends ResourcesTimeUnit implements TimeUnit { + + public Year() { + setMillisPerUnit(2629743830L * 12L); + } + + @Override + protected String getResourceKeyPrefix() { + return "Year"; + } + +} diff --git a/src/main/java/org/xbib/time/pretty/units/package-info.java b/src/main/java/org/xbib/time/pretty/units/package-info.java new file mode 100644 index 0000000..64372d5 --- /dev/null +++ b/src/main/java/org/xbib/time/pretty/units/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for pretty-printing time units. + */ +package org.xbib.time.pretty.units; diff --git a/src/test/java/org/xbib/time/FormatterTest.java b/src/test/java/org/xbib/time/FormatterTest.java new file mode 100644 index 0000000..83f15d8 --- /dev/null +++ b/src/test/java/org/xbib/time/FormatterTest.java @@ -0,0 +1,27 @@ +package org.xbib.time; + +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class FormatterTest { + + @Test + public void testLocalDate() { + String pattern = "yyyyMMdd"; + String name1 = DateTimeFormatter.ofPattern(pattern) + .withZone(ZoneId.systemDefault()) + .format(Instant.now()); + String name2 = DateTimeFormatter.ofPattern(pattern) + .withZone(ZoneId.systemDefault()) + .withLocale(Locale.getDefault()) + .format(LocalDate.now()); + assertEquals(name1, name2); + } +} diff --git a/src/test/java/org/xbib/time/chronic/ChronicTest.java b/src/test/java/org/xbib/time/chronic/ChronicTest.java new file mode 100644 index 0000000..1ea6cf5 --- /dev/null +++ b/src/test/java/org/xbib/time/chronic/ChronicTest.java @@ -0,0 +1,86 @@ +package org.xbib.time.chronic; + +import org.junit.Assert; +import org.junit.Test; +import org.xbib.time.chronic.handlers.Handler; +import org.xbib.time.chronic.repeaters.EnumRepeaterDayPortion; +import org.xbib.time.chronic.repeaters.RepeaterDayName; +import org.xbib.time.chronic.repeaters.RepeaterDayName.DayName; +import org.xbib.time.chronic.repeaters.RepeaterDayPortion; +import org.xbib.time.chronic.repeaters.RepeaterTime; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.LinkedList; +import java.util.List; + +public class ChronicTest extends Assert { + + private final static ZoneId ZONE_ID = ZoneId.of("GMT"); + + public static ZonedDateTime construct(int year, int month) { + return ZonedDateTime.of(year, month, 1, 0, 0, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day) { + return ZonedDateTime.of(year, month, day, 0, 0, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour) { + return ZonedDateTime.of(year, month, day, hour, 0, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes) { + return ZonedDateTime.of(year, month, day, hour, minutes, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes, int seconds) { + return ZonedDateTime.of(year, month, day, hour, minutes, seconds, 0, ZONE_ID); + } + + @Test + public void testPostNormalizeAmPmAliases() { + List tokens = new LinkedList<>(); + + // affect wanted patterns + tokens.add(new Token("5:00")); + tokens.add(new Token("morning")); + tokens.get(0).tag(new RepeaterTime("5:00")); + tokens.get(1).tag(new EnumRepeaterDayPortion(RepeaterDayPortion.DayPortion.MORNING)); + + assertEquals(RepeaterDayPortion.DayPortion.MORNING, tokens.get(1).getTags().get(0).getType()); + + tokens = Handler.dealiasAndDisambiguateTimes(tokens, new Options()); + + assertEquals(RepeaterDayPortion.DayPortion.AM, tokens.get(1).getTags().get(0).getType()); + assertEquals(2, tokens.size()); + + // don't affect unwanted patterns + tokens = new LinkedList<>(); + tokens.add(new Token("friday")); + tokens.add(new Token("morning")); + tokens.get(0).tag(new RepeaterDayName(DayName.FRIDAY)); + tokens.get(1).tag(new EnumRepeaterDayPortion(RepeaterDayPortion.DayPortion.MORNING)); + + assertEquals(RepeaterDayPortion.DayPortion.MORNING, tokens.get(1).getTags().get(0).getType()); + + tokens = Handler.dealiasAndDisambiguateTimes(tokens, new Options()); + + assertEquals(RepeaterDayPortion.DayPortion.MORNING, tokens.get(1).getTags().get(0).getType()); + assertEquals(2, tokens.size()); + } + + @Test + public void testGuess() { + Span span; + + span = new Span(construct(2006, 8, 16, 0), construct(2006, 8, 17, 0)); + assertEquals(construct(2006, 8, 16, 12), Chronic.guess(span).getBeginCalendar()); + + span = new Span(construct(2006, 8, 16, 0), construct(2006, 8, 17, 0, 0, 1)); + assertEquals(construct(2006, 8, 16, 12), Chronic.guess(span).getBeginCalendar()); + + span = new Span(construct(2006, 11), construct(2006, 12)); + assertEquals(construct(2006, 11, 16), Chronic.guess(span).getBeginCalendar()); + } +} diff --git a/src/test/java/org/xbib/time/chronic/HandlerTest.java b/src/test/java/org/xbib/time/chronic/HandlerTest.java new file mode 100644 index 0000000..3da2759 --- /dev/null +++ b/src/test/java/org/xbib/time/chronic/HandlerTest.java @@ -0,0 +1,118 @@ +package org.xbib.time.chronic; + +import org.junit.Assert; +import org.junit.Test; +import org.xbib.time.chronic.handlers.DummyHandler; +import org.xbib.time.chronic.handlers.Handler; +import org.xbib.time.chronic.handlers.HandlerTypePattern; +import org.xbib.time.chronic.handlers.TagPattern; +import org.xbib.time.chronic.repeaters.*; +import org.xbib.time.chronic.tags.Pointer; +import org.xbib.time.chronic.tags.Scalar; +import org.xbib.time.chronic.tags.ScalarDay; + +import java.util.LinkedList; +import java.util.List; + +public class HandlerTest extends Assert { + + @Test + public void testHandlerClass1() { + Handler handler = new Handler(new DummyHandler(), new TagPattern(Repeater.class)); + List tokens = new LinkedList<>(); + tokens.add(new Token("friday")); + tokens.get(0).tag(new RepeaterDayName(RepeaterDayName.DayName.FRIDAY)); + + assertTrue(handler.match(tokens, Handler.definitions())); + + tokens.add(new Token("afternoon")); + tokens.get(1).tag(new EnumRepeaterDayPortion(RepeaterDayPortion.DayPortion.AFTERNOON)); + + assertFalse(handler.match(tokens, Handler.definitions())); + } + + @Test + public void testHandlerClass2() { + Handler handler = new Handler(new DummyHandler(), new TagPattern(Repeater.class), new TagPattern(Repeater.class, true)); + List tokens = new LinkedList<>(); + tokens.add(new Token("friday")); + tokens.get(0).tag(new RepeaterDayName(RepeaterDayName.DayName.FRIDAY)); + + assertTrue(handler.match(tokens, Handler.definitions())); + + tokens.add(new Token("afternoon")); + tokens.get(1).tag(new EnumRepeaterDayPortion(RepeaterDayPortion.DayPortion.AFTERNOON)); + + assertTrue(handler.match(tokens, Handler.definitions())); + + tokens.add(new Token("afternoon")); + tokens.get(2).tag(new EnumRepeaterDayPortion(RepeaterDayPortion.DayPortion.AFTERNOON)); + + assertFalse(handler.match(tokens, Handler.definitions())); + } + + @Test + public void testHandlerClass3() { + Handler handler = new Handler(new DummyHandler(), new TagPattern(Repeater.class), new HandlerTypePattern(Handler.HandlerType.TIME, true)); + List tokens = new LinkedList<>(); + tokens.add(new Token("friday")); + tokens.get(0).tag(new RepeaterDayName(RepeaterDayName.DayName.FRIDAY)); + + assertTrue(handler.match(tokens, Handler.definitions())); + + tokens.add(new Token("afternoon")); + tokens.get(1).tag(new EnumRepeaterDayPortion(RepeaterDayPortion.DayPortion.AFTERNOON)); + + assertFalse(handler.match(tokens, Handler.definitions())); + } + + @Test + public void testHandlerClass4() { + Handler handler = new Handler(new DummyHandler(), new TagPattern(RepeaterMonthName.class), new TagPattern(ScalarDay.class), new HandlerTypePattern(Handler.HandlerType.TIME, true)); + List tokens = new LinkedList<>(); + tokens.add(new Token("may")); + tokens.get(0).tag(new RepeaterMonthName(RepeaterMonthName.MonthName.MAY)); + + assertFalse(handler.match(tokens, Handler.definitions())); + + tokens.add(new Token("27")); + tokens.get(1).tag(new ScalarDay(27)); + + assertTrue(handler.match(tokens, Handler.definitions())); + } + + @Test + public void testHandlerClass5() { + Handler handler = new Handler(new DummyHandler(), new TagPattern(Repeater.class), new HandlerTypePattern(Handler.HandlerType.TIME, true)); + List tokens = new LinkedList<>(); + tokens.add(new Token("friday")); + tokens.get(0).tag(new RepeaterDayName(RepeaterDayName.DayName.FRIDAY)); + + assertTrue(handler.match(tokens, Handler.definitions())); + + tokens.add(new Token("5:00")); + tokens.get(1).tag(new RepeaterTime("5:00")); + + assertTrue(handler.match(tokens, Handler.definitions())); + + tokens.add(new Token("pm")); + tokens.get(2).tag(new EnumRepeaterDayPortion(RepeaterDayPortion.DayPortion.PM)); + + assertTrue(handler.match(tokens, Handler.definitions())); + } + + @Test + public void testHandlerClass6() { + Handler handler = new Handler(new DummyHandler(), new TagPattern(Scalar.class), new TagPattern(Repeater.class), new TagPattern(Pointer.class)); + List tokens = new LinkedList<>(); + tokens.add(new Token("3")); + tokens.add(new Token("years")); + tokens.add(new Token("past")); + + tokens.get(0).tag(new Scalar(3)); + tokens.get(1).tag(new RepeaterYear()); + tokens.get(2).tag(new Pointer(Pointer.PointerType.PAST)); + + assertTrue(handler.match(tokens, Handler.definitions())); + } +} diff --git a/src/test/java/org/xbib/time/chronic/NumerizerTest.java b/src/test/java/org/xbib/time/chronic/NumerizerTest.java new file mode 100644 index 0000000..5c0d595 --- /dev/null +++ b/src/test/java/org/xbib/time/chronic/NumerizerTest.java @@ -0,0 +1,57 @@ +package org.xbib.time.chronic; + +import org.junit.Assert; +import org.junit.Test; +import org.xbib.time.chronic.numerizer.Numerizer; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class NumerizerTest extends Assert { + + @Test + public void testStraightParsing() { + Map strings = new LinkedHashMap<>(); + strings.put(1, "one"); + strings.put(5, "five"); + strings.put(10, "ten"); + strings.put(11, "eleven"); + strings.put(12, "twelve"); + strings.put(13, "thirteen"); + strings.put(14, "fourteen"); + strings.put(15, "fifteen"); + strings.put(16, "sixteen"); + strings.put(17, "seventeen"); + strings.put(18, "eighteen"); + strings.put(19, "nineteen"); + strings.put(20, "twenty"); + strings.put(27, "twenty seven"); + strings.put(31, "thirty-one"); + strings.put(59, "fifty nine"); + strings.put(100, "a hundred"); + strings.put(100, "one hundred"); + strings.put(150, "one hundred and fifty"); + // strings.put(Integer.valueOf(150), "one fifty"); + strings.put(200, "two-hundred"); + strings.put(500, "5 hundred"); + strings.put(999, "nine hundred and ninety nine"); + strings.put(1000, "one thousand"); + strings.put(1200, "twelve hundred"); + strings.put(1200, "one thousand two hundred"); + strings.put(17000, "seventeen thousand"); + strings.put(21473, "twentyone-thousand-four-hundred-and-seventy-three"); + strings.put(74002, "seventy four thousand and two"); + strings.put(99999, "ninety nine thousand nine hundred ninety nine"); + strings.put(100000, "100 thousand"); + strings.put(250000, "two hundred fifty thousand"); + strings.put(1000000, "one million"); + strings.put(1250007, "one million two hundred fifty thousand and seven"); + strings.put(1000000000, "one billion"); + strings.put(1000000001, "one billion and one"); + + for (Integer value : strings.keySet()) { + String str = strings.get(value); + assertEquals(value.intValue(), Integer.parseInt(Numerizer.numerize(str))); + } + } +} diff --git a/src/test/java/org/xbib/time/chronic/ParserTest.java b/src/test/java/org/xbib/time/chronic/ParserTest.java new file mode 100644 index 0000000..1717bf8 --- /dev/null +++ b/src/test/java/org/xbib/time/chronic/ParserTest.java @@ -0,0 +1,687 @@ +package org.xbib.time.chronic; + +import org.junit.Assert; +import org.junit.Test; +import org.xbib.time.chronic.tags.Pointer; + +import java.text.ParseException; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +public class ParserTest extends Assert { + + private final static ZoneId ZONE_ID = ZoneId.of("GMT"); + private ZonedDateTime time_2006_08_16_14_00_00 = construct(2006, 8, 16, 14, 0, 0); + + public static ZonedDateTime construct(int year, int month) { + return ZonedDateTime.of(year, month, 1, 0, 0, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day) { + return ZonedDateTime.of(year, month, day, 0, 0, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour) { + return ZonedDateTime.of(year, month, day, hour, 0, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes) { + return ZonedDateTime.of(year, month, day, hour, minutes, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes, int seconds) { + return ZonedDateTime.of(year, month, day, hour, minutes, seconds, 0, ZONE_ID); + } + + + protected void assertEquals(ZonedDateTime ec, Span ac) { + if (ec != null) { + assertEquals(ec, ac.getBeginCalendar()); + } + } + + @Test + public void test_parse_guess_dates() { + // rm_sd + + Span time; + time = parse_now("may 27"); + assertEquals(construct(2007, 5, 27, 12), time); + + time = parse_now("may 28", new Options().setContext(Pointer.PointerType.PAST)); + assertEquals(construct(2006, 5, 28, 12), time); + + time = parse_now("may 28 5pm", new Options().setContext(Pointer.PointerType.PAST)); + assertEquals(construct(2006, 5, 28, 17), time); + + time = parse_now("may 28 at 5pm", new Options().setContext(Pointer.PointerType.PAST)); + assertEquals(construct(2006, 5, 28, 17), time); + + time = parse_now("may 28 at 5:32.19pm", new Options().setContext(Pointer.PointerType.PAST)); + assertEquals(construct(2006, 5, 28, 17, 32, 19), time); + + // rm_od + + time = parse_now("may 27th"); + assertEquals(construct(2007, 5, 27, 12), time); + + time = parse_now("may 27th", new Options().setContext(Pointer.PointerType.PAST)); + assertEquals(construct(2006, 5, 27, 12), time); + + time = parse_now("may 27th 5:00 pm", new Options().setContext(Pointer.PointerType.PAST)); + assertEquals(construct(2006, 5, 27, 17), time); + + time = parse_now("may 27th at 5pm", new Options().setContext(Pointer.PointerType.PAST)); + assertEquals(construct(2006, 5, 27, 17), time); + + time = parse_now("may 27th at 5", new Options().setAmbiguousTimeRange(0)); + assertEquals(construct(2007, 5, 27, 5), time); + + // rm_sy + + time = parse_now("June 1979"); + assertEquals(construct(1979, 6, 16, 0), time); + + time = parse_now("dec 79"); + assertEquals(construct(1979, 12, 16, 12), time); + + // rm_sd_sy + + time = parse_now("jan 3 2010"); + assertEquals(construct(2010, 1, 3, 12), time); + + time = parse_now("jan 3 2010 midnight"); + assertEquals(construct(2010, 1, 4, 0), time); + + time = parse_now("jan 3 2010 at midnight"); + assertEquals(construct(2010, 1, 4, 0), time); + + time = parse_now("jan 3 2010 at 4", new Options().setAmbiguousTimeRange(0)); + assertEquals(construct(2010, 1, 3, 4), time); + + //time = parse_now("January 12, '00"); + //assertEquals(Time.construct(2000, 1, 12, 12), time); + + time = parse_now("may 27 79"); + assertEquals(construct(1979, 5, 27, 12), time); + + time = parse_now("may 27 79 4:30"); + assertEquals(construct(1979, 5, 27, 16, 30), time); + + time = parse_now("may 27 79 at 4:30", new Options().setAmbiguousTimeRange(0)); + assertEquals(construct(1979, 5, 27, 4, 30), time); + + // sd_rm_sy + + time = parse_now("3 jan 2010"); + assertEquals(construct(2010, 1, 3, 12), time); + + time = parse_now("3 jan 2010 4pm"); + assertEquals(construct(2010, 1, 3, 16), time); + + // sm_sd_sy + + time = parse_now("5/27/1979"); + assertEquals(construct(1979, 5, 27, 12), time); + + time = parse_now("5/27/1979 4am"); + assertEquals(construct(1979, 5, 27, 4), time); + + // sd_sm_sy + + time = parse_now("27/5/1979"); + assertEquals(construct(1979, 5, 27, 12), time); + + time = parse_now("27/5/1979 @ 0700"); + assertEquals(construct(1979, 5, 27, 7), time); + + // sm_sy + + time = parse_now("05/06"); + assertEquals(construct(2006, 5, 16, 12), time); + + time = parse_now("12/06"); + assertEquals(construct(2006, 12, 16, 12), time); + + time = parse_now("13/06"); + assertEquals(null, time); + + // sy_sm_sd + + time = parse_now("2000-1-1"); + assertEquals(construct(2000, 1, 1, 12), time); + + time = parse_now("2006-08-20"); + assertEquals(construct(2006, 8, 20, 12), time); + + time = parse_now("2006-08-20 7pm"); + assertEquals(construct(2006, 8, 20, 19), time); + + time = parse_now("2006-08-20 03:00"); + assertEquals(construct(2006, 8, 20, 3), time); + + time = parse_now("2006-08-20 03:30:30"); + assertEquals(construct(2006, 8, 20, 3, 30, 30), time); + + time = parse_now("2006-08-20 15:30:30"); + assertEquals(construct(2006, 8, 20, 15, 30, 30), time); + + time = parse_now("2006-08-20 15:30.30"); + assertEquals(construct(2006, 8, 20, 15, 30, 30), time); + + time = parse_now("Mon Apr 02 17:00:00 PDT 2007"); + assertEquals(construct(2007, 4, 2, 17), time); + + //time = parse_now("jan 5 13:00"); + //assertEquals(Time.construct(2007, 1, 5, 13), time); + + // due to limitations of the Time class, these don't work + + time = parse_now("may 40"); + assertEquals(null, time); + + time = parse_now("may 27 40"); + assertEquals(null, time); + + time = parse_now("1800-08-20"); + assertEquals(null, time); + } + + @Test + public void test_foo() throws ParseException { + Chronic.parse("two months ago this friday"); + } + + @Test + public void testMonth() throws ParseException { + Span span = Chronic.parse("first day of next month"); + ZonedDateTime zdt = span.getBeginCalendar(); + span = Chronic.parse("first day of this month"); + zdt = span.getBeginCalendar(); + } + + @Test + public void test_parse_guess_r() throws ParseException { + Span time; + time = parse_now("friday"); + assertEquals(construct(2006, 8, 18, 12), time); + + time = parse_now("tue"); + assertEquals(construct(2006, 8, 22, 12), time); + + time = parse_now("5"); + assertEquals(construct(2006, 8, 16, 17), time); + + Options options = new Options() + .setAmbiguousTimeRange(0) + .setCompatibilityMode(true) + .setNow(construct(2006, 8, 16, 3, 0, 0)); + time = Chronic.parse("5", options); + assertEquals(construct(2006, 8, 16, 5), time); + + time = parse_now("13:00"); + assertEquals(construct(2006, 8, 17, 13), time); + + time = parse_now("13:45"); + assertEquals(construct(2006, 8, 17, 13, 45), time); + + time = parse_now("november"); + assertEquals(construct(2006, 11, 16), time); + } + + @Test + public void test_parse_guess_rr() { + Span time; + time = parse_now("friday 13:00"); + assertEquals(construct(2006, 8, 18, 13), time); + + time = parse_now("monday 4:00"); + assertEquals(construct(2006, 8, 21, 16), time); + + time = parse_now("sat 4:00", new Options().setAmbiguousTimeRange(0)); + assertEquals(construct(2006, 8, 19, 4), time); + + time = parse_now("sunday 4:20", new Options().setAmbiguousTimeRange(0)); + assertEquals(construct(2006, 8, 20, 4, 20), time); + + time = parse_now("4 pm"); + assertEquals(construct(2006, 8, 16, 16), time); + + time = parse_now("4 am", new Options().setAmbiguousTimeRange(0)); + assertEquals(construct(2006, 8, 16, 4), time); + + time = parse_now("12 pm"); + assertEquals(construct(2006, 8, 16, 12), time); + + time = parse_now("12:01 pm"); + assertEquals(construct(2006, 8, 16, 12, 1), time); + + time = parse_now("12:01 am"); + assertEquals(construct(2006, 8, 16, 0, 1), time); + + time = parse_now("12 am"); + assertEquals(construct(2006, 8, 16), time); + + time = parse_now("4:00 in the morning"); + assertEquals(construct(2006, 8, 16, 4), time); + + time = parse_now("november 4"); + assertEquals(construct(2006, 11, 4, 12), time); + + time = parse_now("aug 24"); + assertEquals(construct(2006, 8, 24, 12), time); + } + + @Test + public void test_parse_guess_rrr() { + Span time; + time = parse_now("friday 1 pm"); + assertEquals(construct(2006, 8, 18, 13), time); + + time = parse_now("friday 11 at night"); + assertEquals(construct(2006, 8, 18, 23), time); + + time = parse_now("friday 11 in the evening"); + assertEquals(construct(2006, 8, 18, 23), time); + + time = parse_now("sunday 6am"); + assertEquals(construct(2006, 8, 20, 6), time); + + time = parse_now("friday evening at 7"); + assertEquals(construct(2006, 8, 18, 19), time); + } + + @Test + public void test_parse_guess_gr() throws ParseException { + Span time; + // year + + time = parse_now("this year"); + //assertEquals(construct(2006, 10, 24, 12, 30), time); + assertEquals(construct(2006, 10, 24, 12), time); + + time = parse_now("this year", new Options().setContext(Pointer.PointerType.PAST)); + //assertEquals(construct(2006, 4, 24, 12, 30), time); + assertEquals(construct(2006, 4, 24, 12), time); + + // month + + time = parse_now("this month"); + assertEquals(construct(2006, 8, 24, 12), time); + + time = parse_now("this month", new Options().setContext(Pointer.PointerType.PAST)); + assertEquals(construct(2006, 8, 8, 12), time); + + Options options = new Options(); + options.setCompatibilityMode(true); + options.setNow(construct(2006, 11, 15)); + time = Chronic.parse("next month", options); + assertEquals(construct(2006, 12, 16, 12), time); + + // month name + + time = parse_now("last november"); + assertEquals(construct(2005, 11, 16), time); + + // fortnight + + time = parse_now("this fortnight"); + assertEquals(construct(2006, 8, 21, 19, 30), time); + + time = parse_now("this fortnight", new Options().setContext(Pointer.PointerType.PAST)); + assertEquals(construct(2006, 8, 14, 19), time); + + // week + + time = parse_now("this week"); + assertEquals(construct(2006, 8, 18, 7, 30), time); + + time = parse_now("this week", new Options().setContext(Pointer.PointerType.PAST)); + assertEquals(construct(2006, 8, 14, 19), time); + + // week + + time = parse_now("this weekend"); + assertEquals(construct(2006, 8, 20), time); + + time = parse_now("this weekend", new Options().setContext(Pointer.PointerType.PAST)); + assertEquals(construct(2006, 8, 13), time); + + time = parse_now("last weekend"); + assertEquals(construct(2006, 8, 13), time); + + // day + + time = parse_now("this day"); + assertEquals(construct(2006, 8, 16, 19, 30), time); + + time = parse_now("this day", new Options().setContext(Pointer.PointerType.PAST)); + assertEquals(construct(2006, 8, 16, 7), time); + + time = parse_now("today"); + assertEquals(construct(2006, 8, 16, 19, 30), time); + + time = parse_now("yesterday"); + assertEquals(construct(2006, 8, 15, 12), time); + + time = parse_now("tomorrow"); + assertEquals(construct(2006, 8, 17, 12), time); + + // day name + + time = parse_now("this tuesday"); + assertEquals(construct(2006, 8, 22, 12), time); + + time = parse_now("next tuesday"); + assertEquals(construct(2006, 8, 22, 12), time); + + time = parse_now("last tuesday"); + assertEquals(construct(2006, 8, 15, 12), time); + + time = parse_now("this wed"); + assertEquals(construct(2006, 8, 23, 12), time); + + time = parse_now("next wed"); + assertEquals(construct(2006, 8, 23, 12), time); + + time = parse_now("last wed"); + assertEquals(construct(2006, 8, 9, 12), time); + + // day portion + + time = parse_now("this morning"); + assertEquals(construct(2006, 8, 16, 9), time); + + time = parse_now("tonight"); + assertEquals(construct(2006, 8, 16, 22), time); + + // minute + + time = parse_now("next minute"); + assertEquals(construct(2006, 8, 16, 14, 1, 30), time); + + // second + + time = parse_now("this second"); + assertEquals(construct(2006, 8, 16, 14), time); + + time = parse_now("this second", new Options().setContext(Pointer.PointerType.PAST)); + assertEquals(construct(2006, 8, 16, 14), time); + + time = parse_now("next second"); + assertEquals(construct(2006, 8, 16, 14, 0, 1), time); + + time = parse_now("last second"); + assertEquals(construct(2006, 8, 16, 13, 59, 59), time); + } + + @Test + public void test_parse_guess_grr() { + Span time; + time = parse_now("yesterday at 4:00"); + assertEquals(construct(2006, 8, 15, 16), time); + + time = parse_now("today at 9:00"); + assertEquals(construct(2006, 8, 16, 9), time); + + time = parse_now("today at 2100"); + assertEquals(construct(2006, 8, 16, 21), time); + + time = parse_now("this day at 0900"); + assertEquals(construct(2006, 8, 16, 9), time); + + time = parse_now("tomorrow at 0900"); + assertEquals(construct(2006, 8, 17, 9), time); + + time = parse_now("yesterday at 4:00", new Options().setAmbiguousTimeRange(0)); + assertEquals(construct(2006, 8, 15, 4), time); + + time = parse_now("last friday at 4:00"); + assertEquals(construct(2006, 8, 11, 16), time); + + time = parse_now("next wed 4:00"); + assertEquals(construct(2006, 8, 23, 16), time); + + time = parse_now("yesterday afternoon"); + assertEquals(construct(2006, 8, 15, 15), time); + + time = parse_now("last week tuesday"); + assertEquals(construct(2006, 8, 8, 12), time); + + time = parse_now("tonight at 7"); + assertEquals(construct(2006, 8, 16, 19), time); + + time = parse_now("tonight 7"); + assertEquals(construct(2006, 8, 16, 19), time); + + time = parse_now("7 tonight"); + assertEquals(construct(2006, 8, 16, 19), time); + } + + @Test + public void test_parse_guess_grrr() { + Span time; + time = parse_now("today at 6:00pm"); + assertEquals(construct(2006, 8, 16, 18), time); + + time = parse_now("today at 6:00am"); + assertEquals(construct(2006, 8, 16, 6), time); + + time = parse_now("this day 1800"); + assertEquals(construct(2006, 8, 16, 18), time); + + time = parse_now("yesterday at 4:00pm"); + assertEquals(construct(2006, 8, 15, 16), time); + + time = parse_now("tomorrow evening at 7"); + assertEquals(construct(2006, 8, 17, 19), time); + + time = parse_now("tomorrow morning at 5:30"); + assertEquals(construct(2006, 8, 17, 5, 30), time); + + time = parse_now("next monday at 12:01 am"); + assertEquals(construct(2006, 8, 21, 0, 1), time); + + time = parse_now("next monday at 12:01 pm"); + assertEquals(construct(2006, 8, 21, 12, 1), time); + } + + @Test + public void test_parse_guess_rgr() { + Span time; + time = parse_now("afternoon yesterday"); + assertEquals(construct(2006, 8, 15, 15), time); + + time = parse_now("tuesday last week"); + assertEquals(construct(2006, 8, 8, 12), time); + } + + @Test + public void test_parse_guess_s_r_p() throws ParseException { + Span time; + + time = parse_now("3 years ago"); + assertEquals(construct(2003, 8, 16, 14), time); + + time = parse_now("1 month ago"); + assertEquals(construct(2006, 7, 16, 14), time); + + time = parse_now("1 fortnight ago"); + assertEquals(construct(2006, 8, 2, 14), time); + + time = parse_now("2 fortnights ago"); + assertEquals(construct(2006, 7, 19, 14), time); + + time = parse_now("3 weeks ago"); + assertEquals(construct(2006, 7, 26, 14), time); + + time = parse_now("2 weekends ago"); + assertEquals(construct(2006, 8, 5), time); + + time = parse_now("3 days ago"); + assertEquals(construct(2006, 8, 13, 14), time); + + //time = parse_now("1 monday ago"); + //assertEquals(Time.construct(2006, 8, 14, 12), time); + + time = parse_now("5 mornings ago"); + assertEquals(construct(2006, 8, 12, 9), time); + + time = parse_now("7 hours ago"); + assertEquals(construct(2006, 8, 16, 7), time); + + time = parse_now("3 minutes ago"); + assertEquals(construct(2006, 8, 16, 13, 57), time); + + time = parse_now("20 seconds before now"); + assertEquals(construct(2006, 8, 16, 13, 59, 40), time); + + // future + + time = parse_now("3 years from now"); + assertEquals(construct(2009, 8, 16, 14, 0, 0), time); + + time = parse_now("6 months hence"); + assertEquals(construct(2007, 2, 16, 14), time); + + time = parse_now("3 fortnights hence"); + assertEquals(construct(2006, 9, 27, 14), time); + + time = parse_now("1 week from now"); + assertEquals(construct(2006, 8, 23, 14, 0, 0), time); + + time = parse_now("1 weekend from now"); + assertEquals(construct(2006, 8, 19), time); + + time = parse_now("2 weekends from now"); + assertEquals(construct(2006, 8, 26), time); + + time = parse_now("1 day hence"); + assertEquals(construct(2006, 8, 17, 14), time); + + time = parse_now("5 mornings hence"); + assertEquals(construct(2006, 8, 21, 9), time); + + time = parse_now("1 hour from now"); + assertEquals(construct(2006, 8, 16, 15), time); + + time = parse_now("20 minutes hence"); + assertEquals(construct(2006, 8, 16, 14, 20), time); + + time = parse_now("20 seconds from now"); + assertEquals(construct(2006, 8, 16, 14, 0, 20), time); + + Options options = new Options(); + options.setCompatibilityMode(true); + options.setNow(construct(2007, 3, 7, 23, 30)); + time = Chronic.parse("2 months ago", options); + assertEquals(construct(2007, 1, 7, 23, 30), time); + } + + @Test + public void test_parse_guess_p_s_r() { + Span time; + time = parse_now("in 3 hours"); + assertEquals(construct(2006, 8, 16, 17), time); + } + + @Test + public void test_parse_guess_s_r_p_a() { + Span time; + + time = parse_now("3 years ago tomorrow"); + assertEquals(construct(2003, 8, 17, 12), time); + + time = parse_now("3 years ago this friday"); + assertEquals(construct(2003, 8, 18, 12), time); + + time = parse_now("3 months ago saturday at 5:00 pm"); + assertEquals(construct(2006, 5, 19, 17), time); + + time = parse_now("2 days from this second"); + assertEquals(construct(2006, 8, 18, 14), time); + + time = parse_now("7 hours before tomorrow at midnight"); + assertEquals(construct(2006, 8, 17, 17), time); + + } + + @Test + public void test_parse_guess_o_r_s_r() { + Span time = parse_now("3rd wednesday in november"); + assertEquals(construct(2006, 11, 15, 12), time); + + time = parse_now("10th wednesday in november"); + assertEquals(null, time); + + //time = parse_now("3rd wednesday in 2007"); + //assertEquals(Time.construct(2007, 1, 20, 12), time); + } + + @Test + public void test_parse_guess_o_r_g_r() { + Span time; + time = parse_now("3rd month next year"); + //assertEquals(Time.construct(2007, 3, 16, 12, 30), time); + //assertEquals(construct(2007, 3, 16, 11, 30), time); + assertEquals(construct(2007, 3, 16, 12), time); + + time = parse_now("3rd thursday this september"); + assertEquals(construct(2006, 9, 21, 12), time); + + time = parse_now("4th day last week"); + assertEquals(construct(2006, 8, 9, 12), time); + } + + @Test + public void test_parse_guess_nonsense() { + parse_now("some stupid nonsense"); + } + + @Test + public void test_parse_span() throws ParseException { + Span span; + span = parse_now("friday", new Options().setGuess(false)); + assertEquals(construct(2006, 8, 18), span.getBeginCalendar()); + assertEquals(construct(2006, 8, 19), span.getEndCalendar()); + + span = parse_now("november", new Options().setGuess(false)); + assertEquals(construct(2006, 11), span.getBeginCalendar()); + assertEquals(construct(2006, 12), span.getEndCalendar()); + + Options options = new Options().setNow(time_2006_08_16_14_00_00).setGuess(false); + span = Chronic.parse("weekend", options); + assertEquals(construct(2006, 8, 19), span.getBeginCalendar()); + assertEquals(construct(2006, 8, 21), span.getEndCalendar()); + } + + @Test + public void test_parse_words() { + assertEquals(parse_now("33 days from now"), parse_now("thirty-three days from now")); + assertEquals(parse_now("2867532 seconds from now"), parse_now("two million eight hundred and sixty seven thousand five hundred and thirty two seconds from now")); + assertEquals(parse_now("may 10th"), parse_now("may tenth")); + } + + @Test + public void test_parse_only_complete_pointers() { + assertEquals(time_2006_08_16_14_00_00, parse_now("eat pasty buns today at 2pm")); + assertEquals(time_2006_08_16_14_00_00, parse_now("futuristically speaking today at 2pm")); + assertEquals(time_2006_08_16_14_00_00, parse_now("meeting today at 2pm")); + } + + Span parse_now(String string) { + return parse_now(string, new Options()); + } + + Span parse_now(String string, Options options) { + options.setNow(time_2006_08_16_14_00_00); + options.setCompatibilityMode(true); + try { + return Chronic.parse(string, options); + } catch (ParseException e) { + // skip + } + return null; + } + +} diff --git a/src/test/java/org/xbib/time/chronic/RepeaterDayNameTest.java b/src/test/java/org/xbib/time/chronic/RepeaterDayNameTest.java new file mode 100644 index 0000000..7209146 --- /dev/null +++ b/src/test/java/org/xbib/time/chronic/RepeaterDayNameTest.java @@ -0,0 +1,64 @@ +package org.xbib.time.chronic; + +import org.junit.Assert; +import org.junit.Test; +import org.xbib.time.chronic.repeaters.RepeaterDayName; +import org.xbib.time.chronic.tags.Pointer; + +import java.time.ZoneId; +import java.time.ZonedDateTime; + +public class RepeaterDayNameTest extends Assert { + + private final static ZoneId ZONE_ID = ZoneId.of("GMT"); + private ZonedDateTime now = construct(2006, 8, 16, 14, 0, 0); + + public static ZonedDateTime construct(int year, int month, int day) { + return ZonedDateTime.of(year, month, day, 0, 0, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes, int seconds) { + return ZonedDateTime.of(year, month, day, hour, minutes, seconds, 0, ZONE_ID); + } + + @Test + public void testMatch() { + Token token = new Token("saturday"); + RepeaterDayName repeater = RepeaterDayName.scan(token); + assertEquals(RepeaterDayName.DayName.SATURDAY, repeater.getType()); + + token = new Token("sunday"); + repeater = RepeaterDayName.scan(token); + assertEquals(RepeaterDayName.DayName.SUNDAY, repeater.getType()); + } + + @Test + public void testNextFuture() { + Span span; + + RepeaterDayName mondays = new RepeaterDayName(RepeaterDayName.DayName.MONDAY); + mondays.setNow(now); + span = mondays.nextSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 8, 21), span.getBeginCalendar()); + assertEquals(construct(2006, 8, 22), span.getEndCalendar()); + + span = mondays.nextSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 8, 28), span.getBeginCalendar()); + assertEquals(construct(2006, 8, 29), span.getEndCalendar()); + } + + @Test + public void testNextPast() { + Span span; + + RepeaterDayName mondays = new RepeaterDayName(RepeaterDayName.DayName.MONDAY); + mondays.setNow(now); + span = mondays.nextSpan(Pointer.PointerType.PAST); + assertEquals(construct(2006, 8, 14), span.getBeginCalendar()); + assertEquals(construct(2006, 8, 15), span.getEndCalendar()); + + span = mondays.nextSpan(Pointer.PointerType.PAST); + assertEquals(construct(2006, 8, 7), span.getBeginCalendar()); + assertEquals(construct(2006, 8, 8), span.getEndCalendar()); + } +} diff --git a/src/test/java/org/xbib/time/chronic/RepeaterFortnightTest.java b/src/test/java/org/xbib/time/chronic/RepeaterFortnightTest.java new file mode 100644 index 0000000..c5b1348 --- /dev/null +++ b/src/test/java/org/xbib/time/chronic/RepeaterFortnightTest.java @@ -0,0 +1,90 @@ +package org.xbib.time.chronic; + +import org.junit.Assert; +import org.junit.Test; +import org.xbib.time.chronic.repeaters.RepeaterFortnight; +import org.xbib.time.chronic.repeaters.RepeaterWeek; +import org.xbib.time.chronic.tags.Pointer; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +public class RepeaterFortnightTest extends Assert { + + private final static ZoneId ZONE_ID = ZoneId.of("GMT"); + private ZonedDateTime now = construct(2006, 8, 16, 14, 0, 0); + + public static ZonedDateTime construct(int year, int month, int day) { + return ZonedDateTime.of(year, month, day, 0, 0, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour) { + return ZonedDateTime.of(year, month, day, hour, 0, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes) { + return ZonedDateTime.of(year, month, day, hour, minutes, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes, int seconds) { + return ZonedDateTime.of(year, month, day, hour, minutes, seconds, 0, ZONE_ID); + } + + @Test + public void testNextFuture() { + RepeaterFortnight fortnights = new RepeaterFortnight(); + fortnights.setNow(now); + + Span nextFortnight = fortnights.nextSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 8, 20), nextFortnight.getBeginCalendar()); + assertEquals(construct(2006, 9, 3), nextFortnight.getEndCalendar()); + + Span nextNextFortnight = fortnights.nextSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 9, 3), nextNextFortnight.getBeginCalendar()); + assertEquals(construct(2006, 9, 17), nextNextFortnight.getEndCalendar()); + } + + @Test + public void testNextPast() { + RepeaterFortnight fortnights = new RepeaterFortnight(); + fortnights.setNow(now); + Span lastFortnight = fortnights.nextSpan(Pointer.PointerType.PAST); + assertEquals(construct(2006, 7, 30), lastFortnight.getBeginCalendar()); + assertEquals(construct(2006, 8, 13), lastFortnight.getEndCalendar()); + + Span lastLastFortnight = fortnights.nextSpan(Pointer.PointerType.PAST); + assertEquals(construct(2006, 7, 16), lastLastFortnight.getBeginCalendar()); + assertEquals(construct(2006, 7, 30), lastLastFortnight.getEndCalendar()); + } + + @Test + public void testThisFuture() { + RepeaterFortnight fortnights = new RepeaterFortnight(); + fortnights.setNow(now); + + Span thisFortnight = fortnights.thisSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 8, 16, 15), thisFortnight.getBeginCalendar()); + assertEquals(construct(2006, 8, 27), thisFortnight.getEndCalendar()); + } + + @Test + public void testThisPast() { + RepeaterFortnight fortnights = new RepeaterFortnight(); + fortnights.setNow(now); + + Span thisFortnight = fortnights.thisSpan(Pointer.PointerType.PAST); + assertEquals(construct(2006, 8, 13, 0), thisFortnight.getBeginCalendar()); + assertEquals(construct(2006, 8, 16, 14), thisFortnight.getEndCalendar()); + } + + @Test + public void testOffset() { + Span span = new Span(now, ChronoUnit.SECONDS, 1); + + Span offsetSpan = new RepeaterWeek().getOffset(span, 3, Pointer.PointerType.FUTURE); + + assertEquals(construct(2006, 9, 6, 14), offsetSpan.getBeginCalendar()); + assertEquals(construct(2006, 9, 6, 14, 0, 1), offsetSpan.getEndCalendar()); + } +} diff --git a/src/test/java/org/xbib/time/chronic/RepeaterHourTest.java b/src/test/java/org/xbib/time/chronic/RepeaterHourTest.java new file mode 100644 index 0000000..39ed871 --- /dev/null +++ b/src/test/java/org/xbib/time/chronic/RepeaterHourTest.java @@ -0,0 +1,88 @@ +package org.xbib.time.chronic; + +import org.junit.Assert; +import org.junit.Test; +import org.xbib.time.chronic.repeaters.RepeaterHour; +import org.xbib.time.chronic.tags.Pointer; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +public class RepeaterHourTest extends Assert { + + private final static ZoneId ZONE_ID = ZoneId.of("GMT"); + private ZonedDateTime now = construct(2006, 8, 16, 14, 0, 0); + + public static ZonedDateTime construct(int year, int month, int day, int hour) { + return ZonedDateTime.of(year, month, day, hour, 0, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes) { + return ZonedDateTime.of(year, month, day, hour, minutes, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes, int seconds) { + return ZonedDateTime.of(year, month, day, hour, minutes, seconds, 0, ZONE_ID); + } + + @Test + public void testNextFuture() { + RepeaterHour hours = new RepeaterHour(); + hours.setNow(now); + + Span nextHour = hours.nextSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 8, 16, 15), nextHour.getBeginCalendar()); + assertEquals(construct(2006, 8, 16, 16), nextHour.getEndCalendar()); + + Span nextNextHour = hours.nextSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 8, 16, 16), nextNextHour.getBeginCalendar()); + assertEquals(construct(2006, 8, 16, 17), nextNextHour.getEndCalendar()); + } + + @Test + public void testNextPast() { + RepeaterHour hours = new RepeaterHour(); + hours.setNow(now); + Span lastHour = hours.nextSpan(Pointer.PointerType.PAST); + assertEquals(construct(2006, 8, 16, 13), lastHour.getBeginCalendar()); + assertEquals(construct(2006, 8, 16, 14), lastHour.getEndCalendar()); + + Span lastLastHour = hours.nextSpan(Pointer.PointerType.PAST); + assertEquals(construct(2006, 8, 16, 12), lastLastHour.getBeginCalendar()); + assertEquals(construct(2006, 8, 16, 13), lastLastHour.getEndCalendar()); + } + + @Test + public void testThis() { + now = construct(2006, 8, 16, 14, 30); + + RepeaterHour hours = new RepeaterHour(); + hours.setNow(now); + + Span thisHour; + thisHour = hours.thisSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 8, 16, 14, 31), thisHour.getBeginCalendar()); + assertEquals(construct(2006, 8, 16, 15), thisHour.getEndCalendar()); + + thisHour = hours.thisSpan(Pointer.PointerType.PAST); + assertEquals(construct(2006, 8, 16, 14), thisHour.getBeginCalendar()); + assertEquals(construct(2006, 8, 16, 14, 30), thisHour.getEndCalendar()); + } + + @Test + public void testOffset() { + Span span = new Span(now, ChronoUnit.SECONDS, 1); + + Span offsetSpan; + offsetSpan = new RepeaterHour().getOffset(span, 3, Pointer.PointerType.FUTURE); + + assertEquals(construct(2006, 8, 16, 17), offsetSpan.getBeginCalendar()); + assertEquals(construct(2006, 8, 16, 17, 0, 1), offsetSpan.getEndCalendar()); + + offsetSpan = new RepeaterHour().getOffset(span, 24, Pointer.PointerType.PAST); + + assertEquals(construct(2006, 8, 15, 14), offsetSpan.getBeginCalendar()); + assertEquals(construct(2006, 8, 15, 14, 0, 1), offsetSpan.getEndCalendar()); + } +} diff --git a/src/test/java/org/xbib/time/chronic/RepeaterMonthNameTest.java b/src/test/java/org/xbib/time/chronic/RepeaterMonthNameTest.java new file mode 100644 index 0000000..7274140 --- /dev/null +++ b/src/test/java/org/xbib/time/chronic/RepeaterMonthNameTest.java @@ -0,0 +1,65 @@ +package org.xbib.time.chronic; + +import org.junit.Assert; +import org.junit.Test; +import org.xbib.time.chronic.repeaters.RepeaterMonthName; +import org.xbib.time.chronic.tags.Pointer; + +import java.time.ZoneId; +import java.time.ZonedDateTime; + +public class RepeaterMonthNameTest extends Assert { + + private final static ZoneId ZONE_ID = ZoneId.of("GMT"); + private ZonedDateTime now = construct(2006, 8, 16, 14, 0, 0); + + public static ZonedDateTime construct(int year, int month) { + return ZonedDateTime.of(year, month, 1, 0, 0, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes, int seconds) { + return ZonedDateTime.of(year, month, day, hour, minutes, seconds, 0, ZONE_ID); + } + + @Test + public void testNext() { + RepeaterMonthName mays = new RepeaterMonthName(RepeaterMonthName.MonthName.MAY); + mays.setNow(now); + + Span nextMay = mays.nextSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2007, 5), nextMay.getBeginCalendar()); + assertEquals(construct(2007, 6), nextMay.getEndCalendar()); + + Span nextNextMay = mays.nextSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2008, 5), nextNextMay.getBeginCalendar()); + assertEquals(construct(2008, 6), nextNextMay.getEndCalendar()); + + RepeaterMonthName decembers = new RepeaterMonthName(RepeaterMonthName.MonthName.DECEMBER); + decembers.setNow(now); + + Span nextDecember = decembers.nextSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 12), nextDecember.getBeginCalendar()); + assertEquals(construct(2007, 1), nextDecember.getEndCalendar()); + + mays = new RepeaterMonthName(RepeaterMonthName.MonthName.MAY); + mays.setNow(now); + + assertEquals(construct(2006, 5), mays.nextSpan(Pointer.PointerType.PAST).getBeginCalendar()); + assertEquals(construct(2005, 5), mays.nextSpan(Pointer.PointerType.PAST).getBeginCalendar()); + } + + + @Test + public void testThis() { + RepeaterMonthName octobers = new RepeaterMonthName(RepeaterMonthName.MonthName.MAY); + octobers.setNow(now); + + Span nextMay = octobers.nextSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2007, 5), nextMay.getBeginCalendar()); + assertEquals(construct(2007, 6), nextMay.getEndCalendar()); + + Span nextNextMay = octobers.nextSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2008, 5), nextNextMay.getBeginCalendar()); + assertEquals(construct(2008, 6), nextNextMay.getEndCalendar()); + } +} diff --git a/src/test/java/org/xbib/time/chronic/RepeaterMonthTest.java b/src/test/java/org/xbib/time/chronic/RepeaterMonthTest.java new file mode 100644 index 0000000..0940599 --- /dev/null +++ b/src/test/java/org/xbib/time/chronic/RepeaterMonthTest.java @@ -0,0 +1,44 @@ +package org.xbib.time.chronic; + +import org.junit.Assert; +import org.junit.Test; +import org.xbib.time.chronic.repeaters.RepeaterMonth; +import org.xbib.time.chronic.tags.Pointer; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +public class RepeaterMonthTest extends Assert { + + private final static ZoneId ZONE_ID = ZoneId.of("GMT"); + private ZonedDateTime now = construct(2006, 8, 16, 14, 0, 0); + + public static ZonedDateTime construct(int year, int month, int day, int hour) { + return ZonedDateTime.of(year, month, day, hour, 0, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes) { + return ZonedDateTime.of(year, month, day, hour, minutes, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes, int seconds) { + return ZonedDateTime.of(year, month, day, hour, minutes, seconds, 0, ZONE_ID); + } + + @Test + public void testOffset() { + Span span = new Span(now, ChronoUnit.SECONDS, 60); + + Span offsetSpan; + offsetSpan = new RepeaterMonth().getOffset(span, 1, Pointer.PointerType.FUTURE); + + assertEquals(construct(2006, 9, 16, 14), offsetSpan.getBeginCalendar()); + assertEquals(construct(2006, 9, 16, 14, 1), offsetSpan.getEndCalendar()); + + offsetSpan = new RepeaterMonth().getOffset(span, 1, Pointer.PointerType.PAST); + + assertEquals(construct(2006, 7, 16, 14), offsetSpan.getBeginCalendar()); + assertEquals(construct(2006, 7, 16, 14, 1), offsetSpan.getEndCalendar()); + } +} diff --git a/src/test/java/org/xbib/time/chronic/RepeaterTimeTest.java b/src/test/java/org/xbib/time/chronic/RepeaterTimeTest.java new file mode 100644 index 0000000..2117cc8 --- /dev/null +++ b/src/test/java/org/xbib/time/chronic/RepeaterTimeTest.java @@ -0,0 +1,94 @@ +package org.xbib.time.chronic; + +import org.junit.Assert; +import org.junit.Test; +import org.xbib.time.chronic.repeaters.RepeaterTime; +import org.xbib.time.chronic.tags.Pointer; + +import java.time.ZoneId; +import java.time.ZonedDateTime; + +public class RepeaterTimeTest extends Assert { + + private final static ZoneId ZONE_ID = ZoneId.of("GMT"); + private ZonedDateTime now = construct(2006, 8, 16, 14, 0, 0); + + public static ZonedDateTime construct(int year, int month, int day, int hour) { + return ZonedDateTime.of(year, month, day, hour, 0, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes) { + return ZonedDateTime.of(year, month, day, hour, minutes, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes, int seconds) { + return ZonedDateTime.of(year, month, day, hour, minutes, seconds, 0, ZONE_ID); + } + + @Test + public void testNextFuture() { + RepeaterTime t; + + t = new RepeaterTime("4:00"); + t.setNow(now); + + assertEquals(construct(2006, 8, 16, 16), t.nextSpan(Pointer.PointerType.FUTURE).getBeginCalendar()); + assertEquals(construct(2006, 8, 17, 4), t.nextSpan(Pointer.PointerType.FUTURE).getBeginCalendar()); + + t = new RepeaterTime("13:00"); + t.setNow(now); + + assertEquals(construct(2006, 8, 17, 13), t.nextSpan(Pointer.PointerType.FUTURE).getBeginCalendar()); + assertEquals(construct(2006, 8, 18, 13), t.nextSpan(Pointer.PointerType.FUTURE).getBeginCalendar()); + + t = new RepeaterTime("0400"); + t.setNow(now); + + assertEquals(construct(2006, 8, 17, 4), t.nextSpan(Pointer.PointerType.FUTURE).getBeginCalendar()); + assertEquals(construct(2006, 8, 18, 4), t.nextSpan(Pointer.PointerType.FUTURE).getBeginCalendar()); + } + + @Test + public void testNextPast() { + RepeaterTime t; + t = new RepeaterTime("4:00"); + t.setNow(now); + + assertEquals(construct(2006, 8, 16, 4), t.nextSpan(Pointer.PointerType.PAST).getBeginCalendar()); + assertEquals(construct(2006, 8, 15, 16), t.nextSpan(Pointer.PointerType.PAST).getBeginCalendar()); + + t = new RepeaterTime("13:00"); + t.setNow(now); + + assertEquals(construct(2006, 8, 16, 13), t.nextSpan(Pointer.PointerType.PAST).getBeginCalendar()); + assertEquals(construct(2006, 8, 15, 13), t.nextSpan(Pointer.PointerType.PAST).getBeginCalendar()); + } + + @Test + public void testType() { + RepeaterTime t1; + t1 = new RepeaterTime("4"); + assertEquals(14400, t1.getType().intValue()); + + t1 = new RepeaterTime("14"); + assertEquals(50400, t1.getType().intValue()); + + t1 = new RepeaterTime("4:00"); + assertEquals(14400, t1.getType().intValue()); + + t1 = new RepeaterTime("4:30"); + assertEquals(16200, t1.getType().intValue()); + + t1 = new RepeaterTime("1400"); + assertEquals(50400, t1.getType().intValue()); + + t1 = new RepeaterTime("0400"); + assertEquals(14400, t1.getType().intValue()); + + t1 = new RepeaterTime("04"); + assertEquals(14400, t1.getType().intValue()); + + t1 = new RepeaterTime("400"); + assertEquals(14400, t1.getType().intValue()); + } +} diff --git a/src/test/java/org/xbib/time/chronic/RepeaterWeekTest.java b/src/test/java/org/xbib/time/chronic/RepeaterWeekTest.java new file mode 100644 index 0000000..ee53bb2 --- /dev/null +++ b/src/test/java/org/xbib/time/chronic/RepeaterWeekTest.java @@ -0,0 +1,85 @@ +package org.xbib.time.chronic; + +import org.junit.Assert; +import org.junit.Test; +import org.xbib.time.chronic.repeaters.RepeaterWeek; +import org.xbib.time.chronic.tags.Pointer; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +public class RepeaterWeekTest extends Assert { + + private final static ZoneId ZONE_ID = ZoneId.of("GMT"); + private ZonedDateTime now = construct(2006, 8, 16, 14, 0, 0); + + public static ZonedDateTime construct(int year, int month, int day) { + return ZonedDateTime.of(year, month, day, 0, 0, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes, int seconds) { + return ZonedDateTime.of(year, month, day, hour, minutes, seconds, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour) { + return ZonedDateTime.of(year, month, day, hour, 0, 0, 0, ZONE_ID); + } + + @Test + public void testNextFuture() { + RepeaterWeek weeks = new RepeaterWeek(); + weeks.setNow(now); + + Span nextWeek = weeks.nextSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 8, 20), nextWeek.getBeginCalendar()); + assertEquals(construct(2006, 8, 27), nextWeek.getEndCalendar()); + + Span nextNextWeek = weeks.nextSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 8, 27), nextNextWeek.getBeginCalendar()); + assertEquals(construct(2006, 9, 3), nextNextWeek.getEndCalendar()); + } + + @Test + public void testNextPast() { + RepeaterWeek weeks = new RepeaterWeek(); + weeks.setNow(now); + Span lastWeek = weeks.nextSpan(Pointer.PointerType.PAST); + assertEquals(construct(2006, 8, 6), lastWeek.getBeginCalendar()); + assertEquals(construct(2006, 8, 13), lastWeek.getEndCalendar()); + + Span lastLastWeek = weeks.nextSpan(Pointer.PointerType.PAST); + assertEquals(construct(2006, 7, 30), lastLastWeek.getBeginCalendar()); + assertEquals(construct(2006, 8, 6), lastLastWeek.getEndCalendar()); + } + + @Test + public void testThisFuture() { + RepeaterWeek weeks = new RepeaterWeek(); + weeks.setNow(now); + + Span thisWeek = weeks.thisSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 8, 16, 15), thisWeek.getBeginCalendar()); + assertEquals(construct(2006, 8, 20), thisWeek.getEndCalendar()); + } + + @Test + public void testThisPast() { + RepeaterWeek weeks = new RepeaterWeek(); + weeks.setNow(now); + + Span thisWeek = weeks.thisSpan(Pointer.PointerType.PAST); + assertEquals(construct(2006, 8, 13, 0), thisWeek.getBeginCalendar()); + assertEquals(construct(2006, 8, 16, 14), thisWeek.getEndCalendar()); + } + + @Test + public void testOffset() { + Span span = new Span(now, ChronoUnit.SECONDS, 1); + + Span offsetSpan = new RepeaterWeek().getOffset(span, 3, Pointer.PointerType.FUTURE); + + assertEquals(construct(2006, 9, 6, 14), offsetSpan.getBeginCalendar()); + assertEquals(construct(2006, 9, 6, 14, 0, 1), offsetSpan.getEndCalendar()); + } +} diff --git a/src/test/java/org/xbib/time/chronic/RepeaterWeekendTest.java b/src/test/java/org/xbib/time/chronic/RepeaterWeekendTest.java new file mode 100644 index 0000000..40a673d --- /dev/null +++ b/src/test/java/org/xbib/time/chronic/RepeaterWeekendTest.java @@ -0,0 +1,92 @@ +package org.xbib.time.chronic; + +import org.junit.Assert; +import org.junit.Test; +import org.xbib.time.chronic.repeaters.RepeaterWeekend; +import org.xbib.time.chronic.tags.Pointer; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +public class RepeaterWeekendTest extends Assert { + + private final static ZoneId ZONE_ID = ZoneId.of("GMT"); + private ZonedDateTime now = construct(2006, 8, 16, 14, 0, 0); + + public static ZonedDateTime construct(int year, int month, int day) { + return ZonedDateTime.of(year, month, day, 0, 0, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes, int seconds) { + return ZonedDateTime.of(year, month, day, hour, minutes, seconds, 0, ZONE_ID); + } + + @Test + public void testNextFuture() { + RepeaterWeekend weekends = new RepeaterWeekend(); + weekends.setNow(now); + + Span nextWeekend = weekends.nextSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 8, 19), nextWeekend.getBeginCalendar()); + assertEquals(construct(2006, 8, 21), nextWeekend.getEndCalendar()); + } + + @Test + public void testNextPast() { + RepeaterWeekend weekends = new RepeaterWeekend(); + weekends.setNow(now); + Span lastWeekend = weekends.nextSpan(Pointer.PointerType.PAST); + assertEquals(construct(2006, 8, 12), lastWeekend.getBeginCalendar()); + assertEquals(construct(2006, 8, 14), lastWeekend.getEndCalendar()); + } + + @Test + public void testThisFuture() { + RepeaterWeekend weekends = new RepeaterWeekend(); + weekends.setNow(now); + + Span thisWeekend = weekends.thisSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 8, 19), thisWeekend.getBeginCalendar()); + assertEquals(construct(2006, 8, 21), thisWeekend.getEndCalendar()); + } + + @Test + public void testThisPast() { + RepeaterWeekend weekends = new RepeaterWeekend(); + weekends.setNow(now); + + Span thisWeekend = weekends.thisSpan(Pointer.PointerType.PAST); + assertEquals(construct(2006, 8, 12), thisWeekend.getBeginCalendar()); + assertEquals(construct(2006, 8, 14), thisWeekend.getEndCalendar()); + } + + @Test + public void testThisNone() { + RepeaterWeekend weekends = new RepeaterWeekend(); + weekends.setNow(now); + + Span thisWeekend = weekends.thisSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 8, 19), thisWeekend.getBeginCalendar()); + assertEquals(construct(2006, 8, 21), thisWeekend.getEndCalendar()); + } + + @Test + public void testOffset() { + Span span = new Span(now, ChronoUnit.SECONDS, 1); + + Span offsetSpan; + + offsetSpan = new RepeaterWeekend().getOffset(span, 3, Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 9, 2), offsetSpan.getBeginCalendar()); + assertEquals(construct(2006, 9, 2, 0, 0, 1), offsetSpan.getEndCalendar()); + + offsetSpan = new RepeaterWeekend().getOffset(span, 1, Pointer.PointerType.PAST); + assertEquals(construct(2006, 8, 12), offsetSpan.getBeginCalendar()); + assertEquals(construct(2006, 8, 12, 0, 0, 1), offsetSpan.getEndCalendar()); + + offsetSpan = new RepeaterWeekend().getOffset(span, 0, Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 8, 12), offsetSpan.getBeginCalendar()); + assertEquals(construct(2006, 8, 12, 0, 0, 1), offsetSpan.getEndCalendar()); + } +} diff --git a/src/test/java/org/xbib/time/chronic/RepeaterYearTest.java b/src/test/java/org/xbib/time/chronic/RepeaterYearTest.java new file mode 100644 index 0000000..76b51f6 --- /dev/null +++ b/src/test/java/org/xbib/time/chronic/RepeaterYearTest.java @@ -0,0 +1,86 @@ +package org.xbib.time.chronic; + +import org.junit.Assert; +import org.junit.Test; +import org.xbib.time.chronic.repeaters.RepeaterYear; +import org.xbib.time.chronic.tags.Pointer; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +public class RepeaterYearTest extends Assert { + + private final static ZoneId ZONE_ID = ZoneId.of("GMT"); + private ZonedDateTime now = construct(2006, 8, 16, 14, 0, 0); + + public static ZonedDateTime construct(int year, int month, int day) { + return ZonedDateTime.of(year, month, day, 0, 0, 0, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour, int minutes, int seconds) { + return ZonedDateTime.of(year, month, day, hour, minutes, seconds, 0, ZONE_ID); + } + + public static ZonedDateTime construct(int year, int month, int day, int hour) { + return ZonedDateTime.of(year, month, day, hour, 0, 0, 0, ZONE_ID); + } + + @Test + public void testNextFuture() { + RepeaterYear years = new RepeaterYear(); + years.setNow(now); + + Span nextYear = years.nextSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2007, 1, 1), nextYear.getBeginCalendar()); + assertEquals(construct(2008, 1, 1), nextYear.getEndCalendar()); + + Span nextNextYear = years.nextSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2008, 1, 1), nextNextYear.getBeginCalendar()); + assertEquals(construct(2009, 1, 1), nextNextYear.getEndCalendar()); + } + + @Test + public void testNextPast() { + RepeaterYear years = new RepeaterYear(); + years.setNow(now); + Span lastYear = years.nextSpan(Pointer.PointerType.PAST); + assertEquals(construct(2005, 1, 1), lastYear.getBeginCalendar()); + assertEquals(construct(2006, 1, 1), lastYear.getEndCalendar()); + + Span lastLastYear = years.nextSpan(Pointer.PointerType.PAST); + assertEquals(construct(2004, 1, 1), lastLastYear.getBeginCalendar()); + assertEquals(construct(2005, 1, 1), lastLastYear.getEndCalendar()); + } + + @Test + public void testThis() { + RepeaterYear years = new RepeaterYear(); + years.setNow(now); + + Span thisYear; + thisYear = years.thisSpan(Pointer.PointerType.FUTURE); + assertEquals(construct(2006, 8, 17), thisYear.getBeginCalendar()); + assertEquals(construct(2007, 1, 1), thisYear.getEndCalendar()); + + thisYear = years.thisSpan(Pointer.PointerType.PAST); + assertEquals(construct(2006, 1, 1), thisYear.getBeginCalendar()); + assertEquals(construct(2006, 8, 16), thisYear.getEndCalendar()); + } + + @Test + public void testOffset() { + Span span = new Span(now, ChronoUnit.SECONDS, 1); + + Span offsetSpan; + offsetSpan = new RepeaterYear().getOffset(span, 3, Pointer.PointerType.FUTURE); + + assertEquals(construct(2009, 8, 16, 14), offsetSpan.getBeginCalendar()); + assertEquals(construct(2009, 8, 16, 14, 0, 1), offsetSpan.getEndCalendar()); + + offsetSpan = new RepeaterYear().getOffset(span, 10, Pointer.PointerType.PAST); + + assertEquals(construct(1996, 8, 16, 14), offsetSpan.getBeginCalendar()); + assertEquals(construct(1996, 8, 16, 14, 0, 1), offsetSpan.getEndCalendar()); + } +} diff --git a/src/test/java/org/xbib/time/chronic/SpanTest.java b/src/test/java/org/xbib/time/chronic/SpanTest.java new file mode 100644 index 0000000..dbd522e --- /dev/null +++ b/src/test/java/org/xbib/time/chronic/SpanTest.java @@ -0,0 +1,31 @@ +package org.xbib.time.chronic; + +import org.junit.Assert; +import org.junit.Test; + +import java.time.ZoneId; +import java.time.ZonedDateTime; + +public class SpanTest extends Assert { + + private final static ZoneId ZONE_ID = ZoneId.of("GMT"); + + public static ZonedDateTime construct(int year, int month, int day, int hour) { + return ZonedDateTime.of(year, month, day, hour, 0, 0, 0, ZONE_ID); + } + + @Test + public void testSpanWidth() { + Span span = new Span(construct(2006, 8, 16, 0), construct(2006, 8, 17, 0)); + assertEquals(60L * 60L * 24L, (long) span.getWidth()); + } + + @Test + public void testSpanMath() { + Span span = new Span(1, 2, ZoneId.of("GMT")); + assertEquals(2L, (long) span.add(1).getBegin()); + assertEquals(3L, (long) span.add(1).getEnd()); + assertEquals(0L, (long) span.add(-1).getBegin()); + assertEquals(1L, (long) span.add(-1).getEnd()); + } +} diff --git a/src/test/java/org/xbib/time/chronic/TokenTest.java b/src/test/java/org/xbib/time/chronic/TokenTest.java new file mode 100644 index 0000000..bbf9947 --- /dev/null +++ b/src/test/java/org/xbib/time/chronic/TokenTest.java @@ -0,0 +1,25 @@ +package org.xbib.time.chronic; + +import org.junit.Assert; +import org.junit.Test; +import org.xbib.time.chronic.tags.Scalar; +import org.xbib.time.chronic.tags.StringTag; + +public class TokenTest extends Assert { + + @Test + public void testToken() { + Token token = new Token("foo"); + assertEquals(0, token.getTags().size()); + assertFalse(token.isTagged()); + token.tag(new StringTag("mytag")); + assertEquals(1, token.getTags().size()); + assertTrue(token.isTagged()); + assertEquals(StringTag.class, token.getTag(StringTag.class).getClass()); + token.tag(new Scalar(5)); + assertEquals(2, token.getTags().size()); + token.untag(StringTag.class); + assertEquals(1, token.getTags().size()); + assertEquals("foo", token.getWord()); + } +} diff --git a/src/test/java/org/xbib/time/chronic/handlers/DummyHandler.java b/src/test/java/org/xbib/time/chronic/handlers/DummyHandler.java new file mode 100644 index 0000000..5d504e5 --- /dev/null +++ b/src/test/java/org/xbib/time/chronic/handlers/DummyHandler.java @@ -0,0 +1,16 @@ +package org.xbib.time.chronic.handlers; + +import org.xbib.time.chronic.Options; +import org.xbib.time.chronic.Span; +import org.xbib.time.chronic.Token; + +import java.util.List; + +/** + * + */ +public class DummyHandler implements IHandler { + public Span handle(List tokens, Options options) { + return null; + } +} diff --git a/src/test/java/org/xbib/time/format/PeriodFormatterBuilderTest.java b/src/test/java/org/xbib/time/format/PeriodFormatterBuilderTest.java new file mode 100644 index 0000000..5957c27 --- /dev/null +++ b/src/test/java/org/xbib/time/format/PeriodFormatterBuilderTest.java @@ -0,0 +1,24 @@ +package org.xbib.time.format; + +import org.junit.Test; + +import java.time.Period; + +import static org.junit.Assert.assertEquals; + +public class PeriodFormatterBuilderTest { + + @Test + public void test() { + PeriodFormatter yearsAndMonths = new PeriodFormatterBuilder() + .appendYears() + .appendSuffix(" year", " years") + .appendSeparator(" and ") + .appendMonths() + .appendSuffix(" month", " months") + .toFormatter(); + + assertEquals("3 years and 2 months", yearsAndMonths.print(Period.of(3, 2, 1))); + + } +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeAPIManipulationTest.java b/src/test/java/org/xbib/time/pretty/PrettyTimeAPIManipulationTest.java new file mode 100644 index 0000000..b61d578 --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeAPIManipulationTest.java @@ -0,0 +1,90 @@ +package org.xbib.time.pretty; + +import org.junit.Test; +import org.xbib.time.pretty.units.JustNow; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class PrettyTimeAPIManipulationTest { + + List list = null; + PrettyTime t = new PrettyTime(); + private TimeUnitQuantity timeUnitQuantity = null; + + @Test + public void testApiMisuse3() throws Exception { + t.clearUnits(); + } + + @Test(expected = IllegalArgumentException.class) + public void testApiMisuse6() throws Exception { + t.format(list); + } + + @Test(expected = IllegalArgumentException.class) + public void testApiMisuse8() throws Exception { + t.formatUnrounded(timeUnitQuantity); + } + + @Test(expected = IllegalArgumentException.class) + public void testApiMisuse9() throws Exception { + t.getFormat(null); + } + + @Test + public void testApiMisuse10() throws Exception { + t.getLocale(); + } + + @Test + public void testApiMisuse12() throws Exception { + t.getUnits(); + } + + @Test(expected = IllegalArgumentException.class) + public void testApiMisuse13() throws Exception { + t.registerUnit(null, null); + } + + @Test + public void testApiMisuse15() throws Exception { + t.toString(); + } + + @Test(expected = IllegalArgumentException.class) + public void testApiMisuse16() throws Exception { + t.removeUnit((Class) null); + } + + @Test(expected = IllegalArgumentException.class) + public void testApiMisuse17() throws Exception { + t.removeUnit((TimeUnit) null); + } + + @Test(expected = IllegalArgumentException.class) + public void testApiMisuse18() throws Exception { + t.getUnit(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testApiMisuse19() throws Exception { + t.getUnit(null); + } + + @Test + public void testGetUnit() { + JustNow unit = t.getUnit(JustNow.class); + assertNotNull(unit); + } + + @Test + public void testChangeUnit() { + JustNow unit = t.getUnit(JustNow.class); + assertEquals(1000L * 60L * 5L, unit.getMaxQuantity()); + unit.setMaxQuantity(1); + assertEquals(1, t.getUnit(JustNow.class).getMaxQuantity()); + } +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_AR_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_AR_Test.java new file mode 100644 index 0000000..7f2675c --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_AR_Test.java @@ -0,0 +1,250 @@ +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoField; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PrettyTimeI18n_AR_Test { + + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = new Locale("ar"); + Locale.setDefault(locale); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime localDateTime = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime p = new PrettyTime(localDateTime); + assertEquals("1 شهر مضت", p.format(LocalDateTime.of(2009, 5, 20, 0, 0))); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(); + assertEquals("بعد لحظات", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("بعد لحظات", t.format(600)); + } + + @Test + public void testMinutesFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("12 دقائق من الآن", t.format(1000 * 60 * 12)); + } + + @Test + public void testHoursFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 ساعات من الآن", t.format(1000 * 60 * 60 * 3)); + } + + @Test + public void testDaysFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 ايام من الآن", t.format(1000 * 60 * 60 * 24 * 3)); + } + + @Test + public void testWeeksFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 أسابيع من الآن", t.format(1000 * 60 * 60 * 24 * 7 * 3)); + } + + @Test + public void testMonthsFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 أشهر من الآن", t.format(2629743830L * 3L)); + } + + @Test + public void testYearsFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 سنوات من الآن", t.format(2629743830L * 12L * 3L)); + } + + @Test + public void testDecadesFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 عقود من الآن", t.format(315569259747L * 3L)); + } + + @Test + public void testCenturiesFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 قرون من الآن", t.format(3155692597470L * 3L)); + } + + @Test + public void testMomentsAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(6000), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("منذ لحظات", t.format(0)); + } + + @Test + public void testMinutesAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 12), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("12 دقائق مضت", t.format(0)); + } + + @Test + public void testHoursAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 3), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 ساعات مضت", t.format(0)); + } + + @Test + public void testDaysAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 ايام مضت", t.format(0)); + } + + @Test + public void testWeeksAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 7 * 3), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 أسابيع مضت", t.format(0)); + } + + @Test + public void testMonthsAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 أشهر مضت", t.format(0)); + } + + @Test + public void testCustomFormat() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + TimeUnit unit = new TimeUnit() { + @Override + public long getMaxQuantity() { + return 0; + } + + @Override + public long getMillisPerUnit() { + return 5000; + } + }; + t.clearUnits(); + t.registerUnit(unit, new SimpleTimeFormat() + .setSingularName("tick").setPluralName("ticks") + .setPattern("%n %u").setRoundingTolerance(20) + .setFutureSuffix("... RUN!") + .setFuturePrefix("self destruct in: ").setPastPrefix("self destruct was: ").setPastSuffix( + " ago...")); + + assertEquals("self destruct in: 5 ticks ... RUN!", t.format(25000)); + localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(25000), ZoneId.systemDefault()); + t.setReference(localDateTime); + assertEquals("self destruct was: 5 ticks ago...", t.format(0)); + } + + @Test + public void testYearsAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L * 12L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 سنوات مضت", t.format(0)); + } + + @Test + public void testDecadesAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(315569259747L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 عقود مضت", t.format(0)); + } + + @Test + public void testCenturiesAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(3155692597470L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 قرون مضت", t.format(0)); + } + + @Test + public void testWithinTwoHoursRounding() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minus(6543990, ChronoField.MILLI_OF_SECOND.getBaseUnit()); + assertEquals("2 ساعات مضت", t.format(localDateTime)); + } + + @Test + public void testPreciseInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); + assertEquals(5, timeUnitQuantities.get(0).getQuantity()); + assertEquals(10, timeUnitQuantities.get(1).getQuantity()); + } + + @Test + public void testPreciseInThePast() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); + assertEquals(-5, timeUnitQuantities.get(0).getQuantity()); + assertTrue(timeUnitQuantities.get(1).getQuantity() == -9 || timeUnitQuantities.get(1).getQuantity() == -10); + } + + @Test + public void testFormattingDurationListInThePast() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + List timeUnitQuantities = t.calculatePreciseDuration(LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault())); + assertEquals("3 ايام 15 ساعات 38 دقائق مضت", t.format(timeUnitQuantities)); + } + + @Test + public void testFormattingDurationListInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault())); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 ايام 15 ساعات 38 دقائق من الآن", t.format(timeUnitQuantities)); + } + + @Test + public void testSetLocale() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(315569259747L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("3 عقود مضت", t.format(0)); + } + + @After + public void tearDown() throws Exception { + Locale.setDefault(locale); + } + +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_BG_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_BG_Test.java new file mode 100644 index 0000000..0ddcc86 --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_BG_Test.java @@ -0,0 +1,224 @@ +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeI18n_BG_Test { + + // Stores current locale so that it can be restored + private Locale locale; + + // Method setUp() is called automatically before every test method + @Before + public void setUp() throws Exception { + locale = Locale.getDefault(); + Locale.setDefault(new Locale("bg")); + } + + @Test + public void testCenturiesFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 3 века", t.format(3155692597470L * 3L)); + } + + @Test + public void testCenturiesAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(3155692597470L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 3 века", t.format(0)); + } + + @Test + public void testCenturySingular() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(3155692597470L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 1 век", t.format(0)); + } + + @Test + public void testDaysFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 3 дни", t.format(1000 * 60 * 60 * 24 * 3)); + } + + @Test + public void testDaysAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 3 дни", t.format(0)); + } + + @Test + public void testDaySingular() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 1 ден", t.format(0)); + } + + @Test + public void testDecadesAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(315569259747L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 3 десетилетия", t.format(0)); + } + + @Test + public void testDecadesFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 3 десетилетия", t.format(315569259747L * 3L)); + } + + @Test + public void testDecadeSingular() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 1 десетилетие", t.format(315569259747L)); + } + + @Test + public void testHoursFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 3 часа", t.format(1000 * 60 * 60 * 3)); + } + + @Test + public void testHoursAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 3), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 3 часа", t.format(0)); + } + + @Test + public void testHourSingular() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 1 час", t.format(0)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(); + assertEquals("в момента", t.format(LocalDateTime.now())); + } + + @Test + public void testMomentsAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(6000), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("току що", t.format(0)); + } + + @Test + public void testMinutesFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 12 минути", t.format(1000 * 60 * 12)); + } + + @Test + public void testMinutesAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 12), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 12 минути", t.format(0)); + } + + @Test + public void testMonthsFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 3 месеца", t.format((2629743830L * 3L))); + } + + @Test + public void testMonthsAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 3 месеца", t.format(0)); + } + + @Test + public void testMonthSingular() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 1 месец", t.format(0)); + } + + @Test + public void testWeeksFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 3 седмици", t.format(1000 * 60 * 60 * 24 * 7 * 3)); + } + + @Test + public void testWeeksAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 7 * 3), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 3 седмици", t.format(0)); + } + + @Test + public void testWeekSingular() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 7), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 1 седмица", t.format(0)); + } + + @Test + public void testYearsFromNow() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("след 3 години", t.format(2629743830L * 12L * 3L)); + } + + @Test + public void testYearsAgo() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L * 12L * 3L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 3 години", t.format(0)); + } + + @Test + public void testYearSingular() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(2629743830L * 12L), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(localDateTime); + assertEquals("преди 1 година", t.format(0)); + } + + @Test + public void testFormattingDurationListInThePast() throws Exception { + PrettyTime t = new PrettyTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38), ZoneId.systemDefault())); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0L), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("преди 3 дни 15 часа 38 минути", t.format(timeUnitQuantities)); + } + + @Test + public void testFormattingDurationListInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(0L), ZoneId.systemDefault())); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + + 1000 * 60 * 38), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("след 3 дни 15 часа 38 минути", t.format(timeUnitQuantities)); + } + + // Method tearDown() is called automatically after every test method + @After + public void tearDown() throws Exception { + Locale.setDefault(locale); + } + +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_CA_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_CA_Test.java new file mode 100644 index 0000000..c9f5df5 --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_CA_Test.java @@ -0,0 +1,233 @@ +package org.xbib.time.pretty; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PrettyTimeI18n_CA_Test { + + protected static Locale locale; + + @BeforeClass + public static void setUp() throws Exception { + locale = Locale.getDefault(); + Locale.setDefault(new Locale("ca")); + } + + @AfterClass + public static void tearDown() throws Exception { + Locale.setDefault(locale); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref, new Locale("ca")); + assertEquals("fa 1 mes", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(); + assertEquals("en un instant", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("en un instant", t.format(600)); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("dintre de 12 minuts", t.format((1000 * 60 * 12))); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("dintre de 3 hores", t.format((1000 * 60 * 60 * 3))); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("dintre de 3 dies", t.format((1000 * 60 * 60 * 24 * 3))); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("dintre de 3 setmanes", t.format((1000 * 60 * 60 * 24 * 7 * 3))); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("dintre de 3 mesos", t.format((2629743830L * 3L))); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("dintre de 3 anys", t.format((2629743830L * 12L * 3L))); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("dintre de 3 desenis", t.format((315569259747L * 3L))); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("dintre de 3 segles", t.format((3155692597470L * 3L))); + } + + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime(6000); + assertEquals("fa uns instants", t.format((0))); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 12); + assertEquals("fa 12 minuts", t.format((0))); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 3); + assertEquals("fa 3 hores", t.format((0))); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24 * 3); + assertEquals("fa 3 dies", t.format((0))); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24 * 7 * 3); + assertEquals("fa 3 setmanes", t.format((0))); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime(2629743830L * 3L); + assertEquals("fa 3 mesos", t.format((0))); + } + + @Test + public void testCustomFormat() throws Exception { + PrettyTime t = new PrettyTime(0); + TimeUnit unit = new TimeUnit() { + @Override + public long getMaxQuantity() { + return 0; + } + + @Override + public long getMillisPerUnit() { + return 5000; + } + }; + t.clearUnits(); + t.registerUnit(unit, new SimpleTimeFormat() + .setSingularName("tick").setPluralName("ticks") + .setPattern("%n %u").setRoundingTolerance(20) + .setFutureSuffix("... RUN!") + .setFuturePrefix("self destruct in: ").setPastPrefix("self destruct was: ").setPastSuffix( + " ago...")); + + assertEquals("self destruct in: 5 ticks ... RUN!", t.format((25000))); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(25000), ZoneId.systemDefault()); + t.setReference(localDateTime); + assertEquals("self destruct was: 5 ticks ago...", t.format((0))); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime(2629743830L * 12L * 3L); + assertEquals("fa 3 anys", t.format((0))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime(315569259747L * 3L); + assertEquals("fa 3 desenis", t.format((0))); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime(3155692597470L * 3L); + assertEquals("fa 3 segles", t.format((0))); + } + + @Test + public void testWithinTwoHoursRounding() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minusHours(2); + assertEquals("fa 2 hores", t.format(localDateTime)); + } + + @Test + public void testPreciseInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); + assertEquals(5, timeUnitQuantities.get(0).getQuantity()); + assertEquals(10, timeUnitQuantities.get(1).getQuantity()); + } + + @Test + public void testPreciseInThePast() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); + assertEquals(-5, timeUnitQuantities.get(0).getQuantity()); + assertTrue(-10 == timeUnitQuantities.get(1).getQuantity() || -9 == timeUnitQuantities.get(1).getQuantity()); + } + + @Test + public void testFormattingDurationListInThePast() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("fa 3 dies 15 hores 38 minuts", t.format(timeUnitQuantities)); + } + + @Test + public void testFormattingDurationListInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(0); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + + 1000 * 60 * 38), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("dintre de 3 dies 15 hores 38 minuts", t.format(timeUnitQuantities)); + } + + @Test + public void testSetLocale() throws Exception { + PrettyTime t = new PrettyTime(315569259747L * 3L); + assertEquals("fa 3 desenis", t.format((0))); + t.setLocale(Locale.GERMAN); + assertEquals("vor 3 Jahrzehnten", t.format((0))); + } + +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_CS_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_CS_Test.java new file mode 100644 index 0000000..5baf377 --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_CS_Test.java @@ -0,0 +1,227 @@ +package org.xbib.time.pretty; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.xbib.time.pretty.units.JustNow; +import org.xbib.time.pretty.units.Month; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoField; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeI18n_CS_Test { + private static Locale locale; + + @BeforeClass + public static void setUp() throws Exception { + locale = Locale.getDefault(); + Locale.setDefault(new Locale("cs")); + } + + @AfterClass + public static void tearDown() throws Exception { + Locale.setDefault(locale); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref); + assertEquals("před 1 měsícem", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(); + assertEquals("za chvíli", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("za chvíli", t.format(600)); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + for (TimeUnit u : t.getUnits()) { + if (u instanceof JustNow) { + ((JustNow) u).setMaxQuantity(1000L); + } + } + assertEquals("za 1 minutu", t.format(1000 * 60)); + assertEquals("za 3 minuty", t.format(1000 * 60 * 3)); + assertEquals("za 12 minut", t.format(1000 * 60 * 12)); + } + + @Test + public void testHoursFromNow() throws Exception { + + PrettyTime t = new PrettyTime(0); + assertEquals("za 1 hodinu", t.format(1000 * 60 * 60)); + assertEquals("za 3 hodiny", t.format(1000 * 60 * 60 * 3)); + assertEquals("za 10 hodin", t.format(1000 * 60 * 60 * 10)); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("za 1 den", t.format(1000 * 60 * 60 * 24)); + assertEquals("za 3 dny", t.format(1000 * 60 * 60 * 24 * 3)); + assertEquals("za 5 dní", t.format(1000 * 60 * 60 * 24 * 5)); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + for (TimeUnit u : t.getUnits()) { + if (u instanceof Month) { + t.removeUnit(u); + } + } + assertEquals("za 1 týden", t.format(1000 * 60 * 60 * 24 * 7L)); + assertEquals("za 3 týdny", t.format(1000 * 60 * 60 * 24 * 7 * 3L)); + assertEquals("za 5 týdnů", t.format(1000 * 60 * 60 * 24 * 7 * 5L)); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("za 1 měsíc", t.format(2629743830L)); + assertEquals("za 3 měsíce", t.format(2629743830L * 3L)); + assertEquals("za 6 měsíců", t.format(2629743830L * 6L)); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("za 1 rok", t.format(2629743830L * 12L)); + assertEquals("za 3 roky", t.format(2629743830L * 12L * 3L)); + assertEquals("za 9 let", t.format(2629743830L * 12L * 9L)); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("za 3 desetiletí", t.format(315569259747L * 3L)); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("za 3 století", t.format(3155692597470L * 3L)); + } + + /* + * Past + */ + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime(6000); + assertEquals("před chvílí", t.format(0)); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 12); + assertEquals("před 12 minutami", t.format(0)); + } + + @Test + public void testHoursAgo() throws Exception { + LocalDateTime base = LocalDateTime.now(); + PrettyTime t = new PrettyTime(base); + assertEquals("před 1 hodinou", t.format(base.minusHours(1))); + assertEquals("před 3 hodinami", t.format(base.minusHours(3))); + } + + @Test + public void testDaysAgo() throws Exception { + LocalDateTime base = LocalDateTime.now(); + PrettyTime t = new PrettyTime(base); + assertEquals("před 1 dnem", t.format(base.minusDays(1))); + assertEquals("před 3 dny", t.format(base.minusDays(3))); + } + + @Test + public void testWeeksAgo() throws Exception { + LocalDateTime base = LocalDateTime.now(); + PrettyTime t = new PrettyTime(base); + assertEquals("před 1 týdnem", t.format(base.minusWeeks(1))); + assertEquals("před 3 týdny", t.format(base.minusWeeks(3))); + } + + @Test + public void testMonthsAgo() throws Exception { + LocalDateTime base = LocalDateTime.now(); + PrettyTime t = new PrettyTime(base); + + assertEquals("před 1 měsícem", t.format(base.minusMonths(1))); + assertEquals("před 3 měsíci", t.format(base.minusMonths(3))); + } + + @Test + public void testYearsAgo() throws Exception { + LocalDateTime base = LocalDateTime.now(); + PrettyTime t = new PrettyTime(base); + for (TimeUnit u : t.getUnits()) { + if (u instanceof Month) { + t.removeUnit(u); + } + } + assertEquals("před 1 rokem", t.format(base.minusYears(1))); + assertEquals("před 3 roky", t.format(base.minusYears(3))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime(315569259747L * 3L); + assertEquals("před 3 desetiletími", t.format(0)); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime(3155692597470L * 3L); + assertEquals("před 3 stoletími", t.format(0)); + } + + @Test + public void testFormattingDurationListInThePast() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("před 3 dny 15 hodinami 38 minutami", t.format(timeUnitQuantities)); + } + + @Test + public void testFormattingDurationListInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(0); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("za 3 dny 15 hodin 38 minut", t.format(timeUnitQuantities)); + } + + /** + * Tests formatApproximateDuration and by proxy, formatDuration. + * + * @throws Exception + */ + @Test + public void testFormatApproximateDuration() throws Exception { + LocalDateTime localDateTime = LocalDateTime.now(); + LocalDateTime tenMinAgo = localDateTime.minus(10, ChronoField.MINUTE_OF_DAY.getBaseUnit()); + PrettyTime t = new PrettyTime(); + String result = t.formatApproximateDuration(tenMinAgo); + Assert.assertEquals("10 minutami", result); + } + +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_DA_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_DA_Test.java new file mode 100644 index 0000000..0febb08 --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_DA_Test.java @@ -0,0 +1,155 @@ +package org.xbib.time.pretty; + +import org.junit.Before; +import org.junit.Test; + +import java.time.LocalDateTime; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeI18n_DA_Test { + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = new Locale("da"); + } + + @Test + public void testPrettyTime() { + PrettyTime p = new PrettyTime(locale); + assertEquals("straks", p.format(LocalDateTime.now())); + } + + @Test + public void testPrettyTimeCenturies() { + PrettyTime p = new PrettyTime(3155692597470L * 3L, locale); + assertEquals("3 århundreder siden", p.format(0)); + + p = new PrettyTime(0, locale); + assertEquals("3 århundreder fra nu", p.format(3155692597470L * 3L)); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref, locale); + assertEquals("1 måned siden", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(locale); + assertEquals("straks", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("straks", t.format(600)); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("om 12 minutter", t.format(1000 * 60 * 12)); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("om 3 timer", t.format(1000 * 60 * 60 * 3)); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("om 3 dage", t.format(1000 * 60 * 60 * 24 * 3)); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("om 3 uger", t.format(1000 * 60 * 60 * 24 * 7 * 3)); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("om 3 måneder", t.format(2629743830L * 3L)); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("om 3 år", t.format(2629743830L * 12L * 3L)); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("3 årtier fra nu", t.format(315569259747L * 3L)); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("3 århundreder fra nu", t.format(3155692597470L * 3L)); + } + + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime(6000, locale); + assertEquals("et øjeblik siden", t.format(0)); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 12), locale); + assertEquals("12 minutter siden", t.format(0)); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3), locale); + assertEquals("3 timer siden", t.format(0)); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3), locale); + assertEquals("3 dage siden", t.format(0)); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3), locale); + assertEquals("3 uger siden", t.format(0)); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 3L), locale); + assertEquals("3 måneder siden", t.format(0)); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 12L * 3L), locale); + assertEquals("3 år siden", t.format(0)); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 3L), locale); + assertEquals("3 årtier siden", t.format(0)); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime((3155692597470L * 3L), locale); + assertEquals("3 århundreder siden", t.format(0)); + } +} \ No newline at end of file diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_ET_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_ET_Test.java new file mode 100644 index 0000000..546f3dd --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_ET_Test.java @@ -0,0 +1,336 @@ +package org.xbib.time.pretty; + +import org.junit.Before; +import org.junit.Test; +import org.xbib.time.pretty.units.JustNow; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeI18n_ET_Test { + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = new Locale("et"); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("hetke pärast", t.format(6000)); + } + + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime((6000), locale); + assertEquals("hetk tagasi", t.format(0)); + } + + @Test + public void testMilliSecondsFromNow() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow(0, locale); + assertEquals("13 millisekundi pärast", t.format(13)); + } + + @Test + public void testMilliSecondsAgo() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((13), locale); + assertEquals("13 millisekundit tagasi", t.format(0)); + } + + @Test + public void testMilliSecondFromNow() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow(0, locale); + assertEquals("millisekundi pärast", t.format(1)); + } + + @Test + public void testMilliSecondAgo() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow(1, locale); + assertEquals("millisekund tagasi", t.format(0)); + } + + @Test + public void testSecondsFromNow() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow(0, locale); + assertEquals("13 sekundi pärast", t.format(1000 * 13)); + } + + @Test + public void testSecondsAgo() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow(1000 * 13, locale); + assertEquals("13 sekundit tagasi", t.format(0)); + } + + @Test + public void testSecondFromNow() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow(0, locale); + assertEquals("sekundi pärast", t.format(1000)); + } + + @Test + public void testSecondAgo() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow(1000, locale); + assertEquals("sekund tagasi", t.format(0)); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("13 minuti pärast", t.format(1000 * 60 * 13)); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 13), locale); + assertEquals("13 minutit tagasi", t.format(0)); + } + + @Test + public void testMinuteFromNow() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow(0, locale); + assertEquals("minuti pärast", t.format(1000 * 60)); + } + + @Test + public void testMinuteAgo() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow(1000 * 60, locale); + assertEquals("minut tagasi", t.format(0)); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("3 tunni pärast", t.format(1000 * 60 * 60 * 3)); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3), locale); + assertEquals("3 tundi tagasi", t.format(0)); + } + + @Test + public void testHoursFromNowSingle() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("tunni pärast", t.format(1000 * 60 * 60)); + } + + @Test + public void testHoursAgoSingle() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60), locale); + assertEquals("tund tagasi", t.format(0)); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 päeva pärast", t.format(1000 * 60 * 60 * 24 * 3)); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3), locale); + assertEquals("3 päeva tagasi", t.format(0)); + } + + @Test + public void testDaysFromNowSingle() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("homme", t.format(1000 * 60 * 60 * 24)); + } + + @Test + public void testDaysAgoSingle() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24, locale); + assertEquals("eile", t.format(0)); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("3 nädala pärast", t.format(1000 * 60 * 60 * 24 * 7 * 3)); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24 * 7 * 3, locale); + assertEquals("3 nädalat tagasi", t.format(0)); + } + + @Test + public void testWeeksFromNowSingle() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("nädala pärast", t.format(1000 * 60 * 60 * 24 * 7)); + } + + @Test + public void testWeeksAgoSingle() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24 * 7, locale); + assertEquals("nädal tagasi", t.format(0)); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("3 kuu pärast", t.format(1000L * 60 * 60 * 24 * 30 * 3)); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime(1000L * 60 * 60 * 24 * 30 * 3, locale); + assertEquals("3 kuud tagasi", t.format(0)); + } + + @Test + public void testMonthFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("kuu pärast", t.format(1000L * 60 * 60 * 24 * 30)); + } + + @Test + public void testMonthAgo() throws Exception { + PrettyTime t = new PrettyTime(1000L * 60 * 60 * 24 * 30, locale); + assertEquals("kuu tagasi", t.format(0)); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("3 aasta pärast", t.format(1000L * 60 * 60 * 24 * 365 * 3)); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 3), locale); + assertEquals("3 aastat tagasi", t.format(0)); + } + + @Test + public void testYearFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("aasta pärast", t.format(1000L * 60 * 60 * 24 * 366)); + } + + @Test + public void testYearAgo() throws Exception { + PrettyTime t = new PrettyTime(1000L * 60 * 60 * 24 * 366, locale); + assertEquals("aasta tagasi", t.format(0)); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("3 aastakümne pärast", t.format(1000L * 60 * 60 * 24 * 365 * 10 * 3)); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime(1000L * 60 * 60 * 24 * 365 * 10 * 3, locale); + assertEquals("3 aastakümmet tagasi", t.format(0)); + } + + @Test + public void testDecadeFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("aastakümne pärast", t.format(1000L * 60 * 60 * 24 * 365 * 11)); + } + + @Test + public void testDecadeAgo() throws Exception { + PrettyTime t = new PrettyTime(1000L * 60 * 60 * 24 * 365 * 11, locale); + assertEquals("aastakümme tagasi", t.format(0)); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("3 sajandi pärast", t.format(1000L * 60 * 60 * 24 * 365 * 100 * 3)); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 100 * 3), locale); + assertEquals("3 sajandit tagasi", t.format(0)); + } + + @Test + public void testCenturyFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("sajandi pärast", t.format(1000L * 60 * 60 * 24 * 365 * 101)); + } + + @Test + public void testCenturyAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 101), locale); + assertEquals("sajand tagasi", t.format(0)); + } + + @Test + public void testMillenniaFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 aastatuhande pärast", t.format(1000L * 60 * 60 * 24 * 365 * 1000 * 3)); + } + + @Test + public void testMillenniaAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 1000 * 3), locale); + assertEquals("3 aastatuhandet tagasi", t.format(0)); + } + + @Test + public void testMillenniumFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("aastatuhande pärast", t.format(1000L * 60 * 60 * 24 * 365 * 1001)); + } + + @Test + public void testMillenniumAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 1001), locale); + assertEquals("aastatuhat tagasi", t.format(0)); + } + + @Test + public void testFormattingDurationListInThePast() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38), locale); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 päeva 15 tundi 38 minutit tagasi", t.format(timeUnitQuantities)); + } + + @Test + public void testFormattingDurationListInTheFuture() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 päeva 15 tunni 38 minuti pärast", t.format(timeUnitQuantities)); + } + + private PrettyTime newPrettyTimeWOJustNow(long ref, Locale locale) { + PrettyTime t = new PrettyTime(ref, locale); + List units = t.getUnits(); + List formats = new ArrayList<>(); + for (TimeUnit timeUnit : units) { + if (!(timeUnit instanceof JustNow)) { + formats.add(t.getFormat(timeUnit)); + } + } + int index = 0; + t.clearUnits(); + for (TimeUnit timeUnit : units) { + if (!(timeUnit instanceof JustNow)) { + t.registerUnit(timeUnit, formats.get(index)); + index++; + } + } + return t; + } + +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FA_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FA_Test.java new file mode 100644 index 0000000..dc96683 --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FA_Test.java @@ -0,0 +1,239 @@ +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PrettyTimeI18n_FA_Test { + + // Stores current locale so that it can be restored + private Locale locale; + + // Method setUp() is called automatically before every test method + @Before + public void setUp() throws Exception { + locale = new Locale("fa"); + Locale.setDefault(locale); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2012, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2012, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref); + assertEquals("1 ماه پیش", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(); + assertEquals("چند لحظه دیگر", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("چند لحظه دیگر", t.format((600))); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("12 دقیقه دیگر", t.format((1000 * 60 * 12))); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("3 ساعت دیگر", t.format((1000 * 60 * 60 * 3))); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("3 روز دیگر", t.format((1000 * 60 * 60 * 24 * 3))); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("3 هفته دیگر", t.format((1000 * 60 * 60 * 24 * 7 * 3))); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("3 ماه دیگر", t.format((2629743830L * 3L))); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("3 سال دیگر", t.format((2629743830L * 12L * 3L))); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("3 دهه دیگر", t.format((315569259747L * 3L))); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("3 قرن دیگر", t.format((3155692597470L * 3L))); + } + + /* + * Past + */ + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime(6000); + assertEquals("چند لحظه پیش", t.format((0))); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 12); + assertEquals("12 دقیقه پیش", t.format((0))); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 3); + assertEquals("3 ساعت پیش", t.format((0))); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24 * 3); + assertEquals("3 روز پیش", t.format((0))); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24 * 7 * 3); + assertEquals("3 هفته پیش", t.format((0))); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime(2629743830L * 3L); + assertEquals("3 ماه پیش", t.format((0))); + } + + @Test + public void testCustomFormat() throws Exception { + PrettyTime t = new PrettyTime(0); + TimeUnit unit = new TimeUnit() { + @Override + public long getMaxQuantity() { + return 0; + } + + @Override + public long getMillisPerUnit() { + return 5000; + } + }; + t.clearUnits(); + t.registerUnit(unit, new SimpleTimeFormat() + .setSingularName("tick").setPluralName("ticks") + .setPattern("%n %u").setRoundingTolerance(20) + .setFutureSuffix("... RUN!") + .setFuturePrefix("self destruct in: ").setPastPrefix("self destruct was: ").setPastSuffix( + " ago...")); + + assertEquals("self destruct in: 5 ticks ... RUN!", t.format((25000))); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(25000), ZoneId.systemDefault()); + t.setReference(localDateTime); + assertEquals("self destruct was: 5 ticks ago...", t.format((0))); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime(2629743830L * 12L * 3L); + assertEquals("3 سال پیش", t.format((0))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime(315569259747L * 3L); + assertEquals("3 دهه پیش", t.format((0))); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime(3155692597470L * 3L); + assertEquals("3 قرن پیش", t.format((0))); + } + + @Test + public void testWithinTwoHoursRounding() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(6543); + assertEquals("2 ساعت پیش", t.format(localDateTime)); + } + + @Test + public void testPreciseInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); // might be more because of milliseconds between date capturing and result + // calculation + assertEquals(5, timeUnitQuantities.get(0).getQuantity()); + assertEquals(10, timeUnitQuantities.get(1).getQuantity()); + } + + @Test + public void testPreciseInThePast() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); // might be more because of milliseconds between date capturing and result + // calculation + assertEquals(-5, timeUnitQuantities.get(0).getQuantity()); + assertTrue(-10 == timeUnitQuantities.get(1).getQuantity() || -9 == timeUnitQuantities.get(1).getQuantity()); + } + + @Test + public void testFormattingDurationListInThePast() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 روز 15 ساعت 38 دقیقه پیش", t.format(timeUnitQuantities)); + } + + @Test + public void testFormattingDurationListInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(0); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + + 1000 * 60 * 38), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 روز 15 ساعت 38 دقیقه دیگر", t.format(timeUnitQuantities)); + } + + @Test + public void testSetLocale() throws Exception { + PrettyTime t = new PrettyTime(315569259747L * 3L); + assertEquals("3 دهه پیش", t.format((0))); + } + + // Method tearDown() is called automatically after every test method + @After + public void tearDown() throws Exception { + Locale.setDefault(locale); + } + +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FI_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FI_Test.java new file mode 100644 index 0000000..7fd0d44 --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FI_Test.java @@ -0,0 +1,336 @@ +package org.xbib.time.pretty; + +import org.junit.Before; +import org.junit.Test; +import org.xbib.time.pretty.units.JustNow; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeI18n_FI_Test { + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = new Locale("fi"); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("hetken päästä", t.format((6000))); + } + + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime((6000), locale); + assertEquals("hetki sitten", t.format((0))); + } + + @Test + public void testMilliSecondsFromNow() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((0), locale); + assertEquals("13 millisekunnin päästä", t.format((13))); + } + + @Test + public void testMilliSecondsAgo() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((13), locale); + assertEquals("13 millisekuntia sitten", t.format((0))); + } + + @Test + public void testMilliSecondFromNow() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((0), locale); + assertEquals("millisekunnin päästä", t.format((1))); + } + + @Test + public void testMilliSecondAgo() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((1), locale); + assertEquals("millisekunti sitten", t.format((0))); + } + + @Test + public void testSecondsFromNow() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((0), locale); + assertEquals("13 sekunnin päästä", t.format((1000 * 13))); + } + + @Test + public void testSecondsAgo() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((1000 * 13), locale); + assertEquals("13 sekuntia sitten", t.format((0))); + } + + @Test + public void testSecondFromNow() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((0), locale); + assertEquals("sekunnin päästä", t.format((1000))); + } + + @Test + public void testSecondAgo() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((1000), locale); + assertEquals("sekunti sitten", t.format((0))); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("13 minuutin päästä", t.format((1000 * 60 * 13))); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 13), locale); + assertEquals("13 minuuttia sitten", t.format((0))); + } + + @Test + public void testMinuteFromNow() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((0), locale); + assertEquals("minuutin päästä", t.format((1000 * 60))); + } + + @Test + public void testMinuteAgo() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((1000 * 60), locale); + assertEquals("minuutti sitten", t.format((0))); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("3 tunnin päästä", t.format((1000 * 60 * 60 * 3))); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3), locale); + assertEquals("3 tuntia sitten", t.format((0))); + } + + @Test + public void testHoursFromNowSingle() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("tunnin päästä", t.format((1000 * 60 * 60))); + } + + @Test + public void testHoursAgoSingle() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60), locale); + assertEquals("tunti sitten", t.format((0))); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 päivän päästä", t.format((1000 * 60 * 60 * 24 * 3))); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24 * 3, locale); + assertEquals("3 päivää sitten", t.format((0))); + } + + @Test + public void testDaysFromNowSingle() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("huomenna", t.format((1000 * 60 * 60 * 24))); + } + + @Test + public void testDaysAgoSingle() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 24, locale); + assertEquals("eilen", t.format((0))); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 viikon päästä", t.format((1000 * 60 * 60 * 24 * 7 * 3))); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3), locale); + assertEquals("3 viikkoa sitten", t.format((0))); + } + + @Test + public void testWeeksFromNowSingle() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("viikon päästä", t.format((1000 * 60 * 60 * 24 * 7))); + } + + @Test + public void testWeeksAgoSingle() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7), locale); + assertEquals("viikko sitten", t.format((0))); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("3 kuukauden päästä", t.format((1000L * 60 * 60 * 24 * 30 * 3))); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime(1000L * 60 * 60 * 24 * 30 * 3, locale); + assertEquals("3 kuukautta sitten", t.format((0))); + } + + @Test + public void testMonthFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("kuukauden päästä", t.format((1000L * 60 * 60 * 24 * 30))); + } + + @Test + public void testMonthAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 30), locale); + assertEquals("kuukausi sitten", t.format((0))); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 vuoden päästä", t.format((1000L * 60 * 60 * 24 * 365 * 3))); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 3), locale); + assertEquals("3 vuotta sitten", t.format((0))); + } + + @Test + public void testYearFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("vuoden päästä", t.format((1000L * 60 * 60 * 24 * 366))); + } + + @Test + public void testYearAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 366), locale); + assertEquals("vuosi sitten", t.format((0))); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 vuosikymmenen päästä", t.format((1000L * 60 * 60 * 24 * 365 * 10 * 3))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 10 * 3), locale); + assertEquals("3 vuosikymmentä sitten", t.format((0))); + } + + @Test + public void testDecadeFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("vuosikymmenen päästä", t.format((1000L * 60 * 60 * 24 * 365 * 11))); + } + + @Test + public void testDecadeAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 11), locale); + assertEquals("vuosikymmen sitten", t.format((0))); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 vuosisadan päästä", t.format((1000L * 60 * 60 * 24 * 365 * 100 * 3))); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 100 * 3), locale); + assertEquals("3 vuosisataa sitten", t.format((0))); + } + + @Test + public void testCenturyFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("vuosisadan päästä", t.format((1000L * 60 * 60 * 24 * 365 * 101))); + } + + @Test + public void testCenturyAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 101), locale); + assertEquals("vuosisata sitten", t.format((0))); + } + + @Test + public void testMillenniaFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 vuosituhannen päästä", t.format((1000L * 60 * 60 * 24 * 365 * 1000 * 3))); + } + + @Test + public void testMillenniaAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 1000 * 3), locale); + assertEquals("3 vuosituhatta sitten", t.format((0))); + } + + @Test + public void testMillenniumFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("vuosituhannen päästä", t.format((1000L * 60 * 60 * 24 * 365 * 1001))); + } + + @Test + public void testMillenniumAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 1001), locale); + assertEquals("vuosituhat sitten", t.format((0))); + } + + @Test + public void testFormattingDurationListInThePast() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38), locale); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 päivää 15 tuntia 38 minuuttia sitten", t.format(timeUnitQuantities)); + } + + @Test + public void testFormattingDurationListInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + + 1000 * 60 * 38), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 päivän 15 tunnin 38 minuutin päästä", t.format(timeUnitQuantities)); + } + + private PrettyTime newPrettyTimeWOJustNow(long ref, Locale locale) { + PrettyTime t = new PrettyTime(ref, locale); + List units = t.getUnits(); + List formats = new ArrayList<>(); + for (TimeUnit timeUnit : units) { + if (!(timeUnit instanceof JustNow)) { + formats.add(t.getFormat(timeUnit)); + } + } + int index = 0; + t.clearUnits(); + for (TimeUnit timeUnit : units) { + if (!(timeUnit instanceof JustNow)) { + t.registerUnit(timeUnit, formats.get(index)); + index++; + } + } + return t; + } +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FR_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FR_Test.java new file mode 100644 index 0000000..73a5927 --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FR_Test.java @@ -0,0 +1,67 @@ +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * All the tests for PrettyTime. + */ +public class PrettyTimeI18n_FR_Test { + + // Stores current locale so that it can be restored + private Locale locale; + + // Method setUp() is called automatically before every test method + @Before + public void setUp() throws Exception { + locale = Locale.getDefault(); + } + + @Test + public void testPrettyTimeFRENCH() { + // The FRENCH resource bundle should be used + PrettyTime p = new PrettyTime(Locale.FRENCH); + assertEquals("à l'instant", p.format(LocalDateTime.now())); + } + + @Test + public void testPrettyTimeFRENCHCenturies() { + PrettyTime p = new PrettyTime((3155692597470L * 3L), Locale.FRENCH); + assertEquals(p.format(0), "il y a 3 siècles"); + } + + @Test + public void testPrettyTimeViaDefaultLocaleFRENCH() { + // The FRENCH resource bundle should be used + Locale.setDefault(Locale.FRENCH); + PrettyTime p = new PrettyTime(); + assertEquals(p.format(LocalDateTime.now()), "à l'instant"); + } + + @Test + public void testPrettyTimeFRENCHLocale() { + long t = 1L; + PrettyTime p = new PrettyTime((0), Locale.FRENCH); + while (1000L * 60L * 60L * 24L * 365L * 1000000L > t) { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + assertTrue(p.format(localDateTime).startsWith("dans") || p.format(localDateTime).startsWith("à l'instant")); + t *= 2L; + } + } + + // Method tearDown() is called automatically after every test method + @After + public void tearDown() throws Exception { + Locale.setDefault(locale); + } + +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_IT_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_IT_Test.java new file mode 100644 index 0000000..24df2ff --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_IT_Test.java @@ -0,0 +1,336 @@ +package org.xbib.time.pretty; + +import org.junit.Before; +import org.junit.Test; +import org.xbib.time.pretty.units.JustNow; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeI18n_IT_Test { + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = new Locale("it"); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra poco", t.format((6000))); + } + + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime((6000), locale); + assertEquals("poco fa", t.format((0))); + } + + @Test + public void testMilliSecondsFromNow() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((0), locale); + assertEquals("fra 13 millisecondi", t.format((13))); + } + + @Test + public void testMilliSecondsAgo() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((13), locale); + assertEquals("13 millisecondi fa", t.format((0))); + } + + @Test + public void testMilliSecondFromNow() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((0), locale); + assertEquals("fra 1 millisecondo", t.format((1))); + } + + @Test + public void testMilliSecondAgo() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((1), locale); + assertEquals("1 millisecondo fa", t.format((0))); + } + + @Test + public void testSecondsFromNow() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((0), locale); + assertEquals("fra 13 secondi", t.format((1000 * 13))); + } + + @Test + public void testSecondsAgo() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((1000 * 13), locale); + assertEquals("13 secondi fa", t.format((0))); + } + + @Test + public void testSecondFromNow() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((0), locale); + assertEquals("fra 1 secondo", t.format(1000)); + } + + @Test + public void testSecondAgo() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((1000), locale); + assertEquals("1 secondo fa", t.format((0))); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 13 minuti", t.format((1000 * 60 * 13))); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 13), locale); + assertEquals("13 minuti fa", t.format((0))); + } + + @Test + public void testMinuteFromNow() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((0), locale); + assertEquals("fra 1 minuto", t.format((1000 * 60))); + } + + @Test + public void testMinuteAgo() throws Exception { + PrettyTime t = newPrettyTimeWOJustNow((1000 * 60), locale); + assertEquals("1 minuto fa", t.format((0))); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 3 ore", t.format((1000 * 60 * 60 * 3))); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3), locale); + assertEquals("3 ore fa", t.format((0))); + } + + @Test + public void testHoursFromNowSingle() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 1 ora", t.format((1000 * 60 * 60))); + } + + @Test + public void testHoursAgoSingle() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60), locale); + assertEquals("1 ora fa", t.format((0))); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 3 giorni", t.format((1000 * 60 * 60 * 24 * 3))); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3), locale); + assertEquals("3 giorni fa", t.format((0))); + } + + @Test + public void testDaysFromNowSingle() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 1 giorno", t.format((1000 * 60 * 60 * 24))); + } + + @Test + public void testDaysAgoSingle() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24), locale); + assertEquals("1 giorno fa", t.format((0))); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 3 settimane", t.format((1000 * 60 * 60 * 24 * 7 * 3))); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3), locale); + assertEquals("3 settimane fa", t.format((0))); + } + + @Test + public void testWeeksFromNowSingle() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 1 settimana", t.format((1000 * 60 * 60 * 24 * 7))); + } + + @Test + public void testWeeksAgoSingle() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7), locale); + assertEquals("1 settimana fa", t.format((0))); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 3 mesi", t.format((1000L * 60 * 60 * 24 * 30 * 3))); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 30 * 3), locale); + assertEquals("3 mesi fa", t.format((0))); + } + + @Test + public void testMonthFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 1 mese", t.format((1000L * 60 * 60 * 24 * 30))); + } + + @Test + public void testMonthAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 30), locale); + assertEquals("1 mese fa", t.format((0))); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 3 anni", t.format((1000L * 60 * 60 * 24 * 365 * 3))); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 3), locale); + assertEquals("3 anni fa", t.format((0))); + } + + @Test + public void testYearFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 1 anno", t.format((1000L * 60 * 60 * 24 * 366))); + } + + @Test + public void testYearAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 366), locale); + assertEquals("1 anno fa", t.format((0))); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 3 decenni", t.format((1000L * 60 * 60 * 24 * 365 * 10 * 3))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 10 * 3), locale); + assertEquals("3 decenni fa", t.format((0))); + } + + @Test + public void testDecadeFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 1 decennio", t.format((1000L * 60 * 60 * 24 * 365 * 11))); + } + + @Test + public void testDecadeAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 11), locale); + assertEquals("1 decennio fa", t.format((0))); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 3 secoli", t.format((1000L * 60 * 60 * 24 * 365 * 100 * 3))); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 100 * 3), locale); + assertEquals("3 secoli fa", t.format((0))); + } + + @Test + public void testCenturyFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 1 secolo", t.format((1000L * 60 * 60 * 24 * 365 * 101))); + } + + @Test + public void testCenturyAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 101), locale); + assertEquals("1 secolo fa", t.format((0))); + } + + @Test + public void testMillenniaFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 3 millenni", t.format((1000L * 60 * 60 * 24 * 365 * 1000 * 3))); + } + + @Test + public void testMillenniaAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 1000 * 3), locale); + assertEquals("3 millenni fa", t.format((0))); + } + + @Test + public void testMillenniumFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("fra 1 millennio", t.format((1000L * 60 * 60 * 24 * 365 * 1001))); + } + + @Test + public void testMillenniumAgo() throws Exception { + PrettyTime t = new PrettyTime((1000L * 60 * 60 * 24 * 365 * 1001), locale); + assertEquals("1 millennio fa", t.format((0))); + } + + @Test + public void testFormattingDurationListInThePast() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38), locale); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 giorni 15 ore 38 minuti fa", t.format(timeUnitQuantities)); + } + + @Test + public void testFormattingDurationListInTheFuture() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + + 1000 * 60 * 38), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("fra 3 giorni 15 ore 38 minuti", t.format(timeUnitQuantities)); + } + + private PrettyTime newPrettyTimeWOJustNow(long ref, Locale locale) { + PrettyTime t = new PrettyTime(ref, locale); + List units = t.getUnits(); + List formats = new ArrayList<>(); + for (TimeUnit timeUnit : units) { + if (!(timeUnit instanceof JustNow)) { + formats.add(t.getFormat(timeUnit)); + } + } + int index = 0; + t.clearUnits(); + for (TimeUnit timeUnit : units) { + if (!(timeUnit instanceof JustNow)) { + t.registerUnit(timeUnit, formats.get(index)); + index++; + } + } + return t; + } +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_KO_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_KO_Test.java new file mode 100644 index 0000000..8e1607e --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_KO_Test.java @@ -0,0 +1,236 @@ +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PrettyTimeI18n_KO_Test { + + private Locale locale; + + // Method setUp() is called automatically before every test method + @Before + public void setUp() throws Exception { + locale = Locale.getDefault(); + Locale.setDefault(Locale.KOREA); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref, Locale.KOREA); + assertEquals("1개월 전", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(); + assertEquals("지금", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("지금", t.format((600))); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("12분 후", t.format((1000 * 60 * 12))); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3시간 후", t.format((1000 * 60 * 60 * 3))); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3일 후", t.format((1000 * 60 * 60 * 24 * 3))); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3주 후", t.format((1000 * 60 * 60 * 24 * 7 * 3))); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3개월 후", t.format((2629743830L * 3L))); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3년 후", t.format((2629743830L * 12L * 3L))); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("30년 후", t.format((315569259747L * 3L))); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3세기 후", t.format((3155692597470L * 3L))); + } + + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime((6000)); + assertEquals("방금", t.format((0))); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 12)); + assertEquals("12분 전", t.format((0))); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3)); + assertEquals("3시간 전", t.format((0))); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3)); + assertEquals("3일 전", t.format((0))); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3)); + assertEquals("3주 전", t.format((0))); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 3L)); + assertEquals("3개월 전", t.format((0))); + } + + @Test + public void testCustomFormat() throws Exception { + PrettyTime t = new PrettyTime((0)); + TimeUnit unit = new TimeUnit() { + @Override + public long getMaxQuantity() { + return 0; + } + + @Override + public long getMillisPerUnit() { + return 5000; + } + }; + t.clearUnits(); + t.registerUnit(unit, new SimpleTimeFormat() + .setSingularName("tick").setPluralName("ticks") + .setPattern("%n %u").setRoundingTolerance(20) + .setFutureSuffix("... RUN!") + .setFuturePrefix("self destruct in: ").setPastPrefix("self destruct was: ").setPastSuffix( + " ago...")); + + assertEquals("self destruct in: 5 ticks ... RUN!", t.format((25000))); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(25000), ZoneId.systemDefault()); + t.setReference(localDateTime); + assertEquals("self destruct was: 5 ticks ago...", t.format((0))); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 12L * 3L)); + assertEquals("3년 전", t.format((0))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 3L)); + assertEquals("30년 전", t.format((0))); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime((3155692597470L * 3L)); + assertEquals("3세기 전", t.format((0))); + } + + @Test + public void testWithinTwoHoursRounding() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(6544); + assertEquals("2시간 전", t.format(localDateTime)); + } + + @Test + public void testPreciseInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); // might be more because of milliseconds between date capturing and result + // calculation + assertEquals(5, timeUnitQuantities.get(0).getQuantity()); + assertEquals(10, timeUnitQuantities.get(1).getQuantity()); + } + + @Test + public void testPreciseInThePast() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); + assertEquals(-5, timeUnitQuantities.get(0).getQuantity()); + assertTrue(-10 == timeUnitQuantities.get(1).getQuantity() || -9 == timeUnitQuantities.get(1).getQuantity()); + } + + @Test + public void testFormattingDurationListInThePast() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3일 15시간 38분 전", t.format(timeUnitQuantities)); + } + + @Test + public void testFormattingDurationListInTheFuture() throws Exception { + PrettyTime t = new PrettyTime((0)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + + 1000 * 60 * 38), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3일 15시간 38분 후", t.format(timeUnitQuantities)); + } + + @Test + public void testSetLocale() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 3L)); + assertEquals("30년 전", t.format((0))); + t.setLocale(Locale.GERMAN); + assertEquals("vor 3 Jahrzehnten", t.format((0))); + } + + // Method tearDown() is called automatically after every test method + @After + public void tearDown() throws Exception { + Locale.setDefault(locale); + } + +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_NL_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_NL_Test.java new file mode 100644 index 0000000..86f19bc --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_NL_Test.java @@ -0,0 +1,162 @@ +package org.xbib.time.pretty; + +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeI18n_NL_Test { + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = new Locale("nl"); + } + + @Test + public void testPrettyTime() { + PrettyTime p = new PrettyTime(locale); + assertEquals("op dit moment", p.format(LocalDateTime.now())); + } + + @Test + public void testPrettyTimeCenturies() { + PrettyTime p = new PrettyTime((3155692597470L * 3L), locale); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + assertEquals("3 eeuwen geleden", p.format(localDateTime)); + + p = new PrettyTime((0), locale); + localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(3155692597470L * 3L), ZoneId.systemDefault()); + assertEquals("over 3 eeuwen", p.format(localDateTime)); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref, locale); + assertEquals("1 maand geleden", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(locale); + assertEquals("op dit moment", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("op dit moment", t.format(600)); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("over 12 minuten", t.format((1000 * 60 * 12))); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("over 3 uur", t.format((1000 * 60 * 60 * 3))); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("over 3 dagen", t.format((1000 * 60 * 60 * 24 * 3))); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("over 3 weken", t.format((1000 * 60 * 60 * 24 * 7 * 3))); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("over 3 maanden", t.format((2629743830L * 3L))); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("over 3 jaar", t.format((2629743830L * 12L * 3L))); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("over 3 decennia", t.format((315569259747L * 3L))); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("over 3 eeuwen", t.format((3155692597470L * 3L))); + } + + /* + * Past + */ + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime((6000), locale); + assertEquals("een ogenblik geleden", t.format((0))); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 12), locale); + assertEquals("12 minuten geleden", t.format((0))); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3), locale); + assertEquals("3 uur geleden", t.format((0))); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3), locale); + assertEquals("3 dagen geleden", t.format((0))); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3), locale); + assertEquals("3 weken geleden", t.format((0))); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 3L), locale); + assertEquals("3 maanden geleden", t.format((0))); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 12L * 3L), locale); + assertEquals("3 jaar geleden", t.format((0))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 3L), locale); + assertEquals("3 decennia geleden", t.format((0))); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime((3155692597470L * 3L), locale); + assertEquals("3 eeuwen geleden", t.format((0))); + } +} \ No newline at end of file diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_NO_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_NO_Test.java new file mode 100644 index 0000000..364e9fe --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_NO_Test.java @@ -0,0 +1,163 @@ +package org.xbib.time.pretty; + +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeI18n_NO_Test { + + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = new Locale("no"); + } + + @Test + public void testPrettyTime() { + PrettyTime p = new PrettyTime(locale); + assertEquals("straks", p.format(LocalDateTime.now())); + } + + @Test + public void testPrettyTimeCenturies() { + PrettyTime p = new PrettyTime((3155692597470L * 3L), locale); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + assertEquals("3 århundre siden", p.format(localDateTime)); + + p = new PrettyTime(0, locale); + localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(3155692597470L * 3L), ZoneId.systemDefault()); + assertEquals("3 århundre fra nå", p.format(localDateTime)); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref, locale); + assertEquals("1 måned siden", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(locale); + assertEquals("straks", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("straks", t.format((600))); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 12 minutter", t.format((1000 * 60 * 12))); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 timer", t.format((1000 * 60 * 60 * 3))); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 dager", t.format((1000 * 60 * 60 * 24 * 3))); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 uker", t.format((1000 * 60 * 60 * 24 * 7 * 3))); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 måneder", t.format((2629743830L * 3L))); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 år", t.format((2629743830L * 12L * 3L))); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 tiår fra nå", t.format((315569259747L * 3L))); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 århundre fra nå", t.format((3155692597470L * 3L))); + } + + /* + * Past + */ + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime((6000), locale); + assertEquals("et øyeblikk siden", t.format((0))); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 12), locale); + assertEquals("12 minutter siden", t.format((0))); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3), locale); + assertEquals("3 timer siden", t.format((0))); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3), locale); + assertEquals("3 dager siden", t.format((0))); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3), locale); + assertEquals("3 uker siden", t.format((0))); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 3L), locale); + assertEquals("3 måneder siden", t.format((0))); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 12L * 3L), locale); + assertEquals("3 år siden", t.format((0))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 3L), locale); + assertEquals("3 tiår siden", t.format((0))); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime((3155692597470L * 3L), locale); + assertEquals("3 århundre siden", t.format((0))); + } +} \ No newline at end of file diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_RU_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_RU_Test.java new file mode 100644 index 0000000..5a912ac --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_RU_Test.java @@ -0,0 +1,171 @@ +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeI18n_RU_Test { + + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = new Locale("ru"); + Locale.setDefault(locale); + } + + @Test + public void testPrettyTime() { + PrettyTime p = new PrettyTime(locale); + assertEquals("сейчас", p.format(LocalDateTime.now())); + } + + @Test + public void testPrettyTimeCenturies() { + PrettyTime p = new PrettyTime((3155692597470L * 3L), locale); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + assertEquals("3 века назад", p.format(localDateTime)); + + p = new PrettyTime((0), locale); + localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(3155692597470L * 3L), ZoneId.systemDefault()); + assertEquals("через 3 века", p.format(localDateTime)); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref, locale); + assertEquals("1 месяц назад", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(locale); + assertEquals("сейчас", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("сейчас", t.format((600))); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("через 12 минут", t.format((1000 * 60 * 12))); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("через 3 часа", t.format((1000 * 60 * 60 * 3))); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("через 3 дня", t.format((1000 * 60 * 60 * 24 * 3))); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("через 3 недели", t.format((1000 * 60 * 60 * 24 * 7 * 3))); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("через 3 месяца", t.format((2629743830L * 3L))); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("через 3 года", t.format((2629743830L * 12L * 3L))); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("через 3 десятилетия", t.format((315569259747L * 3L))); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("через 3 века", t.format((3155692597470L * 3L))); + } + + /* + * Past + */ + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime((6000), locale); + assertEquals("только что", t.format((0))); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 12), locale); + assertEquals("12 минут назад", t.format((0))); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3), locale); + assertEquals("3 часа назад", t.format((0))); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3), locale); + assertEquals("3 дня назад", t.format((0))); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3), locale); + assertEquals("3 недели назад", t.format((0))); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 3L), locale); + assertEquals("3 месяца назад", t.format((0))); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 12L * 3L), locale); + assertEquals("3 года назад", t.format((0))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 3L), locale); + assertEquals("3 десятилетия назад", t.format((0))); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime((3155692597470L * 3L), locale); + assertEquals("3 века назад", t.format((0))); + } + + + @After + public void tearDown() throws Exception { + Locale.setDefault(locale); + } +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_SV_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_SV_Test.java new file mode 100644 index 0000000..a833e4d --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_SV_Test.java @@ -0,0 +1,164 @@ +package org.xbib.time.pretty; + +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeI18n_SV_Test { + + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = new Locale("sv"); + } + + @Test + public void testPrettyTime() { + PrettyTime p = new PrettyTime(locale); + assertEquals("om en stund", p.format(LocalDateTime.now())); + } + + @Test + public void testPrettyTimeCenturies() { + PrettyTime p = new PrettyTime((3155692597470L * 3L), locale); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + assertEquals("3 århundraden sedan", p.format(localDateTime)); + + p = new PrettyTime((0), locale); + localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(3155692597470L * 3L), ZoneId.systemDefault()); + assertEquals("om 3 århundraden", p.format(localDateTime)); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); + + PrettyTime t = new PrettyTime(ref, locale); + assertEquals("1 månad sedan", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(locale); + assertEquals("om en stund", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om en stund", t.format((600))); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 12 minuter", t.format((1000 * 60 * 12))); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 timmar", t.format((1000 * 60 * 60 * 3))); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 dagar", t.format((1000 * 60 * 60 * 24 * 3))); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 veckor", t.format((1000 * 60 * 60 * 24 * 7 * 3))); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 månader", t.format((2629743830L * 3L))); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 år", t.format((2629743830L * 12L * 3L))); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 årtionden", t.format((315569259747L * 3L))); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("om 3 århundraden", t.format((3155692597470L * 3L))); + } + + /* + * Past + */ + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime((6000), locale); + assertEquals("en stund sedan", t.format((0))); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 12), locale); + assertEquals("12 minuter sedan", t.format((0))); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3), locale); + assertEquals("3 timmar sedan", t.format((0))); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3), locale); + assertEquals("3 dagar sedan", t.format((0))); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3), locale); + assertEquals("3 veckor sedan", t.format((0))); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 3L), locale); + assertEquals("3 månader sedan", t.format((0))); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 12L * 3L), locale); + assertEquals("3 år sedan", t.format((0))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 3L), locale); + assertEquals("3 årtionden sedan", t.format((0))); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime((3155692597470L * 3L), locale); + assertEquals("3 århundraden sedan", t.format((0))); + } +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_Test.java new file mode 100644 index 0000000..b9bfe7b --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_Test.java @@ -0,0 +1,130 @@ +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeI18n_Test { + + // Stores current locale so that it can be restored + private Locale locale; + + // Method setUp() is called automatically before every test method + @Before + public void setUp() throws Exception { + locale = Locale.getDefault(); + } + + @Test + public void testPrettyTimeDefault() { + // The default resource bundle should be used + PrettyTime p = new PrettyTime(0, Locale.ROOT); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1), ZoneId.systemDefault()); + assertEquals("moments from now", p.format(localDateTime)); + } + + @Test + public void testPrettyTimeGerman() { + // The German resource bundle should be used + PrettyTime p = new PrettyTime(Locale.GERMAN); + LocalDateTime ref = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + p.setReference(ref); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1), ZoneId.systemDefault()); + assertEquals("Jetzt", p.format(localDateTime)); + } + + @Test + public void testPrettyTimeSpanish() { + // The Spanish resource bundle should be used + PrettyTime p = new PrettyTime(new Locale("es")); + assertEquals("en un instante", p.format(LocalDateTime.now())); + } + + @Test + public void testPrettyTimeDefaultCenturies() { + // The default resource bundle should be used + PrettyTime p = new PrettyTime((3155692597470L * 3L), Locale.ROOT); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + assertEquals("3 centuries ago", p.format(localDateTime)); + } + + @Test + public void testPrettyTimeGermanCenturies() { + // The default resource bundle should be used + PrettyTime p = new PrettyTime((3155692597470L * 3L), Locale.GERMAN); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + assertEquals("vor 3 Jahrhunderten", p.format(localDateTime)); + } + + @Test + public void testPrettyTimeViaDefaultLocaleDefault() { + // The default resource bundle should be used + Locale.setDefault(Locale.ROOT); + PrettyTime p = new PrettyTime((0)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1), ZoneId.systemDefault()); + assertEquals("moments from now", p.format(localDateTime)); + } + + @Test + public void testPrettyTimeViaDefaultLocaleGerman() { + // The German resource bundle should be used + Locale.setDefault(Locale.GERMAN); + PrettyTime p = new PrettyTime((0)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1), ZoneId.systemDefault()); + assertEquals("Jetzt", p.format(localDateTime)); + } + + @Test + public void testPrettyTimeViaDefaultLocaleDefaultCenturies() { + // The default resource bundle should be used + Locale.setDefault(Locale.ROOT); + PrettyTime p = new PrettyTime((3155692597470L * 3L)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + assertEquals("3 centuries ago", p.format(localDateTime)); + } + + @Test + public void testPrettyTimeViaDefaultLocaleGermanCenturies() { + // The default resource bundle should be used + Locale.setDefault(Locale.GERMAN); + PrettyTime p = new PrettyTime((3155692597470L * 3L)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + assertEquals("vor 3 Jahrhunderten", p.format(localDateTime)); + } + + @Test + public void testPrettyTimeRootLocale() { + long t = 1L; + PrettyTime p = new PrettyTime(0, Locale.ROOT); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + while (1000L * 60L * 60L * 24L * 365L * 1000000L > t) { + assertEquals(p.format(localDateTime).endsWith("now"), true); + t *= 2L; + } + } + + @Test + public void testPrettyTimeGermanLocale() { + long t = 1L; + PrettyTime p = new PrettyTime(0, Locale.GERMAN); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + while (1000L * 60L * 60L * 24L * 365L * 1000000L > t) { + assertEquals(p.format(localDateTime).startsWith("in") || p.format(localDateTime).startsWith("Jetzt"), true); + t *= 2L; + } + } + + // Method tearDown() is called automatically after every test method + @After + public void tearDown() throws Exception { + Locale.setDefault(locale); + } + +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_UA_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_UA_Test.java new file mode 100644 index 0000000..babd14b --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_UA_Test.java @@ -0,0 +1,194 @@ +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeI18n_UA_Test { + + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = new Locale("ua"); + Locale.setDefault(locale); + } + + @Test + public void testPrettyTime() { + PrettyTime p = new PrettyTime(locale); + assertEquals("зараз", p.format(LocalDateTime.now())); + } + + @Test + public void testPrettyTimeCenturies() { + PrettyTime p = new PrettyTime((3155692597470L * 3L), locale); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + assertEquals("3 століття тому", p.format(localDateTime)); + + p = new PrettyTime((0), locale); + localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(3155692597470L * 3L), ZoneId.systemDefault()); + assertEquals("через 3 століття", p.format(localDateTime)); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref, locale); + assertEquals("1 місяць тому", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(locale); + assertEquals("зараз", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("зараз", t.format(600)); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0, locale); + assertEquals("через 12 хвилин", t.format((1000 * 60 * 12))); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("через 3 години", t.format((1000 * 60 * 60 * 3))); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("через 3 дні", t.format((1000 * 60 * 60 * 24 * 3))); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("через 3 тижні", t.format((1000 * 60 * 60 * 24 * 7 * 3))); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("через 3 місяці", t.format((2629743830L * 3L))); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("через 3 роки", t.format((2629743830L * 12L * 3L))); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("через 3 десятиліття", t.format((315569259747L * 3L))); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("через 3 століття", t.format((3155692597470L * 3L))); + } + + /* + * Past + */ + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime((6000), locale); + assertEquals("тільки що", t.format((0))); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 12), locale); + assertEquals("12 хвилин тому", t.format((0))); + } + + @Test + public void test1HourAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60), locale); + assertEquals("1 годину тому", t.format((0))); + } + + @Test + public void test3HoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3), locale); + assertEquals("3 години тому", t.format((0))); + } + + @Test + public void test6HoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 6), locale); + assertEquals("6 годин тому", t.format((0))); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3), locale); + assertEquals("3 дні тому", t.format((0))); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3), locale); + assertEquals("3 тижні тому", t.format((0))); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 3L), locale); + assertEquals("3 місяці тому", t.format((0))); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 12L * 3L), locale); + assertEquals("3 роки тому", t.format((0))); + } + + @Test + public void test8YearsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 12L * 8L), locale); + assertEquals("8 років тому", t.format((0))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 3L), locale); + assertEquals("3 десятиліття тому", t.format((0))); + } + + @Test + public void test8DecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 8L), locale); + assertEquals("8 десятиліть тому", t.format((0))); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime((3155692597470L * 3L), locale); + assertEquals("3 століття тому", t.format((0))); + } + + @After + public void tearDown() throws Exception { + Locale.setDefault(locale); + } +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_hi_IN_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_hi_IN_Test.java new file mode 100644 index 0000000..3629bd1 --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_hi_IN_Test.java @@ -0,0 +1,248 @@ +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PrettyTimeI18n_hi_IN_Test { + + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = new Locale("hi", "IN"); + Locale.setDefault(locale); + } + + @Test + public void testLocaleISOCorrectness() { + assertEquals("hi", this.locale.getLanguage()); + assertEquals("IN", this.locale.getCountry()); + assertEquals("हिंदी", this.locale.getDisplayLanguage()); + assertEquals("भारत", this.locale.getDisplayCountry()); + } + + @Test + public void testNow() { + PrettyTime prettyTime = new PrettyTime(locale); + assertEquals("अभी", prettyTime.format(LocalDateTime.now())); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref); + assertEquals("1 महीना पहले", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(); + assertEquals("अभी", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("अभी", t.format((600))); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("12 मिनट बाद", t.format((1000 * 60 * 12))); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 घंटे बाद", t.format((1000 * 60 * 60 * 3))); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 दिन बाद", + t.format((1000 * 60 * 60 * 24 * 3))); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 सप्ताह बाद", + t.format((1000 * 60 * 60 * 24 * 7 * 3))); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 महीने बाद", t.format((2629743830L * 3L))); + //assertEquals("अभी से 3 महीने बाद", t.format((1000 * 60 * 60 * 24 * 365 * 3L))); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 वर्ष बाद", + t.format((2629743830L * 12L * 3L))); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 दशक बाद", + t.format((315569259747L * 3L))); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 सदियों बाद", + t.format((3155692597470L * 3L))); + } + + /* + * Past + */ + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime((6000)); + assertEquals("अभी", t.format((0))); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 12)); + assertEquals("12 मिनट पहले", t.format((0))); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3)); + assertEquals("3 घंटे पहले", t.format((0))); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3)); + assertEquals("3 दिन पहले", t.format((0))); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3)); + assertEquals("3 सप्ताह पहले", t.format((0))); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 3L)); + assertEquals("3 महीने पहले", t.format((0))); + } + + @Test + public void testCustomFormat() throws Exception { + PrettyTime t = new PrettyTime((0)); + TimeUnit unit = new TimeUnit() { + @Override + public long getMaxQuantity() { + return 0; + } + + @Override + public long getMillisPerUnit() { + return 5000; + } + }; + t.clearUnits(); + t.registerUnit(unit, new SimpleTimeFormat().setSingularName("खेल") + .setPluralName("खेल").setPattern("%n %u") + .setRoundingTolerance(20).setFutureSuffix("होंगे ") + .setFuturePrefix("भविष्य में ") + .setPastPrefix("पहले ") + .setPastSuffix("थे")); + + assertEquals("भविष्य में 5 खेल होंगे", t.format((25000))); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(25000), ZoneId.systemDefault()); + t.setReference(localDateTime); + assertEquals("पहले 5 खेल थे", t.format((0))); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 12L * 3L)); + assertEquals("3 वर्ष पहले", t.format((0))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 3L)); + assertEquals("3 दशक पहले", t.format((0))); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime((3155692597470L * 3L)); + assertEquals("3 सदियों पहले", t.format((0))); + } + + @Test + public void testWithinTwoHoursRounding() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(6544); + assertEquals("2 घंटे पहले", t.format(localDateTime)); + } + + @Test + public void testPreciseInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); + assertEquals(5, timeUnitQuantities.get(0).getQuantity()); + assertEquals(10, timeUnitQuantities.get(1).getQuantity()); + } + + @Test + public void testPreciseInThePast() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); + assertEquals(-5, timeUnitQuantities.get(0).getQuantity()); + assertTrue(timeUnitQuantities.get(1).getQuantity() == -9 || timeUnitQuantities.get(1).getQuantity() == -10); + } + + @Test + public void testFormattingDurationListInThePast() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3 + 1000 + * 60 * 60 * 15 + 1000 * 60 * 38)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 दिन 15 घंटे 38 मिनट पहले", t.format(timeUnitQuantities)); + } + + @Test + public void testFormattingDurationListInTheFuture() throws Exception { + PrettyTime t = new PrettyTime((0)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + + 1000 * 60 * 38), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 दिन 15 घंटे 38 मिनट बाद", t.format(timeUnitQuantities)); + } + + @After + public void tearDown() throws Exception { + Locale.setDefault(Locale.ENGLISH); + } +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_in_ID_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_in_ID_Test.java new file mode 100644 index 0000000..0f13192 --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_in_ID_Test.java @@ -0,0 +1,247 @@ +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PrettyTimeI18n_in_ID_Test { + + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = new Locale("in", "ID"); + Locale.setDefault(locale); + } + + @Test + public void testLocaleISOCorrectness() { + assertEquals("in", this.locale.getLanguage()); + assertEquals("ID", this.locale.getCountry()); + assertEquals("Bahasa Indonesia", this.locale.getDisplayLanguage()); + assertEquals("Indonesia", this.locale.getDisplayCountry()); + } + + @Test + public void testNow() { + PrettyTime prettyTime = new PrettyTime(locale); + assertEquals("dari sekarang", prettyTime.format(LocalDateTime.now())); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref); + assertEquals("1 bulan yang lalu", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(); + assertEquals("dari sekarang", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("dari sekarang", t.format((600))); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("12 menit dari sekarang", t.format((1000 * 60 * 12))); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + assertEquals("3 jam dari sekarang", t.format((1000 * 60 * 60 * 3))); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 hari dari sekarang", + t.format((1000 * 60 * 60 * 24 * 3))); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 minggu dari sekarang", + t.format((1000 * 60 * 60 * 24 * 7 * 3))); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 bulan dari sekarang", t.format((2629743830L * 3L))); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 tahun dari sekarang", + t.format((2629743830L * 12L * 3L))); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 dekade dari sekarang", + t.format((315569259747L * 3L))); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 abad dari sekarang", + t.format((3155692597470L * 3L))); + } + + /* + * Past + */ + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime((6000)); + assertEquals("yang lalu", t.format((0))); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 12)); + assertEquals("12 menit yang lalu", t.format((0))); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3)); + assertEquals("3 jam yang lalu", t.format((0))); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3)); + assertEquals("3 hari yang lalu", t.format((0))); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3)); + assertEquals("3 minggu yang lalu", t.format((0))); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 3L)); + assertEquals("3 bulan yang lalu", t.format((0))); + } + + @Test + public void testCustomFormat() throws Exception { + PrettyTime t = new PrettyTime((0)); + TimeUnit unit = new TimeUnit() { + @Override + public long getMaxQuantity() { + return 0; + } + + @Override + public long getMillisPerUnit() { + return 5000; + } + }; + t.clearUnits(); + t.registerUnit(unit, new SimpleTimeFormat().setSingularName("hitungan") + .setPluralName("hitungan").setPattern("%n %u") + .setRoundingTolerance(20).setFutureSuffix("... LARI!") + .setFuturePrefix("hancur dalam: ") + .setPastPrefix("telah hancur dalam: ") + .setPastSuffix("")); + + assertEquals("hancur dalam: 5 hitungan ... LARI!", t.format((25000))); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(25000), ZoneId.systemDefault()); + t.setReference(localDateTime); + assertEquals("telah hancur dalam: 5 hitungan", t.format((0))); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 12L * 3L)); + assertEquals("3 tahun yang lalu", t.format((0))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 3L)); + assertEquals("3 dekade yang lalu", t.format((0))); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime((3155692597470L * 3L)); + assertEquals("3 abad yang lalu", t.format((0))); + } + + @Test + public void testWithinTwoHoursRounding() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(6544); + assertEquals("2 jam yang lalu", t.format(localDateTime)); + } + + @Test + public void testPreciseInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); + assertEquals(5, timeUnitQuantities.get(0).getQuantity()); + assertEquals(10, timeUnitQuantities.get(1).getQuantity()); + } + + @Test + public void testPreciseInThePast() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); + assertEquals(-5, timeUnitQuantities.get(0).getQuantity()); + assertTrue(-10 == timeUnitQuantities.get(1).getQuantity() || -9 == timeUnitQuantities.get(1).getQuantity()); + } + + @Test + public void testFormattingDurationListInThePast() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3 + 1000 + * 60 * 60 * 15 + 1000 * 60 * 38)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 hari 15 jam 38 menit yang lalu", t.format(timeUnitQuantities)); + } + + @Test + public void testFormattingDurationListInTheFuture() throws Exception { + PrettyTime t = new PrettyTime((0)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + + 1000 * 60 * 38), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 hari 15 jam 38 menit dari sekarang", t.format(timeUnitQuantities)); + } + + @After + public void tearDown() throws Exception { + Locale.setDefault(Locale.ENGLISH); + } +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_zh_TW_Test.java b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_zh_TW_Test.java new file mode 100644 index 0000000..dd9e14b --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeI18n_zh_TW_Test.java @@ -0,0 +1,190 @@ +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.LocalDateTime; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeI18n_zh_TW_Test { + + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = Locale.TRADITIONAL_CHINESE; + Locale.setDefault(Locale.TRADITIONAL_CHINESE); + } + + @Test + public void testPrettyTime() { + PrettyTime p = new PrettyTime(locale); + assertEquals("剛剛", p.format(LocalDateTime.now())); + } + + @Test + public void testPrettyTimeCenturies() { + PrettyTime p = new PrettyTime((3155692597470L * 3L), locale); + assertEquals("3 世紀 前", p.format((0))); + + p = new PrettyTime((0), locale); + assertEquals("3 世紀 後", p.format((3155692597470L * 3L))); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref, locale); + assertEquals("1 個月 前", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(locale); + assertEquals("剛剛", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("剛剛", t.format((600))); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("12 分鐘 後", t.format((1000 * 60 * 12))); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 小時 後", t.format((1000 * 60 * 60 * 3))); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 天 後", t.format((1000 * 60 * 60 * 24 * 3))); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 週 後", t.format((1000 * 60 * 60 * 24 * 7 * 3))); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 個月 後", t.format((2629743830L * 3L))); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 年 後", t.format((2629743830L * 12L * 3L))); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("30 年 後", t.format((315569259747L * 3L))); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0), locale); + assertEquals("3 世紀 後", t.format((3155692597470L * 3L))); + } + + /* + * Past + */ + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime((6000), locale); + assertEquals("片刻之前", t.format((0))); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 12), locale); + assertEquals("12 分鐘 前", t.format((0))); + } + + @Test + public void test1HourAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60), locale); + assertEquals("1 小時 前", t.format((0))); + } + + @Test + public void test3HoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3), locale); + assertEquals("3 小時 前", t.format((0))); + } + + @Test + public void test6HoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 6), locale); + assertEquals("6 小時 前", t.format((0))); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3), locale); + assertEquals("3 天 前", t.format((0))); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3), locale); + assertEquals("3 週 前", t.format((0))); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 3L), locale); + assertEquals("3 個月 前", t.format((0))); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 12L * 3L), locale); + assertEquals("3 年 前", t.format((0))); + } + + @Test + public void test8YearsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 12L * 8L), locale); + assertEquals("8 年 前", t.format((0))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 3L), locale); + assertEquals("30 年 前", t.format((0))); + } + + @Test + public void test8DecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 8L), locale); + assertEquals("80 年 前", t.format((0))); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime((3155692597470L * 3L), locale); + assertEquals("3 世紀 前", t.format((0))); + } + + @After + public void tearDown() throws Exception { + Locale.setDefault(locale); + } +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeLocaleFallbackTest.java b/src/test/java/org/xbib/time/pretty/PrettyTimeLocaleFallbackTest.java new file mode 100644 index 0000000..752bc45 --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeLocaleFallbackTest.java @@ -0,0 +1,38 @@ +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.LocalDateTime; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeLocaleFallbackTest { + + // Stores current locale so that it can be restored + private Locale locale; + + // Method setUp() is called automatically before every test method + @Before + public void setUp() throws Exception { + locale = Locale.getDefault(); + Locale.setDefault(new Locale("Foo", "Bar")); + } + + @Test + public void testCeilingInterval() throws Exception { + assertEquals(new Locale("Foo", "Bar"), Locale.getDefault()); + LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref); + assertEquals("1 month ago", t.format(then)); + } + + @After + public void tearDown() throws Exception { + Locale.setDefault(locale); + } + +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeNoSignTest.java b/src/test/java/org/xbib/time/pretty/PrettyTimeNoSignTest.java new file mode 100644 index 0000000..1559a69 --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeNoSignTest.java @@ -0,0 +1,28 @@ +package org.xbib.time.pretty; + +import org.junit.Assert; +import org.junit.Test; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Locale; + +public class PrettyTimeNoSignTest { + + @Test + public void testNoSuffixes() throws Exception { + LocalDateTime then = LocalDateTime.of(2009, 8, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 5, 17, 0, 0); + PrettyTime p = new PrettyTime(ref, Locale.ENGLISH); + + List units = p.getUnits(); + for (TimeUnit unit : units) { + TimeFormat fmt = p.getFormat(unit); + if (fmt instanceof SimpleTimeFormat) { + ((SimpleTimeFormat) fmt).setFuturePrefix("").setFutureSuffix("").setPastPrefix("").setPastSuffix(""); + } + } + + Assert.assertEquals("3 months", p.format(then)); + } +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeTest.java b/src/test/java/org/xbib/time/pretty/PrettyTimeTest.java new file mode 100644 index 0000000..f30520c --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeTest.java @@ -0,0 +1,242 @@ +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PrettyTimeTest { + + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = Locale.getDefault(); + Locale.setDefault(Locale.ROOT); + } + + @Test + public void testCeilingInterval() throws Exception { + LocalDateTime then = LocalDateTime.of(2009, 5, 20, 0, 0); + LocalDateTime ref = LocalDateTime.of(2009, 6, 17, 0, 0); + PrettyTime t = new PrettyTime(ref); + assertEquals("1 month ago", t.format(then)); + } + + @Test + public void testRightNow() throws Exception { + PrettyTime t = new PrettyTime(); + assertEquals("moments from now", t.format(LocalDateTime.now())); + } + + @Test + public void testRightNowVariance() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("moments from now", t.format(600)); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("12 minutes from now", t.format((1000 * 60 * 12))); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 hours from now", t.format((1000 * 60 * 60 * 3))); + } + + @Test + public void testDaysFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 days from now", t.format((1000 * 60 * 60 * 24 * 3))); + } + + @Test + public void testWeeksFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 weeks from now", t.format((1000 * 60 * 60 * 24 * 7 * 3))); + } + + @Test + public void testMonthsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 months from now", t.format((2629743830L * 3L))); + } + + @Test + public void testYearsFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 years from now", t.format((2629743830L * 12L * 3L))); + } + + @Test + public void testDecadesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 decades from now", t.format((315569259747L * 3L))); + } + + @Test + public void testCenturiesFromNow() throws Exception { + PrettyTime t = new PrettyTime((0)); + assertEquals("3 centuries from now", t.format((3155692597470L * 3L))); + } + + @Test + public void testMomentsAgo() throws Exception { + PrettyTime t = new PrettyTime((6000)); + assertEquals("moments ago", t.format((0))); + } + + @Test + public void testMinutesAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 12)); + assertEquals("12 minutes ago", t.format((0))); + } + + @Test + public void testHoursAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 3)); + assertEquals("3 hours ago", t.format((0))); + } + + @Test + public void testDaysAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3)); + assertEquals("3 days ago", t.format((0))); + } + + @Test + public void testWeeksAgo() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 7 * 3)); + assertEquals("3 weeks ago", t.format((0))); + } + + @Test + public void testMonthsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 3L)); + assertEquals("3 months ago", t.format((0))); + } + + @Test + public void testCustomFormat() throws Exception { + PrettyTime t = new PrettyTime((0)); + TimeUnit unit = new TimeUnit() { + @Override + public long getMaxQuantity() { + return 0; + } + + @Override + public long getMillisPerUnit() { + return 5000; + } + }; + t.clearUnits(); + t.registerUnit(unit, new SimpleTimeFormat() + .setSingularName("tick").setPluralName("ticks") + .setPattern("%n %u").setRoundingTolerance(20) + .setFutureSuffix("... RUN!") + .setFuturePrefix("self destruct in: ").setPastPrefix("self destruct was: ").setPastSuffix( + " ago...")); + + assertEquals("self destruct in: 5 ticks ... RUN!", t.format((25000))); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(25000), ZoneId.systemDefault()); + t.setReference(localDateTime); + assertEquals("self destruct was: 5 ticks ago...", t.format((0))); + } + + @Test + public void testYearsAgo() throws Exception { + PrettyTime t = new PrettyTime((2629743830L * 12L * 3L)); + assertEquals("3 years ago", t.format((0))); + } + + @Test + public void testDecadesAgo() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 3L)); + assertEquals("3 decades ago", t.format((0))); + } + + @Test + public void testCenturiesAgo() throws Exception { + PrettyTime t = new PrettyTime((3155692597470L * 3L)); + assertEquals("3 centuries ago", t.format((0))); + } + + @Test + public void testWithinTwoHoursRounding() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(6544); + assertEquals("2 hours ago", t.format(localDateTime)); + } + + @Test + public void testPreciseInTheFuture() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); + assertEquals(5, timeUnitQuantities.get(0).getQuantity()); + assertEquals(10, timeUnitQuantities.get(1).getQuantity()); + } + + @Test + public void testPreciseInThePast() throws Exception { + PrettyTime t = new PrettyTime(); + LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(10 * 60 + 5 * 60 * 60); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertTrue(timeUnitQuantities.size() >= 2); + assertEquals(-5, timeUnitQuantities.get(0).getQuantity()); + // TODO why -10 or -9? + assertTrue(-10 == timeUnitQuantities.get(1).getQuantity() || -9 == timeUnitQuantities.get(1).getQuantity()); + } + + @Test + public void testFormattingDurationListInThePast() throws Exception { + PrettyTime t = new PrettyTime((1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + 1000 * 60 * 38)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 days 15 hours 38 minutes ago", t.format(timeUnitQuantities)); + } + + @Test + public void testFormattingDurationListInTheFuture() throws Exception { + PrettyTime t = new PrettyTime((0)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 3 + 1000 * 60 * 60 * 15 + + 1000 * 60 * 38), ZoneId.systemDefault()); + List timeUnitQuantities = t.calculatePreciseDuration(localDateTime); + assertEquals("3 days 15 hours 38 minutes from now", t.format(timeUnitQuantities)); + } + + @Test + public void testSetLocale() throws Exception { + PrettyTime t = new PrettyTime((315569259747L * 3L)); + assertEquals("3 decades ago", t.format((0))); + t.setLocale(Locale.GERMAN); + assertEquals("vor 3 Jahrzehnten", t.format((0))); + } + + @Test + public void testFormatApproximateDuration() throws Exception { + LocalDateTime localDateTime = LocalDateTime.now().minusMinutes(10); + PrettyTime t = new PrettyTime(); + String result = t.formatApproximateDuration(localDateTime); + assert result.equals("10 minutes"); + } + + @After + public void tearDown() throws Exception { + Locale.setDefault(locale); + } + +} diff --git a/src/test/java/org/xbib/time/pretty/PrettyTimeUnitConfigurationTest.java b/src/test/java/org/xbib/time/pretty/PrettyTimeUnitConfigurationTest.java new file mode 100644 index 0000000..75f3379 --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/PrettyTimeUnitConfigurationTest.java @@ -0,0 +1,59 @@ +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.xbib.time.pretty.units.Hour; +import org.xbib.time.pretty.units.JustNow; +import org.xbib.time.pretty.units.Minute; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class PrettyTimeUnitConfigurationTest { + + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = Locale.getDefault(); + Locale.setDefault(Locale.ROOT); + } + + @Test + public void testRightNow() throws Exception { + LocalDateTime ref = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + LocalDateTime then = LocalDateTime.ofInstant(Instant.ofEpochMilli(2), ZoneId.systemDefault()); + PrettyTime t = new PrettyTime(ref); + TimeFormat format = t.removeUnit(JustNow.class); + Assert.assertNotNull(format); + assertEquals("2 milliseconds from now", t.format(then)); + } + + @Test + public void testMinutesFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + TimeFormat format = t.removeUnit(Minute.class); + Assert.assertNotNull(format); + assertEquals("720 seconds from now", t.format(1000 * 60 * 12)); + } + + @Test + public void testHoursFromNow() throws Exception { + PrettyTime t = new PrettyTime(0); + TimeFormat format = t.removeUnit(Hour.class); + Assert.assertNotNull(format); + assertEquals("180 minutes from now", t.format(1000 * 60 * 60 * 3)); + } + + @After + public void tearDown() throws Exception { + Locale.setDefault(locale); + } + +} diff --git a/src/test/java/org/xbib/time/pretty/SimpleTimeFormatTest.java b/src/test/java/org/xbib/time/pretty/SimpleTimeFormatTest.java new file mode 100644 index 0000000..0d46260 --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/SimpleTimeFormatTest.java @@ -0,0 +1,52 @@ +package org.xbib.time.pretty; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +public class SimpleTimeFormatTest { + + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = Locale.getDefault(); + Locale.setDefault(Locale.ROOT); + } + + @Test + public void testRounding() throws Exception { + PrettyTime t = new PrettyTime(1000 * 60 * 60 * 3 + 1000 * 60 * 45); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + TimeUnitQuantity timeUnitQuantity = t.approximateDuration(localDateTime); + assertEquals("4 hours ago", t.format(timeUnitQuantity)); + assertEquals("3 hours ago", t.formatUnrounded(timeUnitQuantity)); + } + + @Test + public void testDecorating() throws Exception { + PrettyTime t = new PrettyTime(); + TimeFormat format = new SimpleTimeFormat().setFutureSuffix("from now").setPastSuffix("ago"); + + LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(1); + TimeUnitQuantity timeUnitQuantity = t.approximateDuration(localDateTime); + assertEquals("some time from now", format.decorate(timeUnitQuantity, "some time")); + + localDateTime = LocalDateTime.now().minusSeconds(10); + timeUnitQuantity = t.approximateDuration(localDateTime); + assertEquals("some time ago", format.decorate(timeUnitQuantity, "some time")); + } + + @After + public void tearDown() throws Exception { + Locale.setDefault(locale); + } + +} diff --git a/src/test/java/org/xbib/time/pretty/i18n/Resources_xx.java b/src/test/java/org/xbib/time/pretty/i18n/Resources_xx.java new file mode 100644 index 0000000..2aad1e8 --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/i18n/Resources_xx.java @@ -0,0 +1,53 @@ +package org.xbib.time.pretty.i18n; + +import org.xbib.time.pretty.TimeFormat; +import org.xbib.time.pretty.TimeFormatProvider; +import org.xbib.time.pretty.TimeUnit; +import org.xbib.time.pretty.TimeUnitQuantity; +import org.xbib.time.pretty.units.Minute; + +import java.util.ListResourceBundle; + +public class Resources_xx extends ListResourceBundle implements TimeFormatProvider { + + private static final Object[][] OBJECTS = new Object[][]{}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } + + @Override + public TimeFormat getFormatFor(TimeUnit t) { + if (t instanceof Minute) { + return new TimeFormat() { + + @Override + public String decorate(TimeUnitQuantity timeUnitQuantity, String time) { + String result = timeUnitQuantity.getQuantityRounded(50) > 1 ? time + "es" : "e"; + result += timeUnitQuantity.isInPast() ? " ago" : " from now"; + return result; + } + + @Override + public String decorateUnrounded(TimeUnitQuantity timeUnitQuantity, String time) { + String result = timeUnitQuantity.getQuantity() > 1 ? time + "es" : "e"; + result += timeUnitQuantity.isInPast() ? " ago" : " from now"; + return result; + } + + @Override + public String format(TimeUnitQuantity timeUnitQuantity) { + return timeUnitQuantity.getQuantityRounded(50) + " minut"; + } + + @Override + public String formatUnrounded(TimeUnitQuantity timeUnitQuantity) { + return timeUnitQuantity.getQuantity() + " minut"; + } + }; + } + return null; + } + +} diff --git a/src/test/java/org/xbib/time/pretty/i18n/Resources_yy.java b/src/test/java/org/xbib/time/pretty/i18n/Resources_yy.java new file mode 100644 index 0000000..84fa8fc --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/i18n/Resources_yy.java @@ -0,0 +1,111 @@ +package org.xbib.time.pretty.i18n; + +import java.util.ListResourceBundle; + +public class Resources_yy extends ListResourceBundle { + private static final Object[][] OBJECTS = new Object[][]{ + {"CenturyPattern", "%n %u"}, + {"CenturyFuturePrefix", ""}, + {"CenturyFutureSuffix", " from now"}, + {"CenturyPastPrefix", ""}, + {"CenturyPastSuffix", " ago"}, + {"CenturySingularName", "century"}, + {"CenturyPluralName", "centuries"}, + {"DayPattern", "%n %u"}, + {"DayFuturePrefix", ""}, + {"DayFutureSuffix", " from now"}, + {"DayPastPrefix", ""}, + {"DayPastSuffix", " ago"}, + {"DaySingularName", "day"}, + {"DayPluralName", "days"}, + {"DayFutureSingularName", "day"}, + {"DayFuturePluralName", "days"}, + {"DayPastSingularName", "day"}, + {"DayPastPluralName", "days"}, + {"DecadePattern", "%n %u"}, + {"DecadeFuturePrefix", ""}, + {"DecadeFutureSuffix", " from now"}, + {"DecadePastPrefix", ""}, + {"DecadePastSuffix", " ago"}, + {"DecadeSingularName", "decade"}, + {"DecadePluralName", "decades"}, + {"HourPattern", "%n %u"}, + {"HourFuturePrefix", ""}, + {"HourFutureSuffix", " from now"}, + {"HourPastPrefix", ""}, + {"HourPastSuffix", " ago"}, + {"HourSingularName", "hour"}, + {"HourPluralName", "hours"}, + {"HourFutureSingularName", "hour"}, + {"HourFuturePluralName", "hours"}, + {"HourPastSingularName", ""}, + {"JustNowPattern", "%u"}, + {"JustNowFuturePrefix", ""}, + {"JustNowFutureSuffix", "moments from now"}, + {"JustNowPastPrefix", "moments ago"}, + {"JustNowPastSuffix", ""}, + {"JustNowSingularName", ""}, + {"JustNowPluralName", ""}, + {"MillenniumPattern", "%n %u"}, + {"MillenniumFuturePrefix", ""}, + {"MillenniumFutureSuffix", " from now"}, + {"MillenniumPastPrefix", ""}, + {"MillenniumPastSuffix", " ago"}, + {"MillenniumSingularName", "millennium"}, + {"MillenniumPluralName", "millennia"}, + {"MillisecondPattern", "%n %u"}, + {"MillisecondFuturePrefix", ""}, + {"MillisecondFutureSuffix", " from now"}, + {"MillisecondPastPrefix", ""}, + {"MillisecondPastSuffix", " ago"}, + {"MillisecondSingularName", "millisecond"}, + {"MillisecondPluralName", "milliseconds"}, + {"MinutePattern", "%n %u"}, + {"MinuteFuturePrefix", ""}, + {"MinuteFutureSuffix", " from now"}, + {"MinutePastPrefix", ""}, + {"MinutePastSuffix", " ago"}, + {"MinuteSingularName", "minute"}, + {"MinutePluralName", "minutes"}, + {"MonthPattern", "%n %u"}, + {"MonthFuturePrefix", ""}, + {"MonthFutureSuffix", " from now"}, + {"MonthPastPrefix", ""}, + {"MonthPastSuffix", " ago"}, + {"MonthSingularName", "month"}, + {"MonthPluralName", "months"}, + {"SecondPattern", "%n %u"}, + {"SecondFuturePrefix", ""}, + {"SecondFutureSuffix", " from now"}, + {"SecondPastPrefix", ""}, + {"SecondPastSuffix", " ago"}, + {"SecondSingularName", "second"}, + {"SecondPluralName", "seconds"}, + {"WeekPattern", "%n %u"}, + {"WeekFuturePrefix", ""}, + {"WeekFutureSuffix", " from now"}, + {"WeekPastPrefix", ""}, + {"WeekPastSuffix", " ago"}, + {"WeekSingularName", "week"}, + {"WeekPluralName", "weeks"}, + {"YearPattern", "%n %u"}, + {"YearFuturePrefix", ""}, + {"YearFutureSuffix", " from now"}, + {"YearPastPrefix", ""}, + {"YearPastSuffix", " ago"}, + {"YearSingularName", "year"}, + {"YearPluralName", "years"}, + {"AbstractTimeUnitPattern", ""}, + {"AbstractTimeUnitFuturePrefix", ""}, + {"AbstractTimeUnitFutureSuffix", ""}, + {"AbstractTimeUnitPastPrefix", ""}, + {"AbstractTimeUnitPastSuffix", ""}, + {"AbstractTimeUnitSingularName", ""}, + {"AbstractTimeUnitPluralName", ""}}; + + @Override + public Object[][] getContents() { + return OBJECTS; + } + +} \ No newline at end of file diff --git a/src/test/java/org/xbib/time/pretty/i18n/SimpleTimeFormatTimeQuantifiedNameTest.java b/src/test/java/org/xbib/time/pretty/i18n/SimpleTimeFormatTimeQuantifiedNameTest.java new file mode 100644 index 0000000..8ec02cc --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/i18n/SimpleTimeFormatTimeQuantifiedNameTest.java @@ -0,0 +1,85 @@ +package org.xbib.time.pretty.i18n; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.xbib.time.pretty.PrettyTime; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Locale; + +public class SimpleTimeFormatTimeQuantifiedNameTest { + private Locale locale; + + @Before + public void setUp() throws Exception { + locale = Locale.getDefault(); + Locale.setDefault(new Locale("yy")); + } + + @Test + public void testFuturePluralName() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime p = new PrettyTime(localDateTime); + Assert.assertEquals("2 days from now", p.format(1000 * 60 * 60 * 24 * 2)); + } + + @Test + public void testPastPluralName() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24 * 2), ZoneId.systemDefault()); + PrettyTime p = new PrettyTime(localDateTime); + Assert.assertEquals("2 days ago", p.format(0)); + } + + @Test + public void testFutureSingularName() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime p = new PrettyTime(localDateTime); + Assert.assertEquals("1 day from now", p.format(1000 * 60 * 60 * 24)); + } + + @Test + public void testPastSingularName() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 24), ZoneId.systemDefault()); + PrettyTime p = new PrettyTime(localDateTime); + Assert.assertEquals("1 day ago", p.format(0)); + } + + @Test + public void testFuturePluralNameEmpty() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime p = new PrettyTime(localDateTime); + Assert.assertEquals("2 hours from now", p.format(1000 * 60 * 60 * 2)); + } + + @Test + public void testPastPluralNameMissing() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60 * 2), ZoneId.systemDefault()); + PrettyTime p = new PrettyTime(localDateTime); + Assert.assertEquals("2 hours ago", p.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()))); + } + + @Test + public void testFutureSingularNameCopy() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + PrettyTime p = new PrettyTime(localDateTime); + Assert.assertEquals("1 hour from now", p.format(1000 * 60 * 60)); + } + + @Test + public void testPastSingularNameNull() throws Exception { + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(1000 * 60 * 60), ZoneId.systemDefault()); + PrettyTime p = new PrettyTime(localDateTime); + Assert.assertEquals("1 hour ago", p.format(0)); + } + + // Method tearDown() is called automatically after every test method + @After + public void tearDown() throws Exception { + Locale.setDefault(locale); + } + +} diff --git a/src/test/java/org/xbib/time/pretty/i18n/TimeFormatProviderTest.java b/src/test/java/org/xbib/time/pretty/i18n/TimeFormatProviderTest.java new file mode 100644 index 0000000..34e5f7f --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/i18n/TimeFormatProviderTest.java @@ -0,0 +1,29 @@ +package org.xbib.time.pretty.i18n; + +import org.junit.Assert; +import org.junit.Test; +import org.xbib.time.pretty.PrettyTime; +import org.xbib.time.pretty.TimeFormatProvider; + +import java.util.Locale; +import java.util.ResourceBundle; + +public class TimeFormatProviderTest { + @Test + public void test() { + Locale locale = new Locale("xx"); + Locale.setDefault(locale); + ResourceBundle bundle = ResourceBundle.getBundle(Resources.class.getName(), locale); + Assert.assertTrue(bundle instanceof TimeFormatProvider); + } + + @Test + public void testFormatFromDirectFormatOverride() throws Exception { + Locale locale = new Locale("xx"); + Locale.setDefault(locale); + PrettyTime prettyTime = new PrettyTime(locale); + String result = prettyTime.format(System.currentTimeMillis() + 1000 * 60 * 6); + Assert.assertEquals("6 minutes from now", result); + } + +} diff --git a/src/test/java/org/xbib/time/pretty/units/TimeUnitComparatorTest.java b/src/test/java/org/xbib/time/pretty/units/TimeUnitComparatorTest.java new file mode 100644 index 0000000..9c16121 --- /dev/null +++ b/src/test/java/org/xbib/time/pretty/units/TimeUnitComparatorTest.java @@ -0,0 +1,15 @@ +package org.xbib.time.pretty.units; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class TimeUnitComparatorTest { + + @Test + public void testComparingOrder() throws Exception { + TimeUnitComparator comparator = new TimeUnitComparator(); + assertEquals(-1, comparator.compare(new Hour(), new Day())); + } + +} diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml new file mode 100644 index 0000000..f71aced --- /dev/null +++ b/src/test/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file