From 2aead18a0fcb6ac754f290341c1971cff3c49032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Wed, 2 Nov 2016 11:46:35 +0100 Subject: [PATCH] initial commit --- .gitignore | 16 + LICENSE.txt | 202 ++ build.gradle | 79 + config/checkstyle/checkstyle.xml | 323 +++ gradle/ext.gradle | 8 + gradle/publish.gradle | 73 + gradle/sonarqube.gradle | 41 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 52928 bytes gradle/wrapper/gradle-wrapper.properties | 6 + settings.gradle | 1 + .../java/org/xbib/time/chronic/Chronic.java | 193 ++ .../java/org/xbib/time/chronic/Options.java | 81 + .../java/org/xbib/time/chronic/Range.java | 40 + src/main/java/org/xbib/time/chronic/Span.java | 54 + src/main/java/org/xbib/time/chronic/Tick.java | 35 + .../java/org/xbib/time/chronic/Token.java | 97 + .../xbib/time/chronic/handlers/Handler.java | 386 ++++ .../time/chronic/handlers/HandlerPattern.java | 16 + .../chronic/handlers/HandlerTypePattern.java | 21 + .../xbib/time/chronic/handlers/IHandler.java | 14 + .../xbib/time/chronic/handlers/MDHandler.java | 25 + .../time/chronic/handlers/ORGRHandler.java | 19 + .../time/chronic/handlers/ORRHandler.java | 32 + .../time/chronic/handlers/ORSRHandler.java | 19 + .../time/chronic/handlers/PSRHandler.java | 23 + .../time/chronic/handlers/RGRHandler.java | 23 + .../xbib/time/chronic/handlers/RHandler.java | 19 + .../handlers/RdnRmnSdTTzSyHandler.java | 32 + .../time/chronic/handlers/RmnOdHandler.java | 19 + .../time/chronic/handlers/RmnSdHandler.java | 19 + .../time/chronic/handlers/RmnSdSyHandler.java | 33 + .../time/chronic/handlers/RmnSyHandler.java | 32 + .../time/chronic/handlers/SRPAHandler.java | 20 + .../time/chronic/handlers/SRPHandler.java | 35 + .../time/chronic/handlers/SdRmnSyHandler.java | 25 + .../time/chronic/handlers/SdSmSyHandler.java | 24 + .../time/chronic/handlers/SmSdHandler.java | 25 + .../time/chronic/handlers/SmSdSyHandler.java | 33 + .../time/chronic/handlers/SmSyHandler.java | 32 + .../time/chronic/handlers/SySmSdHandler.java | 24 + .../time/chronic/handlers/TagPattern.java | 29 + .../time/chronic/handlers/package-info.java | 4 + .../time/chronic/numerizer/Numerizer.java | 194 ++ .../time/chronic/numerizer/package-info.java | 4 + .../org/xbib/time/chronic/package-info.java | 4 + .../repeaters/EnumRepeaterDayPortion.java | 45 + .../repeaters/IntegerRepeaterDayPortion.java | 22 + .../xbib/time/chronic/repeaters/Repeater.java | 91 + .../time/chronic/repeaters/RepeaterDay.java | 72 + .../chronic/repeaters/RepeaterDayName.java | 106 + .../chronic/repeaters/RepeaterDayPortion.java | 147 ++ .../chronic/repeaters/RepeaterFortnight.java | 93 + .../time/chronic/repeaters/RepeaterHour.java | 78 + .../chronic/repeaters/RepeaterMinute.java | 73 + .../time/chronic/repeaters/RepeaterMonth.java | 73 + .../chronic/repeaters/RepeaterMonthName.java | 145 ++ .../chronic/repeaters/RepeaterSecond.java | 49 + .../time/chronic/repeaters/RepeaterTime.java | 223 +++ .../time/chronic/repeaters/RepeaterUnit.java | 61 + .../time/chronic/repeaters/RepeaterWeek.java | 96 + .../chronic/repeaters/RepeaterWeekend.java | 80 + .../time/chronic/repeaters/RepeaterYear.java | 77 + .../time/chronic/repeaters/package-info.java | 4 + .../org/xbib/time/chronic/tags/Grabber.java | 58 + .../org/xbib/time/chronic/tags/Ordinal.java | 48 + .../xbib/time/chronic/tags/OrdinalDay.java | 30 + .../org/xbib/time/chronic/tags/Pointer.java | 61 + .../org/xbib/time/chronic/tags/Scalar.java | 103 + .../org/xbib/time/chronic/tags/ScalarDay.java | 32 + .../xbib/time/chronic/tags/ScalarMonth.java | 32 + .../xbib/time/chronic/tags/ScalarYear.java | 37 + .../org/xbib/time/chronic/tags/Separator.java | 51 + .../xbib/time/chronic/tags/SeparatorAt.java | 36 + .../time/chronic/tags/SeparatorComma.java | 37 + .../xbib/time/chronic/tags/SeparatorIn.java | 36 + .../chronic/tags/SeparatorSlashOrDash.java | 38 + .../org/xbib/time/chronic/tags/StringTag.java | 10 + .../java/org/xbib/time/chronic/tags/Tag.java | 36 + .../org/xbib/time/chronic/tags/TimeZone.java | 46 + .../xbib/time/chronic/tags/package-info.java | 4 + .../org/xbib/time/format/FormatUtils.java | 430 ++++ .../org/xbib/time/format/ISOPeriodFormat.java | 190 ++ .../org/xbib/time/format/PeriodAmount.java | 41 + .../org/xbib/time/format/PeriodFormat.java | 374 ++++ .../org/xbib/time/format/PeriodFormatter.java | 257 +++ .../time/format/PeriodFormatterBuilder.java | 1778 +++++++++++++++++ .../org/xbib/time/format/PeriodParser.java | 32 + .../org/xbib/time/format/PeriodPrinter.java | 55 + .../org/xbib/time/format/package-info.java | 4 + .../org/xbib/time/pretty/LocaleAware.java | 18 + .../java/org/xbib/time/pretty/PrettyTime.java | 468 +++++ .../xbib/time/pretty/SimpleTimeFormat.java | 189 ++ .../java/org/xbib/time/pretty/TimeFormat.java | 40 + .../xbib/time/pretty/TimeFormatProvider.java | 16 + .../java/org/xbib/time/pretty/TimeUnit.java | 32 + .../xbib/time/pretty/TimeUnitQuantity.java | 56 + .../time/pretty/i18n/MapResourceBundle.java | 102 + .../org/xbib/time/pretty/i18n/Resources.java | 111 + .../time/pretty/i18n/ResourcesTimeFormat.java | 85 + .../time/pretty/i18n/ResourcesTimeUnit.java | 36 + .../xbib/time/pretty/i18n/Resources_ar.java | 110 + .../xbib/time/pretty/i18n/Resources_bg.java | 112 ++ .../xbib/time/pretty/i18n/Resources_ca.java | 108 + .../xbib/time/pretty/i18n/Resources_cs.java | 341 ++++ .../xbib/time/pretty/i18n/Resources_da.java | 106 + .../xbib/time/pretty/i18n/Resources_de.java | 111 + .../xbib/time/pretty/i18n/Resources_en.java | 107 + .../xbib/time/pretty/i18n/Resources_es.java | 107 + .../xbib/time/pretty/i18n/Resources_et.java | 108 + .../xbib/time/pretty/i18n/Resources_fa.java | 107 + .../xbib/time/pretty/i18n/Resources_fi.java | 246 +++ .../xbib/time/pretty/i18n/Resources_fr.java | 107 + .../xbib/time/pretty/i18n/Resources_hi.java | 108 + .../xbib/time/pretty/i18n/Resources_hr.java | 107 + .../xbib/time/pretty/i18n/Resources_hu.java | 107 + .../xbib/time/pretty/i18n/Resources_in.java | 109 + .../xbib/time/pretty/i18n/Resources_it.java | 110 + .../xbib/time/pretty/i18n/Resources_ja.java | 107 + .../xbib/time/pretty/i18n/Resources_ko.java | 107 + .../xbib/time/pretty/i18n/Resources_nl.java | 107 + .../xbib/time/pretty/i18n/Resources_no.java | 107 + .../xbib/time/pretty/i18n/Resources_pl.java | 107 + .../xbib/time/pretty/i18n/Resources_pt.java | 107 + .../xbib/time/pretty/i18n/Resources_ro.java | 106 + .../xbib/time/pretty/i18n/Resources_ru.java | 163 ++ .../xbib/time/pretty/i18n/Resources_sl.java | 107 + .../xbib/time/pretty/i18n/Resources_sv.java | 107 + .../xbib/time/pretty/i18n/Resources_tr.java | 107 + .../xbib/time/pretty/i18n/Resources_ua.java | 162 ++ .../xbib/time/pretty/i18n/Resources_vi.java | 107 + .../xbib/time/pretty/i18n/Resources_zh.java | 107 + .../time/pretty/i18n/Resources_zh_HK.java | 107 + .../time/pretty/i18n/Resources_zh_TW.java | 107 + .../xbib/time/pretty/i18n/package-info.java | 4 + .../org/xbib/time/pretty/package-info.java | 4 + .../org/xbib/time/pretty/units/Century.java | 19 + .../java/org/xbib/time/pretty/units/Day.java | 20 + .../org/xbib/time/pretty/units/Decade.java | 20 + .../java/org/xbib/time/pretty/units/Hour.java | 20 + .../org/xbib/time/pretty/units/JustNow.java | 20 + .../xbib/time/pretty/units/Millennium.java | 20 + .../xbib/time/pretty/units/Millisecond.java | 20 + .../org/xbib/time/pretty/units/Minute.java | 20 + .../org/xbib/time/pretty/units/Month.java | 20 + .../org/xbib/time/pretty/units/Second.java | 20 + .../time/pretty/units/TimeUnitComparator.java | 20 + .../java/org/xbib/time/pretty/units/Week.java | 20 + .../java/org/xbib/time/pretty/units/Year.java | 20 + .../xbib/time/pretty/units/package-info.java | 4 + .../java/org/xbib/time/FormatterTest.java | 27 + .../org/xbib/time/chronic/ChronicTest.java | 86 + .../org/xbib/time/chronic/HandlerTest.java | 118 ++ .../org/xbib/time/chronic/NumerizerTest.java | 57 + .../org/xbib/time/chronic/ParserTest.java | 687 +++++++ .../time/chronic/RepeaterDayNameTest.java | 64 + .../time/chronic/RepeaterFortnightTest.java | 90 + .../xbib/time/chronic/RepeaterHourTest.java | 88 + .../time/chronic/RepeaterMonthNameTest.java | 65 + .../xbib/time/chronic/RepeaterMonthTest.java | 44 + .../xbib/time/chronic/RepeaterTimeTest.java | 94 + .../xbib/time/chronic/RepeaterWeekTest.java | 85 + .../time/chronic/RepeaterWeekendTest.java | 92 + .../xbib/time/chronic/RepeaterYearTest.java | 86 + .../java/org/xbib/time/chronic/SpanTest.java | 31 + .../java/org/xbib/time/chronic/TokenTest.java | 25 + .../time/chronic/handlers/DummyHandler.java | 16 + .../format/PeriodFormatterBuilderTest.java | 24 + .../pretty/PrettyTimeAPIManipulationTest.java | 90 + .../time/pretty/PrettyTimeI18n_AR_Test.java | 250 +++ .../time/pretty/PrettyTimeI18n_BG_Test.java | 224 +++ .../time/pretty/PrettyTimeI18n_CA_Test.java | 233 +++ .../time/pretty/PrettyTimeI18n_CS_Test.java | 227 +++ .../time/pretty/PrettyTimeI18n_DA_Test.java | 155 ++ .../time/pretty/PrettyTimeI18n_ET_Test.java | 336 ++++ .../time/pretty/PrettyTimeI18n_FA_Test.java | 239 +++ .../time/pretty/PrettyTimeI18n_FI_Test.java | 336 ++++ .../time/pretty/PrettyTimeI18n_FR_Test.java | 67 + .../time/pretty/PrettyTimeI18n_IT_Test.java | 336 ++++ .../time/pretty/PrettyTimeI18n_KO_Test.java | 236 +++ .../time/pretty/PrettyTimeI18n_NL_Test.java | 162 ++ .../time/pretty/PrettyTimeI18n_NO_Test.java | 163 ++ .../time/pretty/PrettyTimeI18n_RU_Test.java | 171 ++ .../time/pretty/PrettyTimeI18n_SV_Test.java | 164 ++ .../xbib/time/pretty/PrettyTimeI18n_Test.java | 130 ++ .../time/pretty/PrettyTimeI18n_UA_Test.java | 194 ++ .../pretty/PrettyTimeI18n_hi_IN_Test.java | 248 +++ .../pretty/PrettyTimeI18n_in_ID_Test.java | 247 +++ .../pretty/PrettyTimeI18n_zh_TW_Test.java | 190 ++ .../pretty/PrettyTimeLocaleFallbackTest.java | 38 + .../time/pretty/PrettyTimeNoSignTest.java | 28 + .../org/xbib/time/pretty/PrettyTimeTest.java | 242 +++ .../PrettyTimeUnitConfigurationTest.java | 59 + .../time/pretty/SimpleTimeFormatTest.java | 52 + .../xbib/time/pretty/i18n/Resources_xx.java | 53 + .../xbib/time/pretty/i18n/Resources_yy.java | 111 + ...impleTimeFormatTimeQuantifiedNameTest.java | 85 + .../pretty/i18n/TimeFormatProviderTest.java | 29 + .../pretty/units/TimeUnitComparatorTest.java | 15 + src/test/resources/log4j2.xml | 13 + 199 files changed, 20184 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.txt create mode 100644 build.gradle create mode 100644 config/checkstyle/checkstyle.xml create mode 100644 gradle/ext.gradle create mode 100644 gradle/publish.gradle create mode 100644 gradle/sonarqube.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 settings.gradle create mode 100644 src/main/java/org/xbib/time/chronic/Chronic.java create mode 100644 src/main/java/org/xbib/time/chronic/Options.java create mode 100644 src/main/java/org/xbib/time/chronic/Range.java create mode 100644 src/main/java/org/xbib/time/chronic/Span.java create mode 100644 src/main/java/org/xbib/time/chronic/Tick.java create mode 100644 src/main/java/org/xbib/time/chronic/Token.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/Handler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/HandlerPattern.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/HandlerTypePattern.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/IHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/MDHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/ORGRHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/ORRHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/ORSRHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/PSRHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/RGRHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/RHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/RdnRmnSdTTzSyHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/RmnOdHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/RmnSdHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/RmnSdSyHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/RmnSyHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/SRPAHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/SRPHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/SdRmnSyHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/SdSmSyHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/SmSdHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/SmSdSyHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/SmSyHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/SySmSdHandler.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/TagPattern.java create mode 100644 src/main/java/org/xbib/time/chronic/handlers/package-info.java create mode 100644 src/main/java/org/xbib/time/chronic/numerizer/Numerizer.java create mode 100644 src/main/java/org/xbib/time/chronic/numerizer/package-info.java create mode 100644 src/main/java/org/xbib/time/chronic/package-info.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/EnumRepeaterDayPortion.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/IntegerRepeaterDayPortion.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/Repeater.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/RepeaterDay.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/RepeaterDayName.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/RepeaterDayPortion.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/RepeaterFortnight.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/RepeaterHour.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/RepeaterMinute.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/RepeaterMonth.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/RepeaterMonthName.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/RepeaterSecond.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/RepeaterTime.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/RepeaterUnit.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/RepeaterWeek.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/RepeaterWeekend.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/RepeaterYear.java create mode 100644 src/main/java/org/xbib/time/chronic/repeaters/package-info.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/Grabber.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/Ordinal.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/OrdinalDay.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/Pointer.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/Scalar.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/ScalarDay.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/ScalarMonth.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/ScalarYear.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/Separator.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/SeparatorAt.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/SeparatorComma.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/SeparatorIn.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/SeparatorSlashOrDash.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/StringTag.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/Tag.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/TimeZone.java create mode 100644 src/main/java/org/xbib/time/chronic/tags/package-info.java create mode 100644 src/main/java/org/xbib/time/format/FormatUtils.java create mode 100644 src/main/java/org/xbib/time/format/ISOPeriodFormat.java create mode 100644 src/main/java/org/xbib/time/format/PeriodAmount.java create mode 100644 src/main/java/org/xbib/time/format/PeriodFormat.java create mode 100644 src/main/java/org/xbib/time/format/PeriodFormatter.java create mode 100644 src/main/java/org/xbib/time/format/PeriodFormatterBuilder.java create mode 100644 src/main/java/org/xbib/time/format/PeriodParser.java create mode 100644 src/main/java/org/xbib/time/format/PeriodPrinter.java create mode 100644 src/main/java/org/xbib/time/format/package-info.java create mode 100644 src/main/java/org/xbib/time/pretty/LocaleAware.java create mode 100644 src/main/java/org/xbib/time/pretty/PrettyTime.java create mode 100644 src/main/java/org/xbib/time/pretty/SimpleTimeFormat.java create mode 100644 src/main/java/org/xbib/time/pretty/TimeFormat.java create mode 100644 src/main/java/org/xbib/time/pretty/TimeFormatProvider.java create mode 100644 src/main/java/org/xbib/time/pretty/TimeUnit.java create mode 100644 src/main/java/org/xbib/time/pretty/TimeUnitQuantity.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/MapResourceBundle.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/ResourcesTimeFormat.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/ResourcesTimeUnit.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_ar.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_bg.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_ca.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_cs.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_da.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_de.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_en.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_es.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_et.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_fa.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_fi.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_fr.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_hi.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_hr.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_hu.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_in.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_it.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_ja.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_ko.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_nl.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_no.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_pl.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_pt.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_ro.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_ru.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_sl.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_sv.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_tr.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_ua.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_vi.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_zh.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_zh_HK.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/Resources_zh_TW.java create mode 100644 src/main/java/org/xbib/time/pretty/i18n/package-info.java create mode 100644 src/main/java/org/xbib/time/pretty/package-info.java create mode 100644 src/main/java/org/xbib/time/pretty/units/Century.java create mode 100644 src/main/java/org/xbib/time/pretty/units/Day.java create mode 100644 src/main/java/org/xbib/time/pretty/units/Decade.java create mode 100644 src/main/java/org/xbib/time/pretty/units/Hour.java create mode 100644 src/main/java/org/xbib/time/pretty/units/JustNow.java create mode 100644 src/main/java/org/xbib/time/pretty/units/Millennium.java create mode 100644 src/main/java/org/xbib/time/pretty/units/Millisecond.java create mode 100644 src/main/java/org/xbib/time/pretty/units/Minute.java create mode 100644 src/main/java/org/xbib/time/pretty/units/Month.java create mode 100644 src/main/java/org/xbib/time/pretty/units/Second.java create mode 100644 src/main/java/org/xbib/time/pretty/units/TimeUnitComparator.java create mode 100644 src/main/java/org/xbib/time/pretty/units/Week.java create mode 100644 src/main/java/org/xbib/time/pretty/units/Year.java create mode 100644 src/main/java/org/xbib/time/pretty/units/package-info.java create mode 100644 src/test/java/org/xbib/time/FormatterTest.java create mode 100644 src/test/java/org/xbib/time/chronic/ChronicTest.java create mode 100644 src/test/java/org/xbib/time/chronic/HandlerTest.java create mode 100644 src/test/java/org/xbib/time/chronic/NumerizerTest.java create mode 100644 src/test/java/org/xbib/time/chronic/ParserTest.java create mode 100644 src/test/java/org/xbib/time/chronic/RepeaterDayNameTest.java create mode 100644 src/test/java/org/xbib/time/chronic/RepeaterFortnightTest.java create mode 100644 src/test/java/org/xbib/time/chronic/RepeaterHourTest.java create mode 100644 src/test/java/org/xbib/time/chronic/RepeaterMonthNameTest.java create mode 100644 src/test/java/org/xbib/time/chronic/RepeaterMonthTest.java create mode 100644 src/test/java/org/xbib/time/chronic/RepeaterTimeTest.java create mode 100644 src/test/java/org/xbib/time/chronic/RepeaterWeekTest.java create mode 100644 src/test/java/org/xbib/time/chronic/RepeaterWeekendTest.java create mode 100644 src/test/java/org/xbib/time/chronic/RepeaterYearTest.java create mode 100644 src/test/java/org/xbib/time/chronic/SpanTest.java create mode 100644 src/test/java/org/xbib/time/chronic/TokenTest.java create mode 100644 src/test/java/org/xbib/time/chronic/handlers/DummyHandler.java create mode 100644 src/test/java/org/xbib/time/format/PeriodFormatterBuilderTest.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeAPIManipulationTest.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_AR_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_BG_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_CA_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_CS_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_DA_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_ET_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FA_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FI_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_FR_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_IT_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_KO_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_NL_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_NO_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_RU_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_SV_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_UA_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_hi_IN_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_in_ID_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeI18n_zh_TW_Test.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeLocaleFallbackTest.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeNoSignTest.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeTest.java create mode 100644 src/test/java/org/xbib/time/pretty/PrettyTimeUnitConfigurationTest.java create mode 100644 src/test/java/org/xbib/time/pretty/SimpleTimeFormatTest.java create mode 100644 src/test/java/org/xbib/time/pretty/i18n/Resources_xx.java create mode 100644 src/test/java/org/xbib/time/pretty/i18n/Resources_yy.java create mode 100644 src/test/java/org/xbib/time/pretty/i18n/SimpleTimeFormatTimeQuantifiedNameTest.java create mode 100644 src/test/java/org/xbib/time/pretty/i18n/TimeFormatProviderTest.java create mode 100644 src/test/java/org/xbib/time/pretty/units/TimeUnitComparatorTest.java create mode 100644 src/test/resources/log4j2.xml 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 0000000000000000000000000000000000000000..6ffa237849ef3607e39c3b334a92a65367962071 GIT binary patch literal 52928 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: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