├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── qaplug_profiles.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── pv │ │ └── sample │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── pv │ │ │ └── sample │ │ │ ├── AwesomeAdapter.java │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ └── suggestion_row.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── pv │ └── sample │ └── ExampleUnitTest.java ├── build.gradle ├── demo └── demo.gif ├── gradle-mvn-push.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── pv │ │ └── datetimeseer │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── pv │ │ │ └── datetimeseer │ │ │ ├── Config.java │ │ │ ├── Constants.java │ │ │ ├── DOWSuggestionHandler.java │ │ │ ├── DateSuggestionHandler.java │ │ │ ├── DateTimeUtils.java │ │ │ ├── InitialSuggestionHandler.java │ │ │ ├── NumberRelativeTimeSuggestionHandler.java │ │ │ ├── RelativeTimeSuggestionHandler.java │ │ │ ├── SeerFilter.java │ │ │ ├── SuggestionHandler.java │ │ │ ├── SuggestionRow.java │ │ │ ├── SuggestionValue.java │ │ │ ├── TODSuggestionHandler.java │ │ │ └── TimeSuggestionHandler.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── pv │ └── datetimeseer │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | Android 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 62 | 63 | 65 | 66 | 67 | 68 | 69 | 70 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/qaplug_profiles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 292 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Pratyush Verma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DateTimeSeer 2 | ### A painless way to pick future time 3 | 4 | DateTimeSeer is an android seer who gets visions of the date and time you are thinking. 5 | 6 | It tells you what you might be thinking and helps in what modern people call as autocompletion. Unfortunately, he currently knows only english. 7 | 8 | ![Demo](demo/demo.gif) 9 | 10 | **Gradle** 11 | 12 | Add the seer to `build.gradle` and you are good to go, 13 | 14 | ```groovy 15 | dependencies { 16 | compile 'com.pv:datetimeseer:1.0.0' 17 | } 18 | ``` 19 | 20 | The library exposes `SeerFilter` which extends android's `Filter` class and so can be hooked to anything which implements `Filterable`. 21 | 22 | Use the `ConfigBuilder` to provide Date/Time formats. 23 | 24 | The sample app here demonstrate the usage of the `Filter` with the `AutoCompleteTextView`. 25 | 26 | 27 | ### Contributing 28 | 29 | For other languages support, 30 | 31 | - Checkout the `dev` branch 32 | - Implement the changes for the language in [this package](https://github.com/p-v/DateTimeSeer/tree/dev/library/src/main/java/com/pv/datetimeseer/parser/handler). Check the [english language implementation](https://github.com/p-v/DateTimeSeer/tree/dev/library/src/main/java/com/pv/datetimeseer/parser/handler/english) for reference. 33 | - Add the `strings.xml` file for the language 34 | - Add the language to `Config.java` 35 | - Update `getLocaleFromLanguage` in `DateTimeUtils.java` 36 | - Initialize the handlers created in `SeerParserInitializer.java` for the language 37 | 38 | Feel free to create an issue in case of any implementation issues. Or email me at the address present on my profile. 39 | 40 | ### TODOS 41 | 42 | - Add more documentation 43 | - Some dirty code clean up 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.1" 6 | defaultConfig { 7 | applicationId "com.pv.sample" 8 | minSdkVersion 15 9 | targetSdkVersion 24 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile project(':library') 25 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 26 | exclude group: 'com.android.support', module: 'support-annotations' 27 | }) 28 | compile 'com.android.support:appcompat-v7:24.2.0' 29 | testCompile 'junit:junit:4.12' 30 | } 31 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/p-v/developer_tools/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/pv/sample/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.pv.sample; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.pv.sample", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/pv/sample/AwesomeAdapter.java: -------------------------------------------------------------------------------- 1 | package com.pv.sample; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ArrayAdapter; 10 | import android.widget.Filter; 11 | import android.widget.Filterable; 12 | import android.widget.TextView; 13 | 14 | import com.pv.datetimeseer.Config; 15 | import com.pv.datetimeseer.SeerFilter; 16 | import com.pv.datetimeseer.SuggestionRow; 17 | 18 | import java.util.List; 19 | 20 | /** 21 | * @author p-v 22 | */ 23 | 24 | public class AwesomeAdapter extends ArrayAdapter implements Filterable { 25 | 26 | private SeerFilter suggestionFilter; 27 | private List suggestionRowList; 28 | 29 | public AwesomeAdapter(Context context, int resource) { 30 | super(context, resource); 31 | } 32 | 33 | @Override 34 | public int getCount() { 35 | return suggestionRowList == null ? 0 : suggestionRowList.size(); 36 | } 37 | 38 | @Nullable 39 | @Override 40 | public SuggestionRow getItem(int position) { 41 | return suggestionRowList.get(position); 42 | } 43 | 44 | @NonNull 45 | @Override 46 | public Filter getFilter() { 47 | if (suggestionFilter == null) { 48 | Config config = new Config.ConfigBuilder() 49 | .setTimeFormat12HoursWithMins("h:mm a") 50 | .setTimeFormat12HoursWithoutMins("h a") 51 | .build(); 52 | 53 | suggestionFilter = new SeerFilter(getContext(), config); 54 | suggestionFilter.setOnSuggestionPublishListener(new SeerFilter.OnSuggestionPublishListener() { 55 | @Override 56 | public void onSuggestionPublish(List suggestionList) { 57 | AwesomeAdapter.this.suggestionRowList = suggestionList; 58 | if (suggestionList != null) { 59 | AwesomeAdapter.this.notifyDataSetChanged(); 60 | } else { 61 | AwesomeAdapter.this.notifyDataSetInvalidated(); 62 | } 63 | } 64 | }); 65 | } 66 | return suggestionFilter; 67 | } 68 | 69 | @NonNull 70 | @Override 71 | public View getView(int position, View convertView, @NonNull ViewGroup parent) { 72 | if (convertView == null) { 73 | convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.suggestion_row, parent, false); 74 | } 75 | TextView textView = (TextView) convertView 76 | .findViewById(R.id.suggestion_textView); 77 | SuggestionRow suggestion = getItem(position); 78 | if (suggestion != null) { 79 | textView.setText(suggestion.getDisplayValue()); 80 | } 81 | return convertView; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/pv/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.pv.sample; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.view.View; 6 | import android.widget.AdapterView; 7 | import android.widget.AutoCompleteTextView; 8 | import android.widget.Toast; 9 | 10 | import com.pv.datetimeseer.SuggestionRow; 11 | 12 | import java.text.DateFormat; 13 | import java.text.SimpleDateFormat; 14 | import java.util.Date; 15 | import java.util.Locale; 16 | 17 | public class MainActivity extends AppCompatActivity { 18 | 19 | private AwesomeAdapter awesomeAdapter; 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_main); 24 | 25 | final AutoCompleteTextView autoCompleteTextView = (AutoCompleteTextView) findViewById(R.id.awesome_view); 26 | autoCompleteTextView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 27 | @Override 28 | public void onItemClick(AdapterView parent, View view, int position, long id) { 29 | if (awesomeAdapter != null) { 30 | SuggestionRow suggestionRow = awesomeAdapter.getItem(position); 31 | if (suggestionRow != null) { 32 | String displayText; 33 | if (suggestionRow.getValue() == SuggestionRow.PARTIAL_VALUE) { 34 | displayText = suggestionRow.getDisplayValue() + " "; 35 | // show dropdown for partial values 36 | autoCompleteTextView.post(new Runnable() { 37 | @Override 38 | public void run() { 39 | autoCompleteTextView.showDropDown(); 40 | } 41 | }); 42 | } else { 43 | displayText = suggestionRow.getDisplayValue(); 44 | long selectedTime = suggestionRow.getValue() * 1000L; 45 | DateFormat df = new SimpleDateFormat("EEEE, d MMMM yyyy h:mma", Locale.ENGLISH); 46 | String timeOnScreen = df.format(new Date(selectedTime)); 47 | Toast.makeText(MainActivity.this, String.format(getString(R.string.awesome_time), 48 | timeOnScreen), Toast.LENGTH_SHORT).show(); 49 | } 50 | autoCompleteTextView.setText(displayText); 51 | autoCompleteTextView.setSelection(displayText.length()); 52 | } 53 | } 54 | } 55 | }); 56 | awesomeAdapter = new AwesomeAdapter(this, android.R.layout.simple_dropdown_item_1line); 57 | autoCompleteTextView.setAdapter(awesomeAdapter); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 20 | 21 | 28 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/suggestion_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p-v/DateTimeSeer/010fb4d222311628ebecc196277b95d6426c372d/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p-v/DateTimeSeer/010fb4d222311628ebecc196277b95d6426c372d/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p-v/DateTimeSeer/010fb4d222311628ebecc196277b95d6426c372d/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p-v/DateTimeSeer/010fb4d222311628ebecc196277b95d6426c372d/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p-v/DateTimeSeer/010fb4d222311628ebecc196277b95d6426c372d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DateTimeSeerSample 3 | Awesomeness waiting for you… 4 | Try some of these: 5 | Tom\nTomorrow\nTomorrow morning\ntomorrow evening\n3 days\n50m\n10mins\n3 months 6 | %s sounds amazing 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/pv/sample/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.pv.sample; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.0' 9 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.3' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | } 20 | } 21 | 22 | task clean(type: Delete) { 23 | delete rootProject.buildDir 24 | } 25 | -------------------------------------------------------------------------------- /demo/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p-v/DateTimeSeer/010fb4d222311628ebecc196277b95d6426c372d/demo/demo.gif -------------------------------------------------------------------------------- /gradle-mvn-push.gradle: -------------------------------------------------------------------------------- 1 | group = PROJ_GROUP 2 | version = PROJ_VERSION 3 | project.archivesBaseName = PROJ_ARTIFACTID 4 | 5 | apply plugin: 'com.jfrog.bintray' 6 | apply plugin: 'maven-publish' 7 | 8 | task sourcesJar(type: Jar) { 9 | from android.sourceSets.main.java.srcDirs 10 | classifier = 'sources' 11 | } 12 | 13 | task javadoc(type: Javadoc) { 14 | source = android.sourceSets.main.java.srcDirs 15 | classpath += configurations.compile 16 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 17 | failOnError false 18 | } 19 | 20 | task javadocJar(type: Jar, dependsOn: javadoc) { 21 | classifier = 'javadoc' 22 | from javadoc.destinationDir 23 | } 24 | 25 | javadoc { 26 | options{ 27 | encoding "UTF-8" 28 | charSet 'UTF-8' 29 | author true 30 | version true 31 | links "http://docs.oracle.com/javase/7/docs/api" 32 | title PROJ_ARTIFACTID 33 | } 34 | } 35 | 36 | artifacts { 37 | archives javadocJar 38 | archives sourcesJar 39 | } 40 | 41 | def pomConfig = { 42 | licenses { 43 | license { 44 | name "MIT" 45 | url "https://raw.githubusercontent.com/p-v/DateTimeSeer/master/LICENSE" 46 | distribution "repo" 47 | } 48 | } 49 | developers { 50 | developer { 51 | id DEVELOPER_ID 52 | name DEVELOPER_NAME 53 | email DEVELOPER_EMAIL 54 | } 55 | } 56 | } 57 | 58 | publishing { 59 | publications { 60 | mavenJava(MavenPublication) { 61 | artifactId PROJ_ARTIFACTID 62 | 63 | pom{ 64 | packaging 'aar' 65 | } 66 | pom.withXml { 67 | def root = asNode() 68 | root.appendNode('description', PROJ_DESCRIPTION) 69 | root.children().last() + pomConfig 70 | } 71 | } 72 | } 73 | } 74 | 75 | bintray { 76 | Properties properties = new Properties() 77 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 78 | user = properties.getProperty('BINTRAY_USER') 79 | key = properties.getProperty('BINTRAY_KEY') 80 | 81 | configurations = ['archives'] 82 | publications = ['mavenJava'] 83 | publish = true 84 | 85 | pkg { 86 | repo = 'maven' 87 | name = PROJ_NAME 88 | desc = PROJ_DESCRIPTION 89 | websiteUrl = PROJ_WEBSITEURL 90 | issueTrackerUrl = PROJ_ISSUETRACKERURL 91 | vcsUrl = PROJ_VCSURL 92 | licenses = ['MIT'] 93 | publicDownloadNumbers = true 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | PROJ_GROUP=com.pv 19 | PROJ_VERSION=1.0.0 20 | PROJ_NAME=DateTimeSeer 21 | PROJ_WEBSITEURL=https://github.com/p-v/DateTimeSeer 22 | PROJ_ISSUETRACKERURL=https://github.com/p-v/DateTimeSeer/issues 23 | PROJ_VCSURL=https://github.com/p-v/DateTimeSeer.git 24 | PROJ_DESCRIPTION=A seer who gets visions of the date and time you are thinking while typing. 25 | PROJ_ARTIFACTID=datetimeseer 26 | 27 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p-v/DateTimeSeer/010fb4d222311628ebecc196277b95d6426c372d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 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-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.1" 6 | 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion 24 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | compile fileTree(dir: 'libs', include: ['*.jar']) 26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 27 | exclude group: 'com.android.support', module: 'support-annotations' 28 | }) 29 | compile 'com.android.support:appcompat-v7:24.2.0' 30 | testCompile 'junit:junit:4.12' 31 | } 32 | 33 | //apply from: '../gradle-mvn-push.gradle' 34 | -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/p-v/developer_tools/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/pv/datetimeseer/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.pv.datetimeseer; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.pv.datetimeseer.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /library/src/main/java/com/pv/datetimeseer/Config.java: -------------------------------------------------------------------------------- 1 | package com.pv.datetimeseer; 2 | 3 | /** 4 | * @author p-v 5 | */ 6 | 7 | public class Config { 8 | 9 | private final String dateFormatWithYear; 10 | private final String dateFormatWithoutYear; 11 | private final String timeFormat24Hours; 12 | private final String timeFormat12HoursWithMins; 13 | private final String timeFormat12HoursWithoutMins; 14 | 15 | private Config(ConfigBuilder builder) { 16 | this.dateFormatWithYear = builder.dateFormatWithYear; 17 | this.dateFormatWithoutYear = builder.dateFormatWithoutYear; 18 | this.timeFormat24Hours = builder.timeFormat24Hours; 19 | this.timeFormat12HoursWithMins = builder.timeFormat12HoursWithMins; 20 | this.timeFormat12HoursWithoutMins = builder.timeFormat12HoursWithoutMins; 21 | } 22 | 23 | public String getDateFormatWithYear() { 24 | return dateFormatWithYear == null ? Constants.DATE_FORMAT_WITH_YEAR : dateFormatWithYear; 25 | } 26 | 27 | public String getDateFormatWithoutYear() { 28 | return dateFormatWithoutYear == null ? Constants.DATE_FORMAT : dateFormatWithoutYear; 29 | } 30 | 31 | public String getTimeFormat24Hours() { 32 | return timeFormat24Hours == null ? Constants.TIME_FORMAT_24HOUR : timeFormat24Hours; 33 | } 34 | 35 | public String getTimeFormat12HoursWithMins() { 36 | return timeFormat12HoursWithMins == null ? 37 | Constants.TIME_FORMAT_12HOUR_WITH_MINS : timeFormat12HoursWithMins; 38 | } 39 | 40 | public String getTimeFormat12HoursWithoutMins() { 41 | return timeFormat12HoursWithoutMins == null ? 42 | Constants.TIME_FORMAT_12HOUR_WITHOUT_MINS : timeFormat12HoursWithoutMins; 43 | } 44 | 45 | 46 | public static class ConfigBuilder { 47 | 48 | private String dateFormatWithYear; 49 | private String dateFormatWithoutYear; 50 | private String timeFormat24Hours; 51 | private String timeFormat12HoursWithMins; 52 | private String timeFormat12HoursWithoutMins; 53 | 54 | public ConfigBuilder setDateFormatWithYear(String dateFormatWithYear) { 55 | this.dateFormatWithYear = dateFormatWithYear; 56 | return this; 57 | } 58 | 59 | public ConfigBuilder setDateFormatWithoutYear(String dateFormatWithoutYear) { 60 | this.dateFormatWithoutYear = dateFormatWithoutYear; 61 | return this; 62 | } 63 | 64 | public ConfigBuilder setTimeFormat24Hours(String timeFormat24Hours) { 65 | this.timeFormat24Hours = timeFormat24Hours; 66 | return this; 67 | } 68 | 69 | public ConfigBuilder setTimeFormat12HoursWithMins(String timeFormat12HoursWithMins) { 70 | this.timeFormat12HoursWithMins = timeFormat12HoursWithMins; 71 | return this; 72 | } 73 | 74 | public ConfigBuilder setTimeFormat12HoursWithoutMins(String timeFormat12HoursWithoutMins) { 75 | this.timeFormat12HoursWithoutMins = timeFormat12HoursWithoutMins; 76 | return this; 77 | } 78 | 79 | public Config build() { 80 | return new Config(this); 81 | } 82 | 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /library/src/main/java/com/pv/datetimeseer/Constants.java: -------------------------------------------------------------------------------- 1 | package com.pv.datetimeseer; 2 | 3 | import android.support.annotation.IntDef; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | /** 9 | * @author p-v 10 | */ 11 | 12 | class Constants { 13 | 14 | @IntDef({Weekend.SATURDAY_SUNDAY, Weekend.FRIDAY_SATURDAY, 15 | Weekend.THURSDAY_FRIDAY, Weekend.FRIDAY_ONLY, 16 | Weekend.SATURDAY_ONLY, Weekend.SUNDAY_ONLY}) 17 | 18 | @Retention(RetentionPolicy.SOURCE) 19 | @interface Weekend { 20 | int SATURDAY_SUNDAY = 0; 21 | int FRIDAY_SATURDAY = 1; 22 | int THURSDAY_FRIDAY = 2; 23 | int FRIDAY_ONLY = 3; 24 | int SATURDAY_ONLY = 4; 25 | int SUNDAY_ONLY = 5; 26 | } 27 | 28 | static final int NEXT_WEEK_THRESHOLD = 4; 29 | 30 | static final String DATE_FORMAT = "EEEE, d MMMM"; 31 | static final String DATE_FORMAT_WITH_YEAR = "EEEE, d MMMM yyyy"; 32 | static final String TIME_FORMAT_24HOUR = "H:mm"; 33 | static final String TIME_FORMAT_12HOUR_WITH_MINS = "h:mma"; 34 | static final String TIME_FORMAT_12HOUR_WITHOUT_MINS = "ha"; 35 | 36 | static final String MONTH_FORMAT_SHORT = "MMM"; 37 | static final String MONTH_FORMAT_LONG = "MMMM"; 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /library/src/main/java/com/pv/datetimeseer/DOWSuggestionHandler.java: -------------------------------------------------------------------------------- 1 | package com.pv.datetimeseer; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.Calendar; 6 | import java.util.List; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * Suggestion handler for Day of Week.
12 | * Handles strings such as, next friday, fri, tuesday
13 | * Also handles partial strings such as tues, thus 14 | * 15 | * @author p-v 16 | */ 17 | class DOWSuggestionHandler extends SuggestionHandler { 18 | 19 | private static final String DAY_OF_WEEK = "(next\\s{0,2})?(?:\\b(?:(?:(mon)|(fri)|(sun))(?:d(?:ay?)?)?)|\\b(tue(?:s(?:d(?:ay?)?)?)?)|\\b(wed(?:n(?:e(?:s(?:d(?:ay?)?)?)?)?)?)|\\b(thu(?:r(?:s(?:d(?:ay?)?)?)?)?)|\\b(sat(?:u(?:r(?:d(?:ay?)?)?)?)?))\\b"; 20 | private Pattern pDow; 21 | 22 | DOWSuggestionHandler(Config config) { 23 | super(config); 24 | pDow = Pattern.compile(DAY_OF_WEEK, Pattern.CASE_INSENSITIVE); 25 | } 26 | 27 | @Override 28 | public void handle(Context context, String input, String lastToken, SuggestionValue suggestionValue) { 29 | Matcher matcher = pDow.matcher(input); 30 | if (matcher.find()) { 31 | int value = -1; 32 | if (matcher.group(2) != null) { 33 | // Monday 34 | value = Calendar.MONDAY; 35 | } else if (matcher.group(3) != null) { 36 | // Friday 37 | value = Calendar.FRIDAY; 38 | } else if (matcher.group(4) != null) { 39 | // Sunday 40 | value = Calendar.SUNDAY; 41 | } else if (matcher.group(5) != null) { 42 | // Tuesday 43 | value = Calendar.TUESDAY; 44 | } else if (matcher.group(6) != null) { 45 | // Wednesday 46 | value = Calendar.WEDNESDAY; 47 | } else if (matcher.group(7) != null) { 48 | // Thursday 49 | value = Calendar.THURSDAY; 50 | } else if (matcher.group(8) != null) { 51 | // Saturday 52 | value = Calendar.SATURDAY; 53 | } 54 | if (value != -1) { 55 | if (matcher.group(1) != null) { 56 | suggestionValue.appendSuggestion(SuggestionValue.DAY_OF_WEEK_NEXT, value); 57 | } else { 58 | suggestionValue.appendSuggestion(SuggestionValue.DAY_OF_WEEK, value); 59 | } 60 | } 61 | } 62 | 63 | super.handle(context, input, lastToken, suggestionValue); 64 | } 65 | 66 | @Override 67 | public void build(Context context, SuggestionValue suggestionValue, List suggestionList) { 68 | SuggestionValue.LocalItemItem dowItem = suggestionValue.getDowItem(); 69 | SuggestionValue.LocalItemItem nextDowItem = suggestionValue.getNextDowItem(); 70 | 71 | // Check whether to handle or not 72 | if (dowItem != null || nextDowItem != null) { 73 | 74 | // Time related items 75 | SuggestionValue.LocalItemItem todItem = suggestionValue.getTodItem(); 76 | TimeSuggestionHandler.TimeItem timeItem = suggestionValue.getTimeItem(); 77 | 78 | // Initialize user value and current day value 79 | Calendar cal = Calendar.getInstance(); 80 | final int currentDayOfWeek = cal.get(Calendar.DAY_OF_WEEK); 81 | final int actualUserValue = dowItem != null ? dowItem.value : nextDowItem.value; 82 | 83 | int daysToAdd; 84 | if (currentDayOfWeek < actualUserValue) { 85 | // day of week user value is less than the current day value 86 | // example user value Saturday = 7 and current day is Friday 6 87 | daysToAdd = actualUserValue - currentDayOfWeek; 88 | } else { 89 | // day of week user value is more than the current day value 90 | // example user value Monday = 1 and current day is Saturday 7 91 | daysToAdd = 7 - (currentDayOfWeek - actualUserValue); 92 | } 93 | 94 | // User inputs the day same as today, then consider the next day of week 95 | if (dowItem != null && daysToAdd == 0) { 96 | daysToAdd += 7; 97 | } 98 | 99 | // For next day of the week item add 7 days if the day is within the threshold value 100 | if (nextDowItem != null && daysToAdd < Constants.NEXT_WEEK_THRESHOLD) { 101 | daysToAdd += 7; 102 | } 103 | 104 | // Compute display and real value if time items are not null 105 | Value timeValue; 106 | if (todItem != null || timeItem != null) { 107 | if (todItem == null) { 108 | // timeItem is not null here 109 | int hour = timeItem.value / 60; 110 | int mins = timeItem.value % 60; 111 | timeValue = getTimeValue(context, hour, mins, null, null); 112 | } else { 113 | timeValue = getTimeValue(context, todItem, timeItem); 114 | } 115 | 116 | timeValue.value.add(Calendar.DAY_OF_WEEK, daysToAdd); 117 | 118 | // add to suggestion list 119 | suggestionList.add(new SuggestionRow(getDisplayDate(context, 120 | timeValue.value, timeValue.displayString), 121 | (int)(timeValue.value.getTimeInMillis()/1000))); 122 | 123 | timeValue.value.add(Calendar.DAY_OF_WEEK, 7); 124 | suggestionList.add(new SuggestionRow(getDisplayDate(context, 125 | timeValue.value, timeValue.displayString), 126 | (int)(timeValue.value.getTimeInMillis()/1000))); 127 | 128 | timeValue.value.add(Calendar.DAY_OF_WEEK, 7); 129 | suggestionList.add(new SuggestionRow(getDisplayDate(context, 130 | timeValue.value, timeValue.displayString), 131 | (int)(timeValue.value.getTimeInMillis()/1000))); 132 | } else { 133 | // Create 3 partial date suggestions 134 | cal = Calendar.getInstance(); 135 | cal.add(Calendar.DAY_OF_WEEK , daysToAdd); 136 | // first suggestion 137 | suggestionList.add(new SuggestionRow(DateTimeUtils.getDisplayDate(cal, config), 138 | SuggestionRow.PARTIAL_VALUE)); 139 | 140 | // second suggestion 141 | cal.add(Calendar.DAY_OF_WEEK , 7); 142 | suggestionList.add(new SuggestionRow(DateTimeUtils.getDisplayDate(cal, config), 143 | SuggestionRow.PARTIAL_VALUE)); 144 | 145 | // third suggestion 146 | cal.add(Calendar.DAY_OF_WEEK , 7); 147 | suggestionList.add(new SuggestionRow(DateTimeUtils.getDisplayDate(cal, config), 148 | SuggestionRow.PARTIAL_VALUE)); 149 | } 150 | 151 | 152 | } else { 153 | super.build(context, suggestionValue, suggestionList); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /library/src/main/java/com/pv/datetimeseer/DateSuggestionHandler.java: -------------------------------------------------------------------------------- 1 | package com.pv.datetimeseer; 2 | 3 | import android.content.Context; 4 | 5 | import java.text.DateFormat; 6 | import java.text.ParseException; 7 | import java.text.SimpleDateFormat; 8 | import java.util.Calendar; 9 | import java.util.Date; 10 | import java.util.List; 11 | import java.util.Locale; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | /** 16 | * @author p-v 17 | */ 18 | class DateSuggestionHandler extends SuggestionHandler { 19 | 20 | private static final String DATE_RGX = "\\b(?:(0?[1-9]|[12][0-9]|3[01])(?:st|nd|rd|th)?\\s+\\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|may|june|july|august|september|october|november|december)\\b(?:(?:,?\\s*)(?:(?:20)?(\\d\\d)(?!:)))?|(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|may|june|july|august|september|october|november|december)\\s+(0?[1-9]|[12][0-9]|3[01])(?:st|nd|rd|th)?\\b(?:(?:,?\\s*)(?:\\b(?:20)?(\\d\\d)(?!:))\\b)?)\\b"; 21 | private Pattern pDate; 22 | 23 | static class DateItem extends SuggestionValue.LocalItemItem { 24 | 25 | int startIdx; 26 | int endIdx; 27 | 28 | DateItem(int value, int startIdx, int endIdx) { 29 | super(value); 30 | this.startIdx = startIdx; 31 | this.endIdx = endIdx; 32 | } 33 | } 34 | 35 | DateSuggestionHandler(Config config) { 36 | super(config); 37 | pDate = Pattern.compile(DATE_RGX, Pattern.CASE_INSENSITIVE); 38 | } 39 | 40 | @Override 41 | public void handle(Context context, String input, String lastToken, SuggestionValue suggestionValue) { 42 | Matcher m = pDate.matcher(input); 43 | 44 | if (m.find()) { 45 | int dayOfMonth; 46 | int year = 0; 47 | String month; 48 | String yearStr; 49 | if (m.group(1) != null) { 50 | dayOfMonth = Integer.parseInt(m.group(1)); 51 | month = m.group(2); 52 | yearStr = m.group(3); 53 | } else { 54 | dayOfMonth = Integer.parseInt(m.group(5)); 55 | month = m.group(4); 56 | yearStr = m.group(6); 57 | } 58 | if (yearStr != null) { 59 | year = 2000 + Integer.parseInt(yearStr); 60 | } 61 | DateFormat fmt; 62 | if(month.length() == 3){ 63 | fmt = new SimpleDateFormat(Constants.MONTH_FORMAT_SHORT, Locale.ENGLISH); 64 | }else{ 65 | fmt = new SimpleDateFormat(Constants.MONTH_FORMAT_LONG, Locale.ENGLISH); 66 | } 67 | Date date = null; 68 | try { 69 | date = fmt.parse(month); 70 | } catch (ParseException e) { 71 | e.printStackTrace(); 72 | } 73 | Calendar cal = Calendar.getInstance(); 74 | 75 | int currentYear = cal.get(Calendar.YEAR); 76 | if (year > 0 && year >= currentYear) { 77 | cal.set(Calendar.YEAR, year); 78 | } 79 | cal.set(Calendar.DAY_OF_MONTH, dayOfMonth); 80 | cal.set(Calendar.HOUR_OF_DAY, 0); 81 | cal.set(Calendar.MINUTE, 0); 82 | cal.set(Calendar.SECOND, 0); 83 | cal.set(Calendar.MILLISECOND, 0); 84 | if (date != null) { 85 | Calendar ctemp = Calendar.getInstance(); 86 | ctemp.setTime(date); 87 | cal.set(Calendar.MONTH, ctemp.get(Calendar.MONTH)); 88 | } 89 | suggestionValue.appendSuggestion(SuggestionValue.DATE, 90 | new DateItem((int)(cal.getTimeInMillis()/1000), m.start(), m.end())); 91 | } 92 | 93 | super.handle(context, input, lastToken, suggestionValue); 94 | } 95 | 96 | @Override 97 | public void build(Context context, SuggestionValue suggestionValue, List suggestionList) { 98 | DateItem dateItem = suggestionValue.getDateItem(); 99 | if (dateItem != null) { 100 | SuggestionValue.LocalItemItem todItem = suggestionValue.getTodItem(); 101 | TimeSuggestionHandler.TimeItem timeItem = suggestionValue.getTimeItem(); 102 | 103 | Value timeValue = null; 104 | int secondsOfDay = 0; 105 | if (todItem != null || timeItem != null) { 106 | if (todItem == null) { 107 | // timeItem is not null here 108 | int hour = timeItem.value / 60; 109 | int mins = timeItem.value % 60; 110 | timeValue = getTimeValue(context, hour, mins, null, null); 111 | secondsOfDay = timeItem.value * 60; 112 | } else { 113 | timeValue = getTimeValue(context, todItem, timeItem); 114 | secondsOfDay = 60 * (timeValue.value.get(Calendar.MINUTE) 115 | + timeValue.value.get(Calendar.HOUR_OF_DAY) * 60); 116 | } 117 | 118 | } 119 | 120 | Calendar cal = Calendar.getInstance(); 121 | boolean incrementYear = false; 122 | if (cal.getTimeInMillis() / 1000 > (dateItem.value + secondsOfDay)) { 123 | // current time is more than the specified date 124 | incrementYear = true; 125 | } 126 | cal.setTimeInMillis(dateItem.value * 1000L); 127 | if (incrementYear) { 128 | cal.add(Calendar.YEAR, 1); 129 | } 130 | 131 | if (timeValue == null) { 132 | if (DateTimeUtils.isWeekend(cal.get(Calendar.DAY_OF_WEEK), WEEKEND)) { 133 | int morningTime = MORNING_TIME_WEEKEND; 134 | int hour = morningTime / 60; 135 | int mins = morningTime % 60; 136 | timeValue = getTimeValue(context, hour, mins, null, null); 137 | } else { 138 | int morningTime = MORNING_TIME_WEEKDAY; 139 | int hour = morningTime / 60; 140 | int mins = morningTime % 60; 141 | timeValue = getTimeValue(context, hour, mins, null, null); 142 | } 143 | 144 | // show multiple suggestions 145 | // update time value 146 | timeValue.value.set(Calendar.DAY_OF_YEAR, cal.get(Calendar.DAY_OF_YEAR)); 147 | timeValue.value.set(Calendar.YEAR, cal.get(Calendar.YEAR)); 148 | 149 | String displayDate = getDisplayDate(timeValue.value); 150 | 151 | // first item 152 | suggestionList.add(new SuggestionRow(displayDate + ", " 153 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 154 | 155 | //second item 156 | int afternoonTime = AFTERNOON_TIME; 157 | int hour = afternoonTime / 60; 158 | int mins = afternoonTime % 60; 159 | timeValue.value.set(Calendar.HOUR_OF_DAY, hour); 160 | timeValue.value.set(Calendar.MINUTE, mins); 161 | suggestionList.add(new SuggestionRow(displayDate + ", " 162 | + DateTimeUtils.getDisplayTime(context, timeValue.value, config), (int)(timeValue.value.getTimeInMillis()/1000))); 163 | 164 | //third item 165 | timeValue.value.set(Calendar.HOUR_OF_DAY, 12 + EVENING_TIME); 166 | timeValue.value.set(Calendar.MINUTE, 0); 167 | suggestionList.add(new SuggestionRow(displayDate + ", " 168 | + DateTimeUtils.getDisplayTime(context, timeValue.value, config), (int)(timeValue.value.getTimeInMillis()/1000))); 169 | } else { 170 | // just show one suggestion as the time is specified with date 171 | // update time value 172 | timeValue.value.set(Calendar.DAY_OF_YEAR, cal.get(Calendar.DAY_OF_YEAR)); 173 | timeValue.value.set(Calendar.YEAR, cal.get(Calendar.YEAR)); 174 | 175 | suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value) + ", " 176 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 177 | } 178 | 179 | 180 | } else { 181 | super.build(context, suggestionValue, suggestionList); 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /library/src/main/java/com/pv/datetimeseer/DateTimeUtils.java: -------------------------------------------------------------------------------- 1 | package com.pv.datetimeseer; 2 | 3 | import android.content.Context; 4 | 5 | import java.text.DateFormat; 6 | import java.text.SimpleDateFormat; 7 | import java.util.Calendar; 8 | import java.util.Locale; 9 | 10 | /** 11 | * @author p-v 12 | */ 13 | @SuppressWarnings("WrongConstant") 14 | class DateTimeUtils { 15 | 16 | static String getDisplayDate(Calendar cal, Config config) { 17 | DateFormat df = new SimpleDateFormat(config.getDateFormatWithoutYear(), Locale.ENGLISH); 18 | return df.format(cal.getTime()); 19 | } 20 | 21 | static String getDisplayTime(Context context, Calendar cal, Config config) { 22 | 23 | String format; 24 | if (android.text.format.DateFormat.is24HourFormat(context)) { 25 | format = config.getTimeFormat24Hours(); 26 | } else { 27 | if (cal.get(Calendar.MINUTE) == 0) { 28 | format = config.getTimeFormat12HoursWithoutMins(); 29 | } else { 30 | format = config.getTimeFormat12HoursWithMins(); 31 | } 32 | } 33 | DateFormat df = new SimpleDateFormat(format, Locale.ENGLISH); 34 | return df.format(cal.getTime()); 35 | } 36 | 37 | static int daysBetween(Calendar day1, Calendar day2) { 38 | /** 39 | * Saved some effort using the solution described here, 40 | * http://stackoverflow.com/a/28865648/1587370 41 | */ 42 | Calendar dayOne = (Calendar) day1.clone(), 43 | dayTwo = (Calendar) day2.clone(); 44 | 45 | if (dayOne.get(Calendar.YEAR) == dayTwo.get(Calendar.YEAR)) { 46 | return Math.abs(dayOne.get(Calendar.DAY_OF_YEAR) - dayTwo.get(Calendar.DAY_OF_YEAR)); 47 | } else { 48 | if (dayTwo.get(Calendar.YEAR) > dayOne.get(Calendar.YEAR)) { 49 | //swap them 50 | Calendar temp = dayOne; 51 | dayOne = dayTwo; 52 | dayTwo = temp; 53 | } 54 | int extraDays = 0; 55 | 56 | int dayOneOriginalYearDays = dayOne.get(Calendar.DAY_OF_YEAR); 57 | 58 | while (dayOne.get(Calendar.YEAR) > dayTwo.get(Calendar.YEAR)) { 59 | dayOne.add(Calendar.YEAR, -1); 60 | // getActualMaximum() important for leap years 61 | extraDays += dayOne.getActualMaximum(Calendar.DAY_OF_YEAR); 62 | } 63 | 64 | return extraDays - dayTwo.get(Calendar.DAY_OF_YEAR) + dayOneOriginalYearDays; 65 | } 66 | } 67 | 68 | /** 69 | * Determines if the passed day of week is weekend 70 | * 71 | * @param dayOfWeek day to determine 72 | * @param weekendValue user's weekend value 73 | * @return true if weekend, false otherwise 74 | */ 75 | static boolean isWeekend(int dayOfWeek, 76 | @Constants.Weekend int weekendValue) { 77 | switch (weekendValue) { 78 | case Constants.Weekend.SATURDAY_SUNDAY: 79 | return Calendar.SATURDAY == dayOfWeek || Calendar.SUNDAY == dayOfWeek; 80 | case Constants.Weekend.FRIDAY_SATURDAY: 81 | return Calendar.FRIDAY == dayOfWeek || Calendar.SATURDAY == dayOfWeek; 82 | case Constants.Weekend.THURSDAY_FRIDAY: 83 | return Calendar.THURSDAY == dayOfWeek || Calendar.FRIDAY == dayOfWeek; 84 | case Constants.Weekend.FRIDAY_ONLY: 85 | return Calendar.FRIDAY == dayOfWeek; 86 | case Constants.Weekend.SATURDAY_ONLY: 87 | return Calendar.SATURDAY == dayOfWeek; 88 | case Constants.Weekend.SUNDAY_ONLY: 89 | return Calendar.SUNDAY == dayOfWeek; 90 | default: 91 | return false; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /library/src/main/java/com/pv/datetimeseer/InitialSuggestionHandler.java: -------------------------------------------------------------------------------- 1 | package com.pv.datetimeseer; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.Calendar; 6 | import java.util.List; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * Initial suggestion handler used for suggesting values and ignoring other handlers 12 | * if its able to handle the input 13 | * 14 | * @author p-v 15 | */ 16 | class InitialSuggestionHandler extends SuggestionHandler { 17 | 18 | private static final String REGEX = "^\\s*(\\d{1,2})\\s*$"; 19 | 20 | private Pattern p; 21 | 22 | InitialSuggestionHandler(Config config) { 23 | super(config); 24 | p = Pattern.compile(REGEX); 25 | } 26 | 27 | @Override 28 | public void handle(Context context, String input, String lastToken, SuggestionValue suggestionValue) { 29 | if (input.trim().length() == 2 && input.trim().matches("(?i)^to")) { 30 | // if only 2 char input is there and that is 't0', do not go further, 31 | // consider it as today and tomorrow, and show suggestions accordingly 32 | suggestionValue.appendSuggestion(SuggestionValue.OTHER, 0); 33 | } else { 34 | // check if the input has only numbers 35 | Matcher m = p.matcher(input); 36 | if (m.find()) { 37 | int number = Integer.parseInt(m.group(1)); 38 | if (number > 0) { 39 | suggestionValue.appendSuggestion(SuggestionValue.NUMBER, number); 40 | } else { 41 | super.handle(context, input, lastToken, suggestionValue); 42 | } 43 | } else { 44 | super.handle(context, input, lastToken, suggestionValue); 45 | } 46 | } 47 | } 48 | 49 | @Override 50 | public void build(Context context, SuggestionValue suggestionValue, List suggestionList) { 51 | SuggestionValue.LocalItemItem numItem = suggestionValue.getNumberItem(); 52 | SuggestionValue.LocalItemItem otherItem = suggestionValue.getOtherItem(); 53 | if (numItem != null && numItem.value <= 31) { 54 | int number = numItem.value; 55 | 56 | Value timeValue; 57 | if (number < 24) { 58 | Calendar cal = Calendar.getInstance(); 59 | final int currentHourOfDay = cal.get(Calendar.HOUR_OF_DAY); 60 | 61 | // consider it as time other wise date 62 | // first time from now be it am or pm 63 | timeValue = getTimeValue(context, number, 0, currentHourOfDay >= 12 ? "pm" : "am", null); 64 | 65 | 66 | // first item 67 | if (currentHourOfDay < timeValue.value.get(Calendar.HOUR_OF_DAY)) { 68 | suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " 69 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 70 | } else { 71 | // increment 12 hours 72 | timeValue.value.add(Calendar.HOUR_OF_DAY, 12); 73 | // if same day show today otherwise tomorrow 74 | if (cal.get(Calendar.DAY_OF_YEAR) == timeValue.value.get(Calendar.DAY_OF_YEAR)) { 75 | suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " 76 | + DateTimeUtils.getDisplayTime(context, timeValue.value, config), (int)(timeValue.value.getTimeInMillis()/1000))); 77 | } else{ 78 | suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " 79 | + DateTimeUtils.getDisplayTime(context, timeValue.value, config), (int)(timeValue.value.getTimeInMillis()/1000))); 80 | } 81 | } 82 | 83 | // second item 84 | // increment 12 hours 85 | timeValue.value.add(Calendar.HOUR_OF_DAY, 12); 86 | // if same day show today otherwise tomorrow 87 | if (cal.get(Calendar.DAY_OF_YEAR) == timeValue.value.get(Calendar.DAY_OF_YEAR)) { 88 | suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " 89 | + DateTimeUtils.getDisplayTime(context, timeValue.value, config), (int)(timeValue.value.getTimeInMillis()/1000))); 90 | } else{ 91 | suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " 92 | + DateTimeUtils.getDisplayTime(context, timeValue.value, config), (int)(timeValue.value.getTimeInMillis()/1000))); 93 | } 94 | } 95 | 96 | Calendar cal = Calendar.getInstance(); 97 | int maxDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH); 98 | 99 | int currentDayOfMonth = cal.get(Calendar.DAY_OF_MONTH); 100 | 101 | if (currentDayOfMonth < number && number <= maxDay) { 102 | cal.set(Calendar.DAY_OF_MONTH, number); 103 | } else { 104 | cal.add(Calendar.MONTH, 1); 105 | // TODO revisit someday 106 | maxDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH); 107 | cal.set(Calendar.DAY_OF_MONTH, maxDay < number ? maxDay : number); 108 | } 109 | suggestionList.add(new SuggestionRow(DateTimeUtils.getDisplayDate(cal, config), SuggestionRow.PARTIAL_VALUE)); 110 | 111 | } else if (otherItem != null) { 112 | suggestionList.add(new SuggestionRow(context.getString(R.string.today), SuggestionRow.PARTIAL_VALUE)); 113 | suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow), SuggestionRow.PARTIAL_VALUE)); 114 | } else { 115 | super.build(context, suggestionValue, suggestionList); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /library/src/main/java/com/pv/datetimeseer/NumberRelativeTimeSuggestionHandler.java: -------------------------------------------------------------------------------- 1 | package com.pv.datetimeseer; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.Calendar; 6 | import java.util.List; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * 12 | * Relative time suggestion handler

13 | * 14 | * e.g.
after 6 hours
15 | * 6mins
16 | * 10 days
17 | * after 2 months
18 | * 19 | * @author p-v 20 | */ 21 | class NumberRelativeTimeSuggestionHandler extends SuggestionHandler { 22 | 23 | private static final String REGEX = "\\b(?:(?:(?:(?:after\\s{1,2})?(\\d\\d?)\\s{0,2})|next\\s{1,2})(?:(month?)|(m(?:i(?:n(?:u(?:te?)?)?)?)?)|(we(?:ek?))|(d(?:ay?))|(hr|h(?:o(?:ur?)?)?))s?)\\b"; 24 | private Pattern pRel; 25 | 26 | class RelativeDayNumItem extends SuggestionValue.LocalItemItem { 27 | 28 | static final int DAY = 1; 29 | static final int HOUR = 2; 30 | static final int MIN = 3; 31 | static final int WEEK = 4; 32 | static final int MONTH = 5; 33 | 34 | int type; 35 | int startIdx; 36 | int endIdx; 37 | 38 | RelativeDayNumItem(int value, int type, int startIdx, int endIdx) { 39 | super(value); 40 | this.type = type; 41 | this.startIdx = startIdx; 42 | this.endIdx = endIdx; 43 | } 44 | } 45 | 46 | NumberRelativeTimeSuggestionHandler(Config config){ 47 | super(config); 48 | pRel = Pattern.compile(REGEX, Pattern.CASE_INSENSITIVE); 49 | } 50 | 51 | @Override 52 | public void handle(Context context, String input, String lastToken, SuggestionValue suggestionValue) { 53 | Matcher m = pRel.matcher(input); 54 | if (m.find()) { 55 | 56 | int digit; 57 | if (m.group(1) != null) { 58 | digit = Integer.parseInt(m.group(1)); 59 | } else { 60 | // group 2 i.e. "next" isn't null 61 | digit = 1; 62 | } 63 | 64 | int type = -1; 65 | if (m.group(2) != null) { 66 | type = RelativeDayNumItem.MONTH; 67 | } else if (m.group(3) != null) { 68 | type = RelativeDayNumItem.MIN; 69 | } else if (m.group(4) != null) { 70 | type = RelativeDayNumItem.WEEK; 71 | } else if (m.group(5) != null) { 72 | type = RelativeDayNumItem.DAY; 73 | } else if (m.group(6) != null) { 74 | type = RelativeDayNumItem.HOUR; 75 | } 76 | 77 | if (type != -1) { 78 | suggestionValue.appendSuggestion(SuggestionValue.RELATIVE_DAY_NUMBER, 79 | new RelativeDayNumItem(digit, type, m.start(), m.end())); 80 | 81 | if (type == RelativeDayNumItem.HOUR || type == RelativeDayNumItem.MIN) { 82 | // ignore rest of the handlers if hour/min found 83 | return; 84 | } 85 | } 86 | } 87 | super.handle(context, input, lastToken, suggestionValue); 88 | } 89 | 90 | @Override 91 | public void build(Context context, SuggestionValue suggestionValue, List suggestionList) { 92 | RelativeDayNumItem relNumItem = suggestionValue.getRelativeDayNumItem(); 93 | if (relNumItem != null) { 94 | Calendar calendar = Calendar.getInstance(); 95 | 96 | boolean isPartial = false; 97 | switch (relNumItem.type) { 98 | case RelativeDayNumItem.DAY: 99 | isPartial = true; 100 | calendar.add(Calendar.DAY_OF_YEAR, relNumItem.value); 101 | break; 102 | case RelativeDayNumItem.HOUR: 103 | calendar.add(Calendar.HOUR_OF_DAY, relNumItem.value); 104 | break; 105 | case RelativeDayNumItem.MIN: 106 | calendar.add(Calendar.MINUTE, relNumItem.value); 107 | break; 108 | case RelativeDayNumItem.WEEK: 109 | isPartial = true; 110 | calendar.add(Calendar.WEEK_OF_YEAR, relNumItem.value); 111 | break; 112 | case RelativeDayNumItem.MONTH: 113 | isPartial = true; 114 | calendar.add(Calendar.MONTH, relNumItem.value); 115 | break; 116 | } 117 | 118 | if (relNumItem.type != RelativeDayNumItem.HOUR && relNumItem.type != RelativeDayNumItem.MIN) { 119 | TimeSuggestionHandler.TimeItem timeItem = suggestionValue.getTimeItem(); 120 | 121 | if (timeItem != null) { 122 | int hour = timeItem.value / 60; 123 | int mins = timeItem.value % 60; 124 | 125 | calendar.set(Calendar.HOUR_OF_DAY, hour); 126 | calendar.set(Calendar.MINUTE, mins); 127 | } 128 | } 129 | 130 | if (isPartial) { 131 | suggestionList.add(new SuggestionRow(DateTimeUtils.getDisplayDate(calendar, config), 132 | SuggestionRow.PARTIAL_VALUE)); 133 | } else { 134 | suggestionList.add(new SuggestionRow(getDisplayDate(context, calendar, true), 135 | (int) (calendar.getTimeInMillis()/1000))); 136 | } 137 | 138 | 139 | } else { 140 | super.build(context, suggestionValue, suggestionList); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /library/src/main/java/com/pv/datetimeseer/RelativeTimeSuggestionHandler.java: -------------------------------------------------------------------------------- 1 | package com.pv.datetimeseer; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.Calendar; 6 | import java.util.List; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * @author p-v 12 | */ 13 | class RelativeTimeSuggestionHandler extends SuggestionHandler { 14 | 15 | private static final String REGEX = "\\b(?:(tod(?:a(?:y)?)?)|(tom(?:o(?:r(?:r(?:o(?:w)?)?)?)?)?)" + 16 | "|(day after tomorrow)|((?:ton(?:i(?:g(?:(?:h)?t)?)?)?)|(?:ton(?:i(?:t(?:e)?)?)?)))\\b"; 17 | private Pattern pRel; 18 | 19 | RelativeTimeSuggestionHandler(Config config) { 20 | super(config); 21 | pRel = Pattern.compile(REGEX, Pattern.CASE_INSENSITIVE); 22 | } 23 | class RelativeDayItem extends SuggestionValue.LocalItemItem { 24 | 25 | boolean isPartial; 26 | 27 | RelativeDayItem(int value, boolean isPartial) { 28 | super(value); 29 | this.isPartial = isPartial; 30 | } 31 | } 32 | 33 | @Override 34 | public void handle(Context context, String input, String lastToken, SuggestionValue suggestionValue) { 35 | Matcher m = pRel.matcher(input); 36 | String text; 37 | if (m.find()) { 38 | if ((text = m.group(1)) != null) { 39 | suggestionValue.appendSuggestion(SuggestionValue.RELATIVE_DAY, 40 | new RelativeDayItem(0, !"today".equalsIgnoreCase(text.trim()))); 41 | } else if ((text = m.group(2)) != null) { 42 | suggestionValue.appendSuggestion(SuggestionValue.RELATIVE_DAY, 43 | new RelativeDayItem(1, !"tomorrow".equalsIgnoreCase(text.trim()))); 44 | } else if (m.group(3) != null){ 45 | suggestionValue.appendSuggestion(SuggestionValue.RELATIVE_DAY, new RelativeDayItem(2, false)); 46 | } else { 47 | suggestionValue.appendSuggestion(SuggestionValue.RELATIVE_DAY, new RelativeDayItem(10, false)); 48 | } 49 | } 50 | super.handle(context, input, lastToken, suggestionValue); 51 | } 52 | 53 | @Override 54 | public void build(Context context, SuggestionValue suggestionValue, List suggestionList) { 55 | RelativeDayItem relItem = suggestionValue.getRelDayItem(); 56 | if (relItem != null) { 57 | SuggestionValue.LocalItemItem todItem = suggestionValue.getTodItem(); 58 | TimeSuggestionHandler.TimeItem timeItem = suggestionValue.getTimeItem(); 59 | Value timeValue = null; 60 | if (todItem != null || timeItem != null) { 61 | if (todItem == null) { 62 | // timeItem is not null here 63 | int hour = timeItem.value / 60; 64 | int mins = timeItem.value % 60; 65 | timeValue = getTimeValue(context, hour, mins, null, null); 66 | } else { 67 | timeValue = getTimeValue(context, todItem, timeItem); 68 | } 69 | } 70 | // handle relative value 71 | if (relItem.value == 0) { 72 | // For today 73 | if (timeValue == null) { 74 | if (!relItem.isPartial) { 75 | 76 | int afternoonTime = AFTERNOON_TIME; 77 | 78 | // time is not specified 79 | Calendar cal = Calendar.getInstance(); 80 | int currentHourOfDay = cal.get(Calendar.HOUR_OF_DAY); 81 | int afternoonHour = afternoonTime / 60; 82 | int afternoonMins = afternoonTime % 60; 83 | int eveningHour = 12 + EVENING_TIME; 84 | 85 | if (currentHourOfDay < 23) { 86 | timeValue = getTimeValue(context, currentHourOfDay + 1, 0, null, null); 87 | // Current time is less than 11PM 88 | suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " 89 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 90 | } 91 | 92 | if (currentHourOfDay < afternoonHour && currentHourOfDay + 1 != afternoonHour) { 93 | timeValue = getTimeValue(context, afternoonHour, afternoonMins, null, null); 94 | suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " 95 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 96 | } 97 | 98 | if (currentHourOfDay < eveningHour && currentHourOfDay +1 != eveningHour) { 99 | timeValue = getTimeValue(context, eveningHour, 0, "pm", null); 100 | suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " 101 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 102 | } 103 | } else { 104 | suggestionList.add(new SuggestionRow(context.getString(R.string.today), SuggestionRow.PARTIAL_VALUE)); 105 | } 106 | } else { 107 | Calendar cal = Calendar.getInstance(); 108 | if (timeValue.value.before(cal)) { 109 | // time past increment time by 12 hours if AM/PM is not specified otherwise by 24 110 | if (timeItem.isAmPmPresent) { 111 | timeValue.value.add(Calendar.HOUR_OF_DAY, 24); 112 | suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " 113 | + DateTimeUtils.getDisplayTime(context, timeValue.value, config), (int)(timeValue.value.getTimeInMillis()/1000))); 114 | } else { 115 | timeValue.value.add(Calendar.HOUR_OF_DAY, 12); 116 | if (timeValue.value.before(cal)) { 117 | timeValue.value.add(Calendar.HOUR_OF_DAY, 12); 118 | suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " 119 | + DateTimeUtils.getDisplayTime(context, timeValue.value, config), (int)(timeValue.value.getTimeInMillis()/1000))); 120 | } else { 121 | if (cal.get(Calendar.DAY_OF_YEAR) == timeValue.value.get(Calendar.DAY_OF_YEAR)) { 122 | suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " 123 | + DateTimeUtils.getDisplayTime(context, timeValue.value, config), (int)(timeValue.value.getTimeInMillis()/1000))); 124 | } else { 125 | suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " 126 | + DateTimeUtils.getDisplayTime(context, timeValue.value, config), (int)(timeValue.value.getTimeInMillis()/1000))); 127 | } 128 | } 129 | } 130 | } else { 131 | suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " 132 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 133 | } 134 | } 135 | } else if (relItem.value == 1) { 136 | if (timeValue == null) { 137 | if (!relItem.isPartial) { 138 | int morningTime = MORNING_TIME_WEEKDAY; 139 | 140 | timeValue = getTimeValue(context, morningTime / 60, morningTime % 60, null, null); 141 | 142 | // increment a day for tomorrow 143 | timeValue.value.add(Calendar.DAY_OF_YEAR, 1); 144 | suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " 145 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 146 | 147 | int afternoonTime = AFTERNOON_TIME; 148 | timeValue = getTimeValue(context, afternoonTime / 60, afternoonTime % 60, null, null); 149 | // increment a day for tomorrow 150 | timeValue.value.add(Calendar.DAY_OF_YEAR, 1); 151 | suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " 152 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 153 | 154 | int eveningHour = 12 + EVENING_TIME; 155 | timeValue = getTimeValue(context, eveningHour, 0, "pm", null); 156 | // increment a day for tomorrow 157 | timeValue.value.add(Calendar.DAY_OF_YEAR, 1); 158 | suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " 159 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 160 | } else { 161 | suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow), SuggestionRow.PARTIAL_VALUE)); 162 | } 163 | 164 | } else { 165 | // increment a day for tomorrow 166 | timeValue.value.add(Calendar.DAY_OF_YEAR, 1); 167 | suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " 168 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 169 | } 170 | } else if (relItem.value == 2) { 171 | if (timeValue == null) { 172 | int morningTime = MORNING_TIME_WEEKDAY; 173 | 174 | timeValue = getTimeValue(context, morningTime / 60, morningTime % 60, null, null); 175 | // increment two days for day after tomorrow 176 | timeValue.value.add(Calendar.DAY_OF_YEAR, 2); 177 | suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value) + ", " 178 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 179 | 180 | int afternoonTime = AFTERNOON_TIME; 181 | timeValue = getTimeValue(context, afternoonTime / 60, afternoonTime % 60, null, null); 182 | // increment two days for day after tomorrow 183 | timeValue.value.add(Calendar.DAY_OF_YEAR, 2); 184 | suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value) + ", " 185 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 186 | 187 | int eveningHour = 12 + EVENING_TIME; 188 | timeValue = getTimeValue(context, eveningHour, 0, "pm", null); 189 | // increment two days for day after tomorrow 190 | timeValue.value.add(Calendar.DAY_OF_YEAR, 2); 191 | suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value) + ", " 192 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 193 | } else { 194 | // increment two days for day after tomorrow 195 | timeValue.value.add(Calendar.DAY_OF_YEAR, 2); 196 | suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value) + ", " 197 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 198 | 199 | } 200 | } else if (relItem.value == 10) { 201 | if (timeValue == null) { 202 | timeValue = getTimeValue(context, 22, 0, "pm", null); 203 | suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " 204 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 205 | } else { 206 | suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " 207 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 208 | } 209 | } 210 | } else { 211 | super.build(context, suggestionValue, suggestionList); 212 | } 213 | } 214 | 215 | } 216 | -------------------------------------------------------------------------------- /library/src/main/java/com/pv/datetimeseer/SeerFilter.java: -------------------------------------------------------------------------------- 1 | package com.pv.datetimeseer; 2 | 3 | import android.content.Context; 4 | import android.text.TextUtils; 5 | import android.widget.Filter; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * @author p-v 12 | */ 13 | 14 | public class SeerFilter extends Filter { 15 | 16 | public interface OnSuggestionPublishListener { 17 | void onSuggestionPublish(List suggestionList); 18 | } 19 | 20 | private InitialSuggestionHandler initialSuggestionHandler; 21 | 22 | 23 | private Context context; 24 | private OnSuggestionPublishListener onSuggestionPublishListener; 25 | 26 | public SeerFilter(Context context, Config config) { 27 | 28 | this.context = context; 29 | 30 | // initialize all handlers 31 | initialSuggestionHandler = 32 | new InitialSuggestionHandler(config); 33 | NumberRelativeTimeSuggestionHandler numberRelativeTimeSuggestionHandler = new NumberRelativeTimeSuggestionHandler(config); 34 | RelativeTimeSuggestionHandler relativeTimeSuggestionHandler = new RelativeTimeSuggestionHandler(config); 35 | DateSuggestionHandler dateSuggestionHandler = new DateSuggestionHandler(config); 36 | DOWSuggestionHandler dowSuggestionHandler = new DOWSuggestionHandler(config); 37 | TimeSuggestionHandler timeSuggestionHandler = new TimeSuggestionHandler(config); 38 | TODSuggestionHandler todSuggestionHandler = new TODSuggestionHandler(config); 39 | 40 | // build handler chain 41 | initialSuggestionHandler.setNextHandler(numberRelativeTimeSuggestionHandler); 42 | numberRelativeTimeSuggestionHandler.setNextHandler(relativeTimeSuggestionHandler); 43 | relativeTimeSuggestionHandler.setNextHandler(dateSuggestionHandler); 44 | dateSuggestionHandler.setNextHandler(dowSuggestionHandler); 45 | dowSuggestionHandler.setNextHandler(timeSuggestionHandler); 46 | timeSuggestionHandler.setNextHandler(todSuggestionHandler); 47 | 48 | // build builder chain 49 | initialSuggestionHandler.setNextBuilder(timeSuggestionHandler); 50 | timeSuggestionHandler.setNextBuilder(todSuggestionHandler); 51 | todSuggestionHandler.setNextBuilder(numberRelativeTimeSuggestionHandler); 52 | numberRelativeTimeSuggestionHandler.setNextBuilder(relativeTimeSuggestionHandler); 53 | relativeTimeSuggestionHandler.setNextBuilder(dateSuggestionHandler); 54 | dateSuggestionHandler.setNextBuilder(dowSuggestionHandler); 55 | } 56 | 57 | public SeerFilter(Context context) { 58 | this(context, null); 59 | } 60 | 61 | @Override 62 | protected FilterResults performFiltering(CharSequence constraint) { 63 | final FilterResults results = new FilterResults(); 64 | if (TextUtils.isEmpty(constraint)) { 65 | // Return empty results. 66 | return results; 67 | } 68 | 69 | String input = constraint.toString(); 70 | String[] splitString = input.split("\\s+"); 71 | 72 | // Stores information about the user input 73 | SuggestionValue suggestionValue = new SuggestionValue(); 74 | 75 | // Interpret user input and store values in suggestion value 76 | initialSuggestionHandler.handle(context, input, splitString[splitString.length - 1], 77 | suggestionValue); 78 | 79 | List suggestionList = new ArrayList<>(3); 80 | 81 | // Save values in instance so `SparseArrayCompat#get` method is not 82 | // called again and again in the builders 83 | suggestionValue.init(); 84 | 85 | // Build suggestion list base on the user input (i.e. the suggestion value) 86 | initialSuggestionHandler.build(context, suggestionValue, suggestionList); 87 | 88 | // update result 89 | results.values = suggestionList; 90 | results.count = suggestionList.size(); 91 | return results; 92 | 93 | } 94 | 95 | @Override 96 | @SuppressWarnings("unchecked") 97 | protected void publishResults(CharSequence constraint, FilterResults results) { 98 | List suggestionList = null; 99 | if(results != null && results.count > 0) { 100 | suggestionList = (List) results.values; 101 | } 102 | if (onSuggestionPublishListener != null) { 103 | onSuggestionPublishListener.onSuggestionPublish(suggestionList); 104 | } 105 | } 106 | 107 | public void setOnSuggestionPublishListener(OnSuggestionPublishListener onSuggestionPublishListener) { 108 | this.onSuggestionPublishListener = onSuggestionPublishListener; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /library/src/main/java/com/pv/datetimeseer/SuggestionHandler.java: -------------------------------------------------------------------------------- 1 | package com.pv.datetimeseer; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | 6 | import java.text.DateFormat; 7 | import java.text.SimpleDateFormat; 8 | import java.util.Calendar; 9 | import java.util.List; 10 | import java.util.Locale; 11 | 12 | /** 13 | * Abstract Suggestion Handler 14 | * 15 | * Has all common methods use for displaying suggestions to the user 16 | * 17 | * The module is based on COR design pattern 18 | * 19 | * @author p-v 20 | */ 21 | abstract class SuggestionHandler { 22 | 23 | private SuggestionHandler nextHandler; 24 | private SuggestionHandler nextBuilder; 25 | Config config; 26 | 27 | static final int EVENING_TIME = 5; 28 | static final int AFTERNOON_TIME = 14 * 60; 29 | static final int MORNING_TIME_WEEKDAY = 9 * 60; 30 | static final int MORNING_TIME_WEEKEND = 10 * 60; 31 | static final int WEEKEND = Constants.Weekend.SATURDAY_SUNDAY; 32 | 33 | public SuggestionHandler(Config config) { 34 | if (config == null) { 35 | config = new Config.ConfigBuilder().build(); 36 | } 37 | this.config = config; 38 | } 39 | 40 | /** 41 | * Sets the next handler after the current handler 42 | * 43 | * @param nextHandler next handler after the current handler 44 | */ 45 | void setNextHandler(SuggestionHandler nextHandler) { 46 | this.nextHandler = nextHandler; 47 | } 48 | 49 | /** 50 | * Sets the next builder after the current builder 51 | * 52 | * @param nextBuilder next builder after the current builder 53 | */ 54 | void setNextBuilder(SuggestionHandler nextBuilder) { 55 | this.nextBuilder = nextBuilder; 56 | } 57 | 58 | /** 59 | * Build the suggestion list based on the suggestion value 60 | * 61 | * @param context The context to use. 62 | * @param suggestionValue The value used to build the suggestions. 63 | * @param suggestionList The list to add suggestions to. (pass empty the first time) 64 | */ 65 | public void build(Context context, SuggestionValue suggestionValue, List suggestionList) { 66 | if (nextBuilder != null) { 67 | nextBuilder.build(context, suggestionValue, suggestionList); 68 | } 69 | } 70 | 71 | /** 72 | * Interprets the input and converts it to SuggestionValue which can be used to build the 73 | * suggestion list 74 | * 75 | * @param context The context to use. 76 | * @param input User input. 77 | * @param lastToken Last token of the user input. 78 | * @param suggestionValue The value where all the related values are stored based on input (pass 79 | * empty the first time) 80 | */ 81 | public void handle(Context context, String input, String lastToken, SuggestionValue suggestionValue) { 82 | if (nextHandler != null) { 83 | nextHandler.handle(context, input, lastToken, suggestionValue); 84 | } 85 | } 86 | 87 | /** 88 | * Get time Value for the time passed 89 | * 90 | * @param context The context to use 91 | * @param hour Hour 92 | * @param mins Minutes 93 | * @param amPm am/pm String 94 | * 95 | * @return value which has display value and the real value 96 | */ 97 | final Value getTimeValue(Context context, int hour, int mins, String amPm, Calendar value) { 98 | 99 | if (value == null) { 100 | value = Calendar.getInstance(); 101 | } 102 | 103 | if (hour > 12) { 104 | if (amPm != null) { 105 | value.set(Calendar.HOUR_OF_DAY, hour); 106 | value.set(Calendar.MINUTE, mins); 107 | value.set(Calendar.SECOND, 0); 108 | value.set(Calendar.MILLISECOND, 0); 109 | String displayValue = DateTimeUtils.getDisplayTime(context, value, config); 110 | return new Value(displayValue, value); 111 | } else { 112 | value.set(Calendar.HOUR, hour % 12); 113 | value.set(Calendar.MINUTE, mins); 114 | value.set(Calendar.SECOND, 0); 115 | value.set(Calendar.MILLISECOND, 0); 116 | value.set(Calendar.AM_PM, Calendar.PM); 117 | String displayValue = DateTimeUtils.getDisplayTime(context, value, config); 118 | return new Value(displayValue, value); 119 | } 120 | } else if (hour < 12) { 121 | if (amPm != null) { 122 | value.set(Calendar.HOUR, hour); 123 | value.set(Calendar.MINUTE, mins); 124 | value.set(Calendar.SECOND, 0); 125 | value.set(Calendar.MILLISECOND, 0); 126 | value.set(Calendar.AM_PM, "pm".equals(amPm) ? Calendar.PM : Calendar.AM); 127 | String displayValue = DateTimeUtils.getDisplayTime(context, value, config); 128 | return new Value(displayValue, value); 129 | } else { 130 | value.set(Calendar.HOUR, hour % 12); 131 | value.set(Calendar.MINUTE, mins); 132 | value.set(Calendar.SECOND, 0); 133 | value.set(Calendar.MILLISECOND, 0); 134 | value.set(Calendar.AM_PM, Calendar.AM); 135 | String displayValue = DateTimeUtils.getDisplayTime(context, value, config); 136 | return new Value(displayValue, value); 137 | } 138 | } else { 139 | // 12 am/pm case 140 | if (amPm != null) { 141 | value.set(Calendar.HOUR_OF_DAY, hour); 142 | value.set(Calendar.MINUTE, mins); 143 | value.set(Calendar.SECOND, 0); 144 | value.set(Calendar.MILLISECOND, 0); 145 | String displayValue = DateTimeUtils.getDisplayTime(context, value, config); 146 | return new Value(displayValue, value); 147 | } else { 148 | value.set(Calendar.HOUR, hour % 12); 149 | value.set(Calendar.MINUTE, mins); 150 | value.set(Calendar.SECOND, 0); 151 | value.set(Calendar.MILLISECOND, 0); 152 | value.set(Calendar.AM_PM, Calendar.PM); 153 | String displayValue = DateTimeUtils.getDisplayTime(context, value, config); 154 | return new Value(displayValue, value); 155 | } 156 | } 157 | } 158 | 159 | /** 160 | * 161 | * Get time Value for the time/tod item passed 162 | * 163 | * Time of day item should never be null 164 | * 165 | * @param context The context to use 166 | * @param todItem Time of day item 167 | * @param timeItem Time item 168 | * @return return the Value having display and real value 169 | */ 170 | final Value getTimeValue(@NonNull Context context, @NonNull SuggestionValue.LocalItemItem todItem, TimeSuggestionHandler.TimeItem timeItem) { 171 | Value value = null; 172 | switch (todItem.value) { 173 | 174 | // Morning 175 | case TODSuggestionHandler.TOD_MORNING: 176 | if (timeItem == null) { 177 | Calendar cal = Calendar.getInstance(); 178 | if (DateTimeUtils.isWeekend(cal.get(Calendar.DAY_OF_WEEK), WEEKEND)) { 179 | int morningTime = MORNING_TIME_WEEKEND; 180 | int hour = morningTime / 60; 181 | int mins = morningTime % 60; 182 | value = getTimeValue(context, hour, mins, null, null); 183 | } else { 184 | int morningTime = MORNING_TIME_WEEKDAY; 185 | int hour = morningTime / 60; 186 | int mins = morningTime % 60; 187 | value = getTimeValue(context, hour, mins, null, null); 188 | } 189 | } else { 190 | int hour = timeItem.value / 60; 191 | int mins = timeItem.value % 60; 192 | 193 | if (hour < 12) { 194 | value = getTimeValue(context, hour, mins, "am", null); 195 | } else { 196 | value = getTimeValue(context, hour, mins, "pm", null); 197 | } 198 | } 199 | break; 200 | 201 | // Afternoon 202 | case TODSuggestionHandler.TOD_AFTERNOON: 203 | if (timeItem == null) { 204 | int afternoonTime = AFTERNOON_TIME; 205 | int hour = afternoonTime / 60; 206 | int mins = afternoonTime % 60; 207 | value = getTimeValue(context, hour, mins, null, null); 208 | } else { 209 | int hour = timeItem.value / 60; 210 | int mins = timeItem.value % 60; 211 | value = getTimeValue(context, hour, mins, "pm", null); 212 | } 213 | break; 214 | 215 | // Evening 216 | case TODSuggestionHandler.TOD_EVENING: 217 | if (timeItem == null) { 218 | value = getTimeValue(context, EVENING_TIME, 0, "pm", null); 219 | } else { 220 | int hour = timeItem.value / 60; 221 | int mins = timeItem.value % 60; 222 | value = getTimeValue(context, hour, mins, "pm", null); 223 | } 224 | break; 225 | 226 | // Night 227 | case TODSuggestionHandler.TOD_NIGHT: 228 | if (timeItem == null) { 229 | int nightTime = 10; 230 | value = getTimeValue(context, nightTime, 0, "pm", null); 231 | } else { 232 | int hour = timeItem.value / 60; 233 | int mins = timeItem.value % 60; 234 | int modHour = hour % 12; 235 | if (modHour >=0 && modHour < 4){ 236 | value = getTimeValue(context, hour, mins, "am", null); 237 | } else if (modHour >= 4 && modHour <= 6){ 238 | value = getTimeValue(context, 9, mins, "pm", null); 239 | } else { 240 | value = getTimeValue(context, hour, mins, "pm", null); 241 | } 242 | } 243 | break; 244 | 245 | default: 246 | break; 247 | 248 | } 249 | return value; 250 | } 251 | 252 | /** 253 | * Get the display string to be shown in the suggestions 254 | * 255 | * @param context The context to use 256 | * @param cal Calendar object to update 257 | * @param isRelative Is the time relative to the current time 258 | * @return Display string 259 | */ 260 | final String getDisplayDate(Context context, Calendar cal, boolean isRelative) { 261 | if (isRelative) { 262 | int days = DateTimeUtils.daysBetween(Calendar.getInstance(), cal); 263 | if (days == 0) { 264 | return context.getString(R.string.today) + ", " 265 | + DateTimeUtils.getDisplayTime(context, cal, config); 266 | } else if (days == 1) { 267 | return context.getString(R.string.tomorrow) + ", " 268 | + DateTimeUtils.getDisplayTime(context, cal, config); 269 | } else { 270 | return getDisplayDate(cal) + ", " + DateTimeUtils.getDisplayTime(context, cal, config); 271 | } 272 | } else { 273 | return getDisplayDate(cal); 274 | } 275 | } 276 | 277 | final String getDisplayDate(Context context, Calendar cal, String timeString) { 278 | int days = DateTimeUtils.daysBetween(Calendar.getInstance(), cal); 279 | if (days == 0) { 280 | return context.getString(R.string.today) + ", " + timeString; 281 | } else if (days == 1) { 282 | return context.getString(R.string.tomorrow) + ", " + timeString; 283 | } else { 284 | return getDisplayDate(cal) + ", " + timeString; 285 | } 286 | } 287 | 288 | /** 289 | * Get display date string from calendar in "EEEE, dd MMM" format, 290 | *
e.g. Mon, 12 Dec
Tue, 20 Jul 291 | * 292 | * @param cal Calendar to use 293 | * @return the display value 294 | */ 295 | final String getDisplayDate(Calendar cal) { 296 | if (Calendar.getInstance().get(Calendar.YEAR) == cal.get(Calendar.YEAR)) { 297 | DateFormat df = new SimpleDateFormat(config.getDateFormatWithoutYear(), Locale.ENGLISH); 298 | return df.format(cal.getTime()); 299 | } else { 300 | DateFormat df = new SimpleDateFormat(config.getDateFormatWithYear(), Locale.ENGLISH); 301 | return df.format(cal.getTime()); 302 | } 303 | } 304 | 305 | /** 306 | * Class used internally with all the handlers 307 | * to build and show suggestions 308 | */ 309 | protected class Value { 310 | 311 | String displayString; 312 | public Calendar value; 313 | 314 | public Value(String displayString, Calendar value) { 315 | this.displayString = displayString; 316 | this.value = value; 317 | } 318 | 319 | } 320 | 321 | } 322 | -------------------------------------------------------------------------------- /library/src/main/java/com/pv/datetimeseer/SuggestionRow.java: -------------------------------------------------------------------------------- 1 | package com.pv.datetimeseer; 2 | 3 | /** 4 | * @author p-v 5 | */ 6 | 7 | public class SuggestionRow { 8 | 9 | public static final int PARTIAL_VALUE = -999; 10 | 11 | private String displayValue; 12 | /** 13 | * Time in 14 | */ 15 | private int value; 16 | 17 | public SuggestionRow(String displayValue, int value) { 18 | this.displayValue = displayValue; 19 | this.value = value; 20 | } 21 | 22 | /** 23 | * Use this to get more results when the value is set to PARTIAL_VALUE. 24 | * 25 | * @return the display value.
26 | * 27 | */ 28 | public String getDisplayValue() { 29 | return displayValue; 30 | } 31 | 32 | /** 33 | * 34 | * @return the time in seconds 35 | */ 36 | public int getValue() { 37 | return value; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /library/src/main/java/com/pv/datetimeseer/SuggestionValue.java: -------------------------------------------------------------------------------- 1 | package com.pv.datetimeseer; 2 | 3 | import android.support.v4.util.SparseArrayCompat; 4 | 5 | /** 6 | * Values regarding the user input 7 | * 8 | * @author p-v 9 | */ 10 | class SuggestionValue extends SparseArrayCompat { 11 | 12 | static final int RELATIVE_DAY = 0x01; 13 | static final int DAY_OF_WEEK = 0x02; 14 | static final int TIME_OF_DAY = 0x04; 15 | static final int MONTH = 0x08; 16 | static final int NUMBER = 0x10; 17 | static final int TIME = 0x20; 18 | static final int DATE = 0x40; 19 | static final int DAY_OF_WEEK_NEXT = 0x80; 20 | static final int RELATIVE_DAY_NUMBER = 0x0100; 21 | static final int OTHER = 0x0200; 22 | 23 | private TimeSuggestionHandler.TimeItem timeItem; 24 | private LocalItemItem todItem; 25 | private RelativeTimeSuggestionHandler.RelativeDayItem relDayItem; 26 | private NumberRelativeTimeSuggestionHandler.RelativeDayNumItem relativeDayNumItem; 27 | private LocalItemItem dowItem; 28 | private LocalItemItem nextDowItem; 29 | private LocalItemItem monthItem; 30 | private DateSuggestionHandler.DateItem dateItem; 31 | private LocalItemItem numberItem; 32 | private LocalItemItem otherItem; 33 | 34 | void init() { 35 | relDayItem = (RelativeTimeSuggestionHandler.RelativeDayItem) this.get(RELATIVE_DAY); 36 | relativeDayNumItem = (NumberRelativeTimeSuggestionHandler.RelativeDayNumItem) this.get(RELATIVE_DAY_NUMBER); 37 | dowItem = this.get(DAY_OF_WEEK); 38 | nextDowItem = this.get(DAY_OF_WEEK_NEXT); 39 | monthItem = this.get(MONTH); 40 | dateItem = (DateSuggestionHandler.DateItem) this.get(DATE); 41 | todItem = this.get(TIME_OF_DAY); 42 | timeItem = (TimeSuggestionHandler.TimeItem) this.get(TIME); 43 | numberItem = this.get(NUMBER); 44 | otherItem = this.get(OTHER); 45 | } 46 | 47 | public void appendSuggestion(int flag, int value) { 48 | super.append(flag, new LocalItemItem(value)); 49 | } 50 | 51 | public void appendSuggestion(int flag, LocalItemItem item) { 52 | super.append(flag, item); 53 | } 54 | 55 | public static class LocalItemItem { 56 | 57 | public int value; 58 | 59 | public LocalItemItem(int value) { 60 | this.value = value; 61 | } 62 | 63 | } 64 | 65 | public TimeSuggestionHandler.TimeItem getTimeItem() { 66 | return timeItem; 67 | } 68 | 69 | public LocalItemItem getTodItem() { 70 | return todItem; 71 | } 72 | 73 | public RelativeTimeSuggestionHandler.RelativeDayItem getRelDayItem() { 74 | return relDayItem; 75 | } 76 | 77 | public LocalItemItem getDowItem() { 78 | return dowItem; 79 | } 80 | 81 | public LocalItemItem getNextDowItem() { 82 | return nextDowItem; 83 | } 84 | 85 | public LocalItemItem getMonthItem() { 86 | return monthItem; 87 | } 88 | 89 | public DateSuggestionHandler.DateItem getDateItem() { 90 | return dateItem; 91 | } 92 | 93 | public NumberRelativeTimeSuggestionHandler.RelativeDayNumItem getRelativeDayNumItem() { 94 | return relativeDayNumItem; 95 | } 96 | 97 | public LocalItemItem getNumberItem() { 98 | return numberItem; 99 | } 100 | 101 | public LocalItemItem getOtherItem() { 102 | return otherItem; 103 | } 104 | 105 | 106 | } 107 | -------------------------------------------------------------------------------- /library/src/main/java/com/pv/datetimeseer/TODSuggestionHandler.java: -------------------------------------------------------------------------------- 1 | package com.pv.datetimeseer; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.Calendar; 6 | import java.util.List; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * @author p-v 12 | */ 13 | class TODSuggestionHandler extends SuggestionHandler { 14 | 15 | static final int TOD_MORNING = 1; 16 | static final int TOD_AFTERNOON = 2; 17 | static final int TOD_EVENING = 3; 18 | static final int TOD_NIGHT = 4; 19 | 20 | private static final String REGEX = "\\b(?:(morn(?:i(?:n(?:g)?)?)?)|(after(?=(?:\\S+|$))(?:n(?:o(?:o(?:n)?)?)?)?)|(even(?:i(?:n(?:g)?)?)?)|(ni(?:g(?:h(?:t)?)?)?))\\b"; 21 | private Pattern pTod; 22 | 23 | TODSuggestionHandler(Config config) { 24 | super(config); 25 | pTod = Pattern.compile(REGEX, Pattern.CASE_INSENSITIVE); 26 | } 27 | 28 | @Override 29 | public void handle(Context context, String input, String lastToken, SuggestionValue suggestionValue) { 30 | Matcher matcher = pTod.matcher(input); 31 | if (matcher.find()) { 32 | int value; 33 | if (matcher.group(1) != null) { 34 | value = TOD_MORNING; 35 | } else if (matcher.group(2) != null) { 36 | value = TOD_AFTERNOON; 37 | } else if(matcher.group(3) != null) { 38 | value = TOD_EVENING; 39 | } else { 40 | value = TOD_NIGHT; 41 | } 42 | suggestionValue.appendSuggestion(SuggestionValue.TIME_OF_DAY, value); 43 | } 44 | super.handle(context, input, lastToken, suggestionValue); 45 | } 46 | 47 | @Override 48 | public void build(Context context, SuggestionValue suggestionValue, List suggestionList) { 49 | SuggestionValue.LocalItemItem relItem = suggestionValue.getRelDayItem(); 50 | SuggestionValue.LocalItemItem dowItem = suggestionValue.getDowItem(); 51 | SuggestionValue.LocalItemItem nextDowItem = suggestionValue.getNextDowItem(); 52 | SuggestionValue.LocalItemItem monthItem = suggestionValue.getMonthItem(); 53 | SuggestionValue.LocalItemItem dateItem = suggestionValue.getDateItem(); 54 | TimeSuggestionHandler.TimeItem timeItem = suggestionValue.getTimeItem(); 55 | NumberRelativeTimeSuggestionHandler.RelativeDayNumItem 56 | relNumItem = suggestionValue.getRelativeDayNumItem(); 57 | SuggestionValue.LocalItemItem todItem = suggestionValue.getTodItem(); 58 | 59 | // Ignoring time timeItem. Using it later in the method 60 | boolean hasOnlyTime = relItem == null && dowItem == null && nextDowItem == null && 61 | monthItem == null && dateItem == null && relNumItem == null && todItem != null; 62 | 63 | if (hasOnlyTime) { 64 | Value timeValue = getTimeValue(context, todItem, timeItem); 65 | if (timeValue != null) { 66 | if (timeItem == null || !timeItem.isAmPmPresent) { 67 | suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " 68 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 69 | 70 | // increment a day for tomorrow 71 | timeValue.value.add(Calendar.DAY_OF_YEAR, 1); 72 | suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " 73 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 74 | 75 | // increment a day for day after tomorrow 76 | timeValue.value.add(Calendar.DAY_OF_YEAR, 1); 77 | suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value) + ", " 78 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 79 | } else { 80 | // Time present with AM/PM 81 | int hour = timeItem.value / 60; 82 | int mins = timeItem.value % 60; 83 | timeValue = getTimeValue(context, hour, mins, null, null); 84 | 85 | suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " 86 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 87 | 88 | // increment a day for tomorrow 89 | timeValue.value.add(Calendar.DAY_OF_YEAR, 1); 90 | suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " 91 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 92 | 93 | // increment a day for day after tomorrow 94 | timeValue.value.add(Calendar.DAY_OF_YEAR, 1); 95 | suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value) + ", " 96 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 97 | } 98 | } 99 | } else { 100 | super.build(context, suggestionValue, suggestionList); 101 | } 102 | 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /library/src/main/java/com/pv/datetimeseer/TimeSuggestionHandler.java: -------------------------------------------------------------------------------- 1 | package com.pv.datetimeseer; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.Calendar; 6 | import java.util.List; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * Suggestion handler for handling time information.
12 | * Handles strings like 6:30, 6 40, 3pm etc. 13 | * 14 | * @author p-v 15 | */ 16 | class TimeSuggestionHandler extends SuggestionHandler { 17 | 18 | private static final String TIME_RGX = "\\b((?:2[0-3])|(?:1\\d)|(?:0?\\d))(?:(?::|\\s)((?:0?\\d)|(?:[0-5][0-9]?)))?\\s{0,2}([ap](?:\\.?m\\.?)?)?\\b"; 19 | private Pattern timePattern; 20 | 21 | /** 22 | * Time item class. 23 | */ 24 | class TimeItem extends SuggestionValue.LocalItemItem { 25 | 26 | boolean isAmPmPresent; 27 | 28 | TimeItem(int value, boolean amPmPresent) { 29 | super(value); 30 | this.isAmPmPresent = amPmPresent; 31 | } 32 | } 33 | 34 | TimeSuggestionHandler(Config config) { 35 | super(config); 36 | timePattern = Pattern.compile(TIME_RGX, Pattern.CASE_INSENSITIVE); 37 | } 38 | 39 | @Override 40 | public void handle(Context context, String input, String lastToken, SuggestionValue suggestionValue) { 41 | 42 | DateSuggestionHandler.DateItem dateSuggestion = 43 | (DateSuggestionHandler.DateItem) suggestionValue.get(SuggestionValue.DATE); 44 | NumberRelativeTimeSuggestionHandler.RelativeDayNumItem numItem = 45 | (NumberRelativeTimeSuggestionHandler.RelativeDayNumItem) suggestionValue.get(SuggestionValue.RELATIVE_DAY_NUMBER); 46 | 47 | StringBuilder builder = new StringBuilder(input); 48 | if (numItem != null) { 49 | builder.replace(numItem.startIdx, numItem.endIdx, ""); 50 | } else if (dateSuggestion != null) { 51 | builder.replace(dateSuggestion.startIdx, dateSuggestion.endIdx, ""); 52 | } 53 | 54 | Matcher matcher = timePattern.matcher(builder.toString()); 55 | 56 | while (matcher.find()) { 57 | int hourOfDay = Integer.parseInt(matcher.group(1)); 58 | int mins = 0; 59 | String minsStr = matcher.group(2); 60 | String amPm = matcher.group(3); 61 | 62 | if (minsStr != null) { 63 | mins = Integer.parseInt(minsStr); 64 | } 65 | if (hourOfDay < 12) { 66 | if (amPm != null && amPm.matches("(?i)^p.*")) { 67 | hourOfDay = hourOfDay + 12; 68 | } 69 | } else if (hourOfDay == 12) { 70 | if (amPm != null && amPm.matches("(?i)^a.*")) { 71 | hourOfDay = 0; 72 | } 73 | } 74 | int minsInDay = hourOfDay * 60 + mins; 75 | 76 | suggestionValue.appendSuggestion(SuggestionValue.TIME, new TimeItem(minsInDay, amPm != null)); 77 | } 78 | 79 | super.handle(context, input, lastToken, suggestionValue); 80 | } 81 | 82 | @Override 83 | public void build(Context context, SuggestionValue suggestionValue, List suggestionList) { 84 | SuggestionValue.LocalItemItem relItem = suggestionValue.getRelDayItem(); 85 | SuggestionValue.LocalItemItem dowItem = suggestionValue.getDowItem(); 86 | SuggestionValue.LocalItemItem nextDowItem = suggestionValue.getNextDowItem(); 87 | SuggestionValue.LocalItemItem monthItem = suggestionValue.getMonthItem(); 88 | SuggestionValue.LocalItemItem dateItem = suggestionValue.getDateItem(); 89 | SuggestionValue.LocalItemItem todItem = suggestionValue.getTodItem(); 90 | SuggestionValue.LocalItemItem relItemNum = suggestionValue.getRelativeDayNumItem(); 91 | 92 | TimeItem timeItem = suggestionValue.getTimeItem(); 93 | 94 | boolean hasOnlyTime = relItem == null && dowItem == null && nextDowItem == null && 95 | monthItem == null && dateItem == null && todItem == null && relItemNum == null 96 | && timeItem != null; 97 | 98 | if (hasOnlyTime) { 99 | Value timeValue; 100 | int hour = timeItem.value / 60; 101 | int mins = timeItem.value % 60; 102 | 103 | Calendar cal = Calendar.getInstance(); 104 | final int currentHourOfDay = cal.get(Calendar.HOUR_OF_DAY); 105 | final int currentMinsOfHour = cal.get(Calendar.MINUTE); 106 | 107 | if (timeItem.isAmPmPresent) { 108 | timeValue = getTimeValue(context, hour, mins, null, null); 109 | if (!(currentHourOfDay > hour || currentHourOfDay == hour && currentMinsOfHour > mins)) { 110 | suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " 111 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 112 | } 113 | 114 | // increment a day for tomorrow 115 | timeValue.value.add(Calendar.DAY_OF_YEAR, 1); 116 | suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " 117 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 118 | 119 | // increment a day for day after tomorrow 120 | timeValue.value.add(Calendar.DAY_OF_YEAR, 1); 121 | suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value)+ ", " 122 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 123 | } else { 124 | // if the current time is more than the entered time 125 | if (currentHourOfDay > hour || currentHourOfDay == hour && currentMinsOfHour > mins) { 126 | 127 | timeValue = getTimeValue(context, hour, mins, null, null); 128 | 129 | int calculatedHourForToday = timeValue.value.get(Calendar.HOUR_OF_DAY); 130 | int calculatedMinsForToday = timeValue.value.get(Calendar.MINUTE); 131 | // Check if the calculated time for today has past 132 | if (calculatedHourForToday > currentHourOfDay || 133 | calculatedHourForToday == currentHourOfDay && calculatedMinsForToday > currentMinsOfHour) { 134 | suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " 135 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 136 | } 137 | // increment a day for tomorrow 138 | timeValue.value.add(Calendar.DAY_OF_YEAR, 1); 139 | suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " 140 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 141 | 142 | // increment a day for day after tomorrow 143 | timeValue.value.add(Calendar.DAY_OF_YEAR, 1); 144 | suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value)+ ", " 145 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 146 | } else { 147 | timeValue = getTimeValue(context, hour, mins, null, null); 148 | suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " 149 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 150 | 151 | // increment a day for tomorrow 152 | timeValue.value.add(Calendar.DAY_OF_YEAR, 1); 153 | 154 | suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " 155 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 156 | 157 | // increment a day for day after tomorrow 158 | timeValue.value.add(Calendar.DAY_OF_YEAR, 1); 159 | suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value)+ ", " 160 | + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); 161 | } 162 | } 163 | } else { 164 | super.build(context, suggestionValue, suggestionList); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DateTimeSeer 3 | Today 4 | Tomorrow 5 | 1 month ago 6 | %d months ago 7 | 1 year ago 8 | %d years ago 9 | %d hours 10 | %d hour 11 | %d minutes 12 | %d minute 13 | 1 day 14 | %d days 15 | 16 | -------------------------------------------------------------------------------- /library/src/test/java/com/pv/datetimeseer/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.pv.datetimeseer; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | --------------------------------------------------------------------------------