├── .github └── workflows │ └── android.yml ├── .gitignore ├── README.md ├── androiddatetimetextprovider ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── sergiandreplace │ │ └── androiddatetimetextprovider │ │ ├── DayOfWeekFormattingTest.java │ │ └── MonthFormattingTest.java │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── sergiandreplace │ └── androiddatetimetextprovider │ └── AndroidDateTimeTextProvider.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: set up JDK 1.8 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 1.8 16 | - name: Build with Gradle 17 | run: ./gradlew build 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea/ 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidDateTimeTextProvider 2 | 3 | A solution for day and month formatting for languages with standalone configuration for ThreeTenBP for Android 4 | 5 | ## tl;dr 6 | 7 | Do you need to format dates to locales with specific standalone configurations for Month or DayOfWeek like catalan, polish, finnish, or others? This is the solution. 8 | 9 | ## What is this? 10 | 11 | I've been using [ThreeTenBp](https://github.com/ThreeTen/threetenbp) for Android projects for a long time. It's a great project that allows me to use Java 8 date/time framework without any problem. 12 | 13 | But, recently I found an issue in an application I had to translate into catalan. Basically: 14 | 15 | - In catalan, the preposition used in front of month for dates changes depending on the month (8 *de* febrer vs. 8 *d'* abril) 16 | - In order to fix this, the `MMMM` pattern includes the preposition (so, `d MMMM` will generate `8 de febrer` or `8 d'abril` as the `de` and `d'` are part of the month name. 17 | - What happens when you need to generate just the name of the month without preposition? That's when `LLLL` comes to the rescue. 18 | - Theorically, Java supports `LLLL` in date formatting. But not exactly. For languages like catalan `LLLL` and `MMMM` generate exactly the same output. 19 | - The worst part, is that Android does it right, and ye olde `SimpleDateFormat` returns diferent month names for `LLLL` and `MMMM`. Also, `LLLL` vs `MMMM` is well documented in the `SimpleDateFormat` javadoc (even using catalan as example) but no mention about it in `DateTimeFormatter` 20 | 21 | Then I started to investigate, I've spent hours looking at repos, documentation and standards, but basically: 22 | 23 | - As android is using the right standards, the information is there. But, as ThreeTenBP mimics Java behaviour, Java does not expect this info to be there. 24 | - I've claimed about it in this issue (https://github.com/ThreeTen/threetenbp/issues/55) where other users were having the same problems in other languages. 25 | - @sschaap linked to his own answer in other issue with a coherent explanation of what's going on (thanks a lot!) 26 | - Then I saw a link to this issue in another library: https://github.com/gabrielittner/lazythreetenbp/pull/11 27 | - Here @pamalyshev offers a solution in a PR for Android. But the answer of the library author is quite right, do not force it, as is not following the idea of ThreeTenBp (mimic Java 8), but offer as a separate library/solution. 28 | - And then the discussion is dead. @pamalyshev cloned the repo with the solution, but it's never published as a library (or I didn't see it). 29 | - Then I decided to publish this solution as a standalone solution. 30 | 31 | Keep in mind I'm simplifying the investigation process, as it's quite long and I'm sure not many of you look at huge xml of language configurations with the same excitement than me. 32 | 33 | Basically, this solution is not created by me, it's the collected effort of different individuals and put it together for your convenience. 34 | 35 | ## How to use it? 36 | 37 | You need to include three libraries in your app. 38 | 39 | - ThreeTenBP (https://github.com/ThreeTen/threetenbp). The original backport of the JSR-310. 40 | - Lazythreetenbp (https://github.com/gabrielittner/lazythreetenbp). A lazy loading ZoneRuleProvider for ThreeTenBp. This is used in Android as the zones information should be loaded before using ThreeTenBp and it could be a noticeable long time for an Android app startup. This library implements a lazy loading mechanism for that information. 41 | - This cute small library to fix the issue. 42 | 43 | So, add to your gradle: 44 | 45 | 1. The jitpack.io as repository. This is a small library and it's the fastest way of publishing it. 46 | 47 | ``` 48 | allprojects { 49 | repositories { 50 | ... 51 | maven { url 'https://jitpack.io' } 52 | } 53 | } 54 | ``` 55 | 56 | 2. All the dependencies needed 57 | 58 | ``` 59 | implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.7.0" 60 | implementation "org.threeten:threetenbp:1.4.0:no-tzdb" 61 | implementation 'com.github.sergiandreplace:androiddatetimetextprovider:1.0.0' 62 | ``` 63 | 64 | 3. And now you can use both LazyThreeTenBp and AndroidDateTimeTextProvider in your init part (tipically in you Application class): 65 | ``` 66 | LazyThreeTen.init(context); 67 | DateTimeTextProvider.setInitializer(AndroidDateTimeTextProvider()); 68 | ``` 69 | 70 | And that's it! 71 | 72 | ## Testing 73 | 74 | If you want to test that it works, there is a couple of test files that will tests the formatting for Months and Days of week for Catalan, Finnish, Polish and Russian. 75 | 76 | If there is another language that could benefit from this solution and I'm not aware, feel free to open an isse to check it, or if you feel extra proactive, open a PR adding it to the tests. 77 | 78 | 79 | ## The real heroes 80 | 81 | As I said, I've just joined other people work, so this people is the one to thank for tihs solution: 82 | 83 | - @jodastephen and the rest of people who worked in the ThreeTenBP project. A real life-saver! 84 | - @gabrielittner for creating LazyThreeTenBp 85 | - @sschaap for his great explanations and patience answering 86 | - @pamalyshev for creating the solution and doing the changes needed to ThreeTenBp to apply it. 87 | 88 | ## Disclaimer 89 | 90 | All the information I wrote here is my understanding of the problem the solution and how the involved people has participated. If there is something wrong or misleading do not hesitate to say so and I will apply the right corrections. 91 | 92 | Have fun! 93 | -------------------------------------------------------------------------------- /androiddatetimetextprovider/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /androiddatetimetextprovider/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | group='com.github.sergiandreplace' 4 | 5 | android { 6 | compileSdkVersion 29 7 | 8 | defaultConfig { 9 | minSdkVersion 16 10 | targetSdkVersion 29 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles 'consumer-rules.pro' 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | api 'com.gabrielittner.threetenbp:lazythreetenbp:0.7.0' 28 | api 'org.threeten:threetenbp:1.4.0:no-tzdb' 29 | testImplementation 'junit:junit:4.12' 30 | androidTestImplementation 'com.gabrielittner.threetenbp:lazythreetenbp:0.7.0' 31 | androidTestImplementation 'org.threeten:threetenbp:1.4.0:no-tzdb' 32 | androidTestImplementation 'androidx.test:runner:1.2.0' 33 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 34 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 35 | } 36 | -------------------------------------------------------------------------------- /androiddatetimetextprovider/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/androiddatetimetextprovider/d8cf719a62597f7cc28076707daf752ee905b292/androiddatetimetextprovider/consumer-rules.pro -------------------------------------------------------------------------------- /androiddatetimetextprovider/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /androiddatetimetextprovider/src/androidTest/java/com/sergiandreplace/androiddatetimetextprovider/DayOfWeekFormattingTest.java: -------------------------------------------------------------------------------- 1 | package com.sergiandreplace.androiddatetimetextprovider; 2 | 3 | import android.os.Build; 4 | import android.util.Log; 5 | import androidx.test.ext.junit.runners.AndroidJUnit4; 6 | import androidx.test.platform.app.InstrumentationRegistry; 7 | import com.gabrielittner.threetenbp.LazyThreeTen; 8 | import java.text.SimpleDateFormat; 9 | import java.util.Calendar; 10 | import java.util.GregorianCalendar; 11 | import java.util.Locale; 12 | import org.junit.BeforeClass; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.threeten.bp.DayOfWeek; 16 | import org.threeten.bp.LocalDate; 17 | import org.threeten.bp.format.DateTimeFormatter; 18 | import org.threeten.bp.format.DateTimeTextProvider; 19 | import org.threeten.bp.temporal.TemporalAdjusters; 20 | 21 | import static org.junit.Assert.assertEquals; 22 | import static org.junit.Assume.assumeTrue; 23 | 24 | @RunWith(AndroidJUnit4.class) 25 | public class DayOfWeekFormattingTest { 26 | 27 | private static Object[][] data; 28 | 29 | @BeforeClass 30 | public static void init() { 31 | LazyThreeTen.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); 32 | DateTimeTextProvider.setInitializer(new AndroidDateTimeTextProvider()); 33 | 34 | //Not using Parameterized tests because they are slower, 35 | //and if for one month test fails then usually others fail as well. 36 | data = new Object[][] { 37 | { Calendar.SUNDAY, DayOfWeek.SUNDAY }, 38 | { Calendar.MONDAY, DayOfWeek.MONDAY }, 39 | { Calendar.TUESDAY, DayOfWeek.TUESDAY }, 40 | { Calendar.WEDNESDAY, DayOfWeek.WEDNESDAY }, 41 | { Calendar.THURSDAY, DayOfWeek.THURSDAY }, 42 | { Calendar.FRIDAY, DayOfWeek.FRIDAY }, 43 | { Calendar.SATURDAY, DayOfWeek.SATURDAY }, 44 | }; 45 | } 46 | 47 | private GregorianCalendar getCalendar(int calendarDay) { 48 | GregorianCalendar calendar = new GregorianCalendar(1970, 0, 1); 49 | //Force recalculation. 50 | calendar.getTime(); 51 | calendar.set(Calendar.DAY_OF_WEEK, calendarDay); 52 | return calendar; 53 | } 54 | 55 | private LocalDate getLocalDate(DayOfWeek dayOfWeek) { 56 | return LocalDate.of(1970, 1, 1).with(TemporalAdjusters.nextOrSame(dayOfWeek)); 57 | } 58 | 59 | private void testPattern(String pattern, Locale locale) { 60 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern, locale); 61 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern, locale); 62 | 63 | Log.d("Month Formatting Test", "Locale: " + locale.toString()); 64 | 65 | for (Object[] entry : data) { 66 | int calendarDay = (Integer) entry[0]; 67 | DayOfWeek dayOfWeek = (DayOfWeek) entry[1]; 68 | 69 | String javaText = simpleDateFormat.format(getCalendar(calendarDay).getTime()); 70 | String threeTenText = getLocalDate(dayOfWeek).format(formatter); 71 | 72 | Log.d("Month Formatting Test", " Pattern: " + pattern); 73 | Log.d("Month Formatting Test", " DayOfWeek: " + dayOfWeek); 74 | Log.d("Month Formatting Test", " Java: " + javaText); 75 | Log.d("Month Formatting Test", " ThreeTenBp: " + threeTenText); 76 | 77 | assertEquals(javaText, threeTenText); 78 | } 79 | } 80 | 81 | private void assumeNarrowStyleSupported() { 82 | assumeTrue(Build.VERSION.SDK_INT >= 18); 83 | } 84 | 85 | public void testFull(Locale locale) { 86 | testPattern("EEEE", locale); 87 | } 88 | 89 | public void testShort(Locale locale) { 90 | testPattern("EEE", locale); 91 | } 92 | 93 | public void testNarrow(Locale locale) { 94 | assumeNarrowStyleSupported(); 95 | testPattern("EEEEE", locale); 96 | } 97 | 98 | public void testFullStandalone(Locale locale) { 99 | testPattern("cccc", locale); 100 | } 101 | 102 | public void testShortStandalone(Locale locale) { 103 | testPattern("ccc", locale); 104 | } 105 | 106 | public void testNarrowStandalone(Locale locale) { 107 | assumeNarrowStyleSupported(); 108 | testPattern("ccccc", locale); 109 | } 110 | 111 | public void testLocale(Locale locale) { 112 | testFull(locale); 113 | testShort(locale); 114 | testNarrow(locale); 115 | testFullStandalone(locale); 116 | testShortStandalone(locale); 117 | testNarrowStandalone(locale); 118 | } 119 | 120 | @Test 121 | public void testLocalesWithStandaloneDaysOfWeek() { 122 | testLocale(new Locale("ru", "RU")); 123 | testLocale(new Locale("pl", "PL")); 124 | testLocale(new Locale("fi", "FI")); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /androiddatetimetextprovider/src/androidTest/java/com/sergiandreplace/androiddatetimetextprovider/MonthFormattingTest.java: -------------------------------------------------------------------------------- 1 | package com.sergiandreplace.androiddatetimetextprovider; 2 | 3 | import android.os.Build; 4 | import android.util.Log; 5 | import androidx.test.ext.junit.runners.AndroidJUnit4; 6 | import androidx.test.platform.app.InstrumentationRegistry; 7 | import com.gabrielittner.threetenbp.LazyThreeTen; 8 | import java.text.SimpleDateFormat; 9 | import java.util.Calendar; 10 | import java.util.GregorianCalendar; 11 | import java.util.Locale; 12 | import org.junit.BeforeClass; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.threeten.bp.LocalDate; 16 | import org.threeten.bp.Month; 17 | import org.threeten.bp.format.DateTimeFormatter; 18 | import org.threeten.bp.format.DateTimeTextProvider; 19 | 20 | import static org.junit.Assert.assertEquals; 21 | import static org.junit.Assume.assumeTrue; 22 | 23 | @RunWith(AndroidJUnit4.class) 24 | public class MonthFormattingTest { 25 | 26 | private static Object[][] data; 27 | 28 | @BeforeClass 29 | public static void init() { 30 | LazyThreeTen.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); 31 | DateTimeTextProvider.setInitializer(new AndroidDateTimeTextProvider()); 32 | 33 | //Not using Parameterized tests because they are slower, 34 | //and if for one month test fails then usually others fail as well. 35 | data = new Object[][] { 36 | { Calendar.JANUARY, Month.JANUARY }, 37 | { Calendar.FEBRUARY, Month.FEBRUARY }, 38 | { Calendar.MARCH, Month.MARCH }, 39 | { Calendar.APRIL, Month.APRIL }, 40 | { Calendar.MAY, Month.MAY }, 41 | { Calendar.JUNE, Month.JUNE }, 42 | { Calendar.JULY, Month.JULY }, 43 | { Calendar.AUGUST, Month.AUGUST }, 44 | { Calendar.SEPTEMBER, Month.SEPTEMBER }, 45 | { Calendar.OCTOBER, Month.OCTOBER }, 46 | { Calendar.NOVEMBER, Month.NOVEMBER }, 47 | { Calendar.DECEMBER, Month.DECEMBER } 48 | }; 49 | } 50 | 51 | private GregorianCalendar getCalendar(int calendarMonth) { 52 | GregorianCalendar calendar = new GregorianCalendar(1970, 0, 1); 53 | calendar.set(Calendar.MONTH, calendarMonth); 54 | return calendar; 55 | } 56 | 57 | private LocalDate getLocalDate(Month month) { 58 | return LocalDate.of(1970, month, 1); 59 | } 60 | 61 | private void testPattern(String pattern, Locale locale) { 62 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern, locale); 63 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern, locale); 64 | 65 | Log.d("Month Formatting Test", "Locale: " + locale.toString()); 66 | 67 | for (Object[] entry : data) { 68 | int calendarMonth = (Integer) entry[0]; 69 | Month month = (Month) entry[1]; 70 | 71 | String javaText = simpleDateFormat.format(getCalendar(calendarMonth).getTime()); 72 | String threeTenText = getLocalDate(month).format(formatter); 73 | 74 | Log.d("Month Formatting Test", " Pattern: " + pattern); 75 | Log.d("Month Formatting Test", " Month: " + month); 76 | Log.d("Month Formatting Test", " Java: " + javaText); 77 | Log.d("Month Formatting Test", " ThreeTenBp: " + threeTenText); 78 | 79 | assertEquals(javaText, threeTenText); 80 | } 81 | } 82 | 83 | private void assumeNarrowStyleSupported() { 84 | assumeTrue(Build.VERSION.SDK_INT >= 18); 85 | } 86 | 87 | public void testFull(Locale locale) { 88 | testPattern("MMMM", locale); 89 | } 90 | 91 | public void testShort(Locale locale) { 92 | testPattern("MMM", locale); 93 | } 94 | 95 | public void testNarrow(Locale locale) { 96 | assumeNarrowStyleSupported(); 97 | testPattern("MMMMM", locale); 98 | } 99 | 100 | public void testFullStandalone(Locale locale) { 101 | testPattern("LLLL", locale); 102 | } 103 | 104 | public void testShortStandalone(Locale locale) { 105 | testPattern("LLL", locale); 106 | } 107 | 108 | public void testNarrowStandalone(Locale locale) { 109 | assumeNarrowStyleSupported(); 110 | testPattern("LLLLL", locale); 111 | } 112 | 113 | public void testLocale(Locale locale) { 114 | testFull(locale); 115 | testShort(locale); 116 | testNarrow(locale); 117 | testFullStandalone(locale); 118 | testShortStandalone(locale); 119 | testNarrowStandalone(locale); 120 | } 121 | 122 | @Test 123 | public void testLocalesWithStandaloneMonths() { 124 | testLocale(new Locale("ru", "RU")); 125 | testLocale(new Locale("ca", "ES")); 126 | testLocale(new Locale("pl", "PL")); 127 | testLocale(new Locale("fi", "FI")); 128 | } 129 | } -------------------------------------------------------------------------------- /androiddatetimetextprovider/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /androiddatetimetextprovider/src/main/java/com/sergiandreplace/androiddatetimetextprovider/AndroidDateTimeTextProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * * Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the documentation 14 | * and/or other materials provided with the distribution. 15 | * 16 | * * Neither the name of JSR-310 nor the names of its contributors 17 | * may be used to endorse or promote products derived from this software 18 | * without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | package com.sergiandreplace.androiddatetimetextprovider; 33 | 34 | import android.os.Build; 35 | import java.text.DateFormatSymbols; 36 | import java.text.SimpleDateFormat; 37 | import java.util.AbstractMap.SimpleImmutableEntry; 38 | import java.util.ArrayList; 39 | import java.util.Calendar; 40 | import java.util.Collections; 41 | import java.util.Comparator; 42 | import java.util.GregorianCalendar; 43 | import java.util.HashMap; 44 | import java.util.Iterator; 45 | import java.util.List; 46 | import java.util.Locale; 47 | import java.util.Map; 48 | import java.util.Map.Entry; 49 | import java.util.concurrent.ConcurrentHashMap; 50 | import java.util.concurrent.ConcurrentMap; 51 | import org.threeten.bp.format.DateTimeTextProvider; 52 | import org.threeten.bp.format.TextStyle; 53 | import org.threeten.bp.temporal.IsoFields; 54 | import org.threeten.bp.temporal.TemporalField; 55 | 56 | import static org.threeten.bp.temporal.ChronoField.AMPM_OF_DAY; 57 | import static org.threeten.bp.temporal.ChronoField.DAY_OF_WEEK; 58 | import static org.threeten.bp.temporal.ChronoField.ERA; 59 | import static org.threeten.bp.temporal.ChronoField.MONTH_OF_YEAR; 60 | 61 | /** 62 | * The Service Provider Implementation to obtain date-time text for a field. 63 | *

64 | * This implementation extracts data from {@link DateFormatSymbols} and {@link SimpleDateFormat}. 65 | *

66 | * This implementation is based on {@link org.threeten.bp.format.SimpleDateTimeTextProvider SimpleDateTimeTextProvider}, 67 | * but uses Android-specific features of {@link SimpleDateFormat} 68 | * to extract texts for STANDALONE and NARROW styles 69 | * for {@link org.threeten.bp.temporal.ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} 70 | * and {@link org.threeten.bp.temporal.ChronoField#DAY_OF_WEEK DAY_OF_WEEK}. 71 | * 72 | * Texts for other fields are fetched the same way as in 73 | * {@link org.threeten.bp.format.SimpleDateTimeTextProvider SimpleDateTimeTextProvider}. 74 | *

75 | * Note that texts for NARROW style can be extracted only on devices with Android 4.3 or higher. 76 | * On pre-Android 4.3 devices NARROW style texts are emulated 77 | * by returning only first character of FULL style text. 78 | */ 79 | final public class AndroidDateTimeTextProvider extends DateTimeTextProvider { 80 | 81 | /** Comparator. */ 82 | private static final Comparator> COMPARATOR = new Comparator>() { 83 | @Override 84 | public int compare(Entry obj1, Entry obj2) { 85 | return obj2.getKey().length() - obj1.getKey().length(); // longest to shortest 86 | } 87 | }; 88 | 89 | /** Cache. */ 90 | private final ConcurrentMap, Object> cache = 91 | new ConcurrentHashMap, Object>(16, 0.75f, 2); 92 | 93 | //----------------------------------------------------------------------- 94 | @Override 95 | public String getText(TemporalField field, long value, TextStyle style, Locale locale) { 96 | Object store = findStore(field, locale); 97 | if (store instanceof LocaleStore) { 98 | return ((LocaleStore) store).getText(value, style); 99 | } 100 | return null; 101 | } 102 | 103 | @Override 104 | public Iterator> getTextIterator(TemporalField field, TextStyle style, Locale locale) { 105 | Object store = findStore(field, locale); 106 | if (store instanceof LocaleStore) { 107 | return ((LocaleStore) store).getTextIterator(style); 108 | } 109 | return null; 110 | } 111 | 112 | //----------------------------------------------------------------------- 113 | private Object findStore(TemporalField field, Locale locale) { 114 | Entry key = createEntry(field, locale); 115 | Object store = cache.get(key); 116 | if (store == null) { 117 | store = createStore(field, locale); 118 | cache.putIfAbsent(key, store); 119 | store = cache.get(key); 120 | } 121 | return store; 122 | } 123 | 124 | private Object createStore(TemporalField field, Locale locale) { 125 | if (field == MONTH_OF_YEAR) { 126 | DateFormatSymbols oldSymbols = DateFormatSymbols.getInstance(locale); 127 | Map> styleMap = new HashMap>(); 128 | SimpleDateFormat dateFormat = new SimpleDateFormat("", locale); 129 | 130 | //Uses the same assumptions about months as SimpleDateTimeTextProvider. 131 | 132 | String[] array = oldSymbols.getMonths(); 133 | Map map = createMonthsMapFromSymbolsArray(array); 134 | styleMap.put(TextStyle.FULL, map); 135 | 136 | array = oldSymbols.getShortMonths(); 137 | map = createMonthsMapFromSymbolsArray(array); 138 | styleMap.put(TextStyle.SHORT, map); 139 | 140 | if (Build.VERSION.SDK_INT >= 18) { 141 | map = createMonthsMapFromPattern(dateFormat, "MMMMM"); 142 | styleMap.put(TextStyle.NARROW, map); 143 | map = createMonthsMapFromPattern(dateFormat, "LLLLL"); 144 | styleMap.put(TextStyle.NARROW_STANDALONE, map); 145 | } else { 146 | map = createNarrowMonthsMapFromPattern(dateFormat, "MMMM"); 147 | styleMap.put(TextStyle.NARROW, map); 148 | map = createNarrowMonthsMapFromPattern(dateFormat, "LLLL"); 149 | styleMap.put(TextStyle.NARROW_STANDALONE, map); 150 | } 151 | 152 | map = createMonthsMapFromPattern(dateFormat, "LLLL"); 153 | styleMap.put(TextStyle.FULL_STANDALONE, map); 154 | 155 | map = createMonthsMapFromPattern(dateFormat, "LLL"); 156 | styleMap.put(TextStyle.SHORT_STANDALONE, map); 157 | 158 | return createLocaleStore(styleMap); 159 | } 160 | if (field == DAY_OF_WEEK) { 161 | DateFormatSymbols oldSymbols = DateFormatSymbols.getInstance(locale); 162 | Map> styleMap = new HashMap>(); 163 | SimpleDateFormat dateFormat = new SimpleDateFormat("", locale); 164 | 165 | String[] array = oldSymbols.getWeekdays(); 166 | Map map = createDaysMapFromSymbolsArray(array); 167 | styleMap.put(TextStyle.FULL, map); 168 | 169 | array = oldSymbols.getShortWeekdays(); 170 | map = createDaysMapFromSymbolsArray(array); 171 | styleMap.put(TextStyle.SHORT, map); 172 | 173 | if (Build.VERSION.SDK_INT >= 18) { 174 | map = createDaysMapFromPattern(dateFormat, "EEEEE"); 175 | styleMap.put(TextStyle.NARROW, map); 176 | map = createDaysMapFromPattern(dateFormat, "ccccc"); 177 | styleMap.put(TextStyle.NARROW_STANDALONE, map); 178 | } else { 179 | map = createNarrowDaysMapFromPattern(dateFormat, "EEEE"); 180 | styleMap.put(TextStyle.NARROW, map); 181 | map = createNarrowDaysMapFromPattern(dateFormat, "cccc"); 182 | styleMap.put(TextStyle.NARROW_STANDALONE, map); 183 | } 184 | 185 | map = createDaysMapFromPattern(dateFormat, "cccc"); 186 | styleMap.put(TextStyle.FULL_STANDALONE, map); 187 | 188 | map = createDaysMapFromPattern(dateFormat, "ccc"); 189 | styleMap.put(TextStyle.SHORT_STANDALONE, map); 190 | 191 | return createLocaleStore(styleMap); 192 | } 193 | if (field == AMPM_OF_DAY) { 194 | DateFormatSymbols oldSymbols = DateFormatSymbols.getInstance(locale); 195 | Map> styleMap = new HashMap>(); 196 | String[] array = oldSymbols.getAmPmStrings(); 197 | Map map = new HashMap(); 198 | map.put(0L, array[Calendar.AM]); 199 | map.put(1L, array[Calendar.PM]); 200 | styleMap.put(TextStyle.FULL, map); 201 | styleMap.put(TextStyle.SHORT, map); // re-use, as we don't have different data 202 | return createLocaleStore(styleMap); 203 | } 204 | if (field == ERA) { 205 | DateFormatSymbols oldSymbols = DateFormatSymbols.getInstance(locale); 206 | Map> styleMap = new HashMap>(); 207 | String[] array = oldSymbols.getEras(); 208 | Map map = new HashMap(); 209 | map.put(0L, array[GregorianCalendar.BC]); 210 | map.put(1L, array[GregorianCalendar.AD]); 211 | styleMap.put(TextStyle.SHORT, map); 212 | if (locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) { 213 | map = new HashMap(); 214 | map.put(0L, "Before Christ"); 215 | map.put(1L, "Anno Domini"); 216 | styleMap.put(TextStyle.FULL, map); 217 | } else { 218 | // re-use, as we don't have different data 219 | styleMap.put(TextStyle.FULL, map); 220 | } 221 | map = new HashMap(); 222 | map.put(0L, array[GregorianCalendar.BC].substring(0, 1)); 223 | map.put(1L, array[GregorianCalendar.AD].substring(0, 1)); 224 | styleMap.put(TextStyle.NARROW, map); 225 | return createLocaleStore(styleMap); 226 | } 227 | // hard code English quarter text 228 | if (field == IsoFields.QUARTER_OF_YEAR) { 229 | Map> styleMap = new HashMap>(); 230 | Map map = new HashMap(); 231 | map.put(1L, "Q1"); 232 | map.put(2L, "Q2"); 233 | map.put(3L, "Q3"); 234 | map.put(4L, "Q4"); 235 | styleMap.put(TextStyle.SHORT, map); 236 | map = new HashMap(); 237 | map.put(1L, "1st quarter"); 238 | map.put(2L, "2nd quarter"); 239 | map.put(3L, "3rd quarter"); 240 | map.put(4L, "4th quarter"); 241 | styleMap.put(TextStyle.FULL, map); 242 | return createLocaleStore(styleMap); 243 | } 244 | return ""; // null marker for map 245 | } 246 | 247 | private Long calMonthToThreeTenMonth(int calMonth) { 248 | //Calendar months are from 0 (JANUARY) to 11 (DECEMBER) 249 | //ThreeTen months are from 1 (JANUARY) to 12 (DECEMBER) 250 | 251 | //Show boxing explicitly. 252 | //noinspection UnnecessaryBoxing 253 | return Long.valueOf(calMonth + 1); 254 | } 255 | 256 | private Long calDayToThreeTenDay(int calDay) { 257 | //Calendar days start from SUNDAY 258 | //ThreeTen days start from MONDAY 259 | //So 1 -> 7, 2 -> 1, ..., 7 -> 6 260 | 261 | //Show boxing explicitly. 262 | //noinspection UnnecessaryBoxing 263 | return Long.valueOf(((calDay + 5) % 7) + 1); 264 | } 265 | 266 | private Map createMonthsMapFromSymbolsArray(String[] array) { 267 | Map map = new HashMap(); 268 | for (int calMonth = Calendar.JANUARY; calMonth <= Calendar.DECEMBER; ++calMonth) { 269 | Long threeTenMonth = calMonthToThreeTenMonth(calMonth); 270 | map.put(threeTenMonth, array[calMonth]); 271 | } 272 | return map; 273 | } 274 | 275 | private Map createMonthsMapFromPattern( 276 | SimpleDateFormat dateFormat, String pattern) { 277 | dateFormat.applyPattern(pattern); 278 | 279 | Map map = new HashMap(); 280 | for (int calMonth = Calendar.JANUARY; calMonth <= Calendar.DECEMBER; ++calMonth) { 281 | Long threeTenMonth = calMonthToThreeTenMonth(calMonth); 282 | dateFormat.getCalendar().set(Calendar.MONTH, calMonth); 283 | String formattedMonth = dateFormat.format(dateFormat.getCalendar().getTime()); 284 | map.put(threeTenMonth, formattedMonth); 285 | } 286 | return map; 287 | } 288 | 289 | private Map createNarrowMonthsMapFromPattern( 290 | SimpleDateFormat dateFormat, String pattern) { 291 | dateFormat.applyPattern(pattern); 292 | 293 | Map map = new HashMap(); 294 | for (int calMonth = Calendar.JANUARY; calMonth <= Calendar.DECEMBER; ++calMonth) { 295 | Long threeTenMonth = calMonthToThreeTenMonth(calMonth); 296 | dateFormat.getCalendar().set(Calendar.MONTH, calMonth); 297 | String formattedMonth = dateFormat.format(dateFormat.getCalendar().getTime()); 298 | map.put(threeTenMonth, formattedMonth.substring(0, 1)); 299 | } 300 | return map; 301 | } 302 | 303 | private Map createDaysMapFromSymbolsArray(String[] array) { 304 | Map map = new HashMap(); 305 | for (int calDay = Calendar.SUNDAY; calDay <= Calendar.SATURDAY; ++calDay) { 306 | Long threeTenDay = calDayToThreeTenDay(calDay); 307 | map.put(threeTenDay, array[calDay]); 308 | } 309 | return map; 310 | } 311 | 312 | private Map createDaysMapFromPattern( 313 | SimpleDateFormat dateFormat, String pattern) { 314 | dateFormat.applyPattern(pattern); 315 | 316 | Map map = new HashMap(); 317 | for (int calDay = Calendar.SUNDAY; calDay <= Calendar.SATURDAY; ++calDay) { 318 | Long threeTenDay = calDayToThreeTenDay(calDay); 319 | dateFormat.getCalendar().set(Calendar.DAY_OF_WEEK, calDay); 320 | String formattedMonth = dateFormat.format(dateFormat.getCalendar().getTime()); 321 | map.put(threeTenDay, formattedMonth); 322 | } 323 | return map; 324 | } 325 | 326 | private Map createNarrowDaysMapFromPattern( 327 | SimpleDateFormat dateFormat, String pattern) { 328 | dateFormat.applyPattern(pattern); 329 | 330 | Map map = new HashMap(); 331 | for (int calDay = Calendar.SUNDAY; calDay <= Calendar.SATURDAY; ++calDay) { 332 | Long threeTenDay = calDayToThreeTenDay(calDay); 333 | dateFormat.getCalendar().set(Calendar.DAY_OF_WEEK, calDay); 334 | String formattedMonth = dateFormat.format(dateFormat.getCalendar().getTime()); 335 | map.put(threeTenDay, formattedMonth.substring(0, 1)); 336 | } 337 | return map; 338 | } 339 | 340 | //----------------------------------------------------------------------- 341 | 342 | /** 343 | * Helper method to create an immutable entry. 344 | * 345 | * @param text the text, not null 346 | * @param field the field, not null 347 | * @return the entry, not null 348 | */ 349 | private static Entry createEntry(A text, B field) { 350 | return new SimpleImmutableEntry(text, field); 351 | } 352 | 353 | //----------------------------------------------------------------------- 354 | private static LocaleStore createLocaleStore(Map> valueTextMap) { 355 | if (!valueTextMap.containsKey(TextStyle.FULL_STANDALONE)) { 356 | valueTextMap.put(TextStyle.FULL_STANDALONE, valueTextMap.get(TextStyle.FULL)); 357 | } 358 | 359 | if (!valueTextMap.containsKey(TextStyle.SHORT_STANDALONE)) { 360 | valueTextMap.put(TextStyle.SHORT_STANDALONE, valueTextMap.get(TextStyle.SHORT)); 361 | } 362 | 363 | if (valueTextMap.containsKey(TextStyle.NARROW) && valueTextMap.containsKey(TextStyle.NARROW_STANDALONE) == false) { 364 | valueTextMap.put(TextStyle.NARROW_STANDALONE, valueTextMap.get(TextStyle.NARROW)); 365 | } 366 | return new LocaleStore(valueTextMap); 367 | } 368 | 369 | /** 370 | * Stores the text for a single locale. 371 | *

372 | * Some fields have a textual representation, such as day-of-week or month-of-year. 373 | * These textual representations can be captured in this class for printing 374 | * and parsing. 375 | *

376 | * This class is immutable and thread-safe. 377 | */ 378 | static final class LocaleStore { 379 | /** 380 | * Map of value to text. 381 | */ 382 | private final Map> valueTextMap; 383 | /** 384 | * Parsable data. 385 | */ 386 | private final Map>> parsable; 387 | 388 | //----------------------------------------------------------------------- 389 | 390 | /** 391 | * Constructor. 392 | * 393 | * @param valueTextMap the map of values to text to store, assigned and not altered, not null 394 | */ 395 | LocaleStore(Map> valueTextMap) { 396 | this.valueTextMap = valueTextMap; 397 | Map>> map = new HashMap>>(); 398 | List> allList = new ArrayList>(); 399 | for (TextStyle style : valueTextMap.keySet()) { 400 | Map> reverse = new HashMap>(); 401 | for (Entry entry : valueTextMap.get(style).entrySet()) { 402 | if (reverse.put(entry.getValue(), createEntry(entry.getValue(), entry.getKey())) != null) { 403 | continue; // not parsable, try next style 404 | } 405 | } 406 | List> list = new ArrayList>(reverse.values()); 407 | Collections.sort(list, COMPARATOR); 408 | map.put(style, list); 409 | allList.addAll(list); 410 | map.put(null, allList); 411 | } 412 | Collections.sort(allList, COMPARATOR); 413 | this.parsable = map; 414 | } 415 | 416 | //----------------------------------------------------------------------- 417 | 418 | /** 419 | * Gets the text for the specified field value, locale and style 420 | * for the purpose of printing. 421 | * 422 | * @param value the value to get text for, not null 423 | * @param style the style to get text for, not null 424 | * @return the text for the field value, null if no text found 425 | */ 426 | String getText(long value, TextStyle style) { 427 | Map map = valueTextMap.get(style); 428 | return map != null ? map.get(value) : null; 429 | } 430 | 431 | /** 432 | * Gets an iterator of text to field for the specified style for the purpose of parsing. 433 | *

434 | * The iterator must be returned in order from the longest text to the shortest. 435 | * 436 | * @param style the style to get text for, null for all parsable text 437 | * @return the iterator of text to field pairs, in order from longest text to shortest text, 438 | * null if the style is not parsable 439 | */ 440 | Iterator> getTextIterator(TextStyle style) { 441 | List> list = parsable.get(style); 442 | return list != null ? list.iterator() : null; 443 | } 444 | } 445 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.50' 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.5.0' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/androiddatetimetextprovider/d8cf719a62597f7cc28076707daf752ee905b292/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Sep 07 13:14:34 CEST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':androiddatetimetextprovider' 2 | rootProject.name='AndroidDateTimeTextProvider' 3 | --------------------------------------------------------------------------------