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

* * e.g.
after 6 hours
* 6mins
* 10 days
* after 2 months
* * @author p-v */ class NumberRelativeTimeSuggestionHandler extends SuggestionHandler { 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"; private Pattern pRel; class RelativeDayNumItem extends SuggestionValue.LocalItemItem { static final int DAY = 1; static final int HOUR = 2; static final int MIN = 3; static final int WEEK = 4; static final int MONTH = 5; int type; int startIdx; int endIdx; RelativeDayNumItem(int value, int type, int startIdx, int endIdx) { super(value); this.type = type; this.startIdx = startIdx; this.endIdx = endIdx; } } NumberRelativeTimeSuggestionHandler(Config config){ super(config); pRel = Pattern.compile(REGEX, Pattern.CASE_INSENSITIVE); } @Override public void handle(Context context, String input, String lastToken, SuggestionValue suggestionValue) { Matcher m = pRel.matcher(input); if (m.find()) { int digit; if (m.group(1) != null) { digit = Integer.parseInt(m.group(1)); } else { // group 2 i.e. "next" isn't null digit = 1; } int type = -1; if (m.group(2) != null) { type = RelativeDayNumItem.MONTH; } else if (m.group(3) != null) { type = RelativeDayNumItem.MIN; } else if (m.group(4) != null) { type = RelativeDayNumItem.WEEK; } else if (m.group(5) != null) { type = RelativeDayNumItem.DAY; } else if (m.group(6) != null) { type = RelativeDayNumItem.HOUR; } if (type != -1) { suggestionValue.appendSuggestion(SuggestionValue.RELATIVE_DAY_NUMBER, new RelativeDayNumItem(digit, type, m.start(), m.end())); if (type == RelativeDayNumItem.HOUR || type == RelativeDayNumItem.MIN) { // ignore rest of the handlers if hour/min found return; } } } super.handle(context, input, lastToken, suggestionValue); } @Override public void build(Context context, SuggestionValue suggestionValue, List suggestionList) { RelativeDayNumItem relNumItem = suggestionValue.getRelativeDayNumItem(); if (relNumItem != null) { Calendar calendar = Calendar.getInstance(); boolean isPartial = false; switch (relNumItem.type) { case RelativeDayNumItem.DAY: isPartial = true; calendar.add(Calendar.DAY_OF_YEAR, relNumItem.value); break; case RelativeDayNumItem.HOUR: calendar.add(Calendar.HOUR_OF_DAY, relNumItem.value); break; case RelativeDayNumItem.MIN: calendar.add(Calendar.MINUTE, relNumItem.value); break; case RelativeDayNumItem.WEEK: isPartial = true; calendar.add(Calendar.WEEK_OF_YEAR, relNumItem.value); break; case RelativeDayNumItem.MONTH: isPartial = true; calendar.add(Calendar.MONTH, relNumItem.value); break; } if (relNumItem.type != RelativeDayNumItem.HOUR && relNumItem.type != RelativeDayNumItem.MIN) { TimeSuggestionHandler.TimeItem timeItem = suggestionValue.getTimeItem(); if (timeItem != null) { int hour = timeItem.value / 60; int mins = timeItem.value % 60; calendar.set(Calendar.HOUR_OF_DAY, hour); calendar.set(Calendar.MINUTE, mins); } } if (isPartial) { suggestionList.add(new SuggestionRow(DateTimeUtils.getDisplayDate(calendar, config), SuggestionRow.PARTIAL_VALUE)); } else { suggestionList.add(new SuggestionRow(getDisplayDate(context, calendar, true), (int) (calendar.getTimeInMillis()/1000))); } } else { super.build(context, suggestionValue, suggestionList); } } } ================================================ FILE: library/src/main/java/com/pv/datetimeseer/RelativeTimeSuggestionHandler.java ================================================ package com.pv.datetimeseer; import android.content.Context; import java.util.Calendar; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author p-v */ class RelativeTimeSuggestionHandler extends SuggestionHandler { private static final String REGEX = "\\b(?:(tod(?:a(?:y)?)?)|(tom(?:o(?:r(?:r(?:o(?:w)?)?)?)?)?)" + "|(day after tomorrow)|((?:ton(?:i(?:g(?:(?:h)?t)?)?)?)|(?:ton(?:i(?:t(?:e)?)?)?)))\\b"; private Pattern pRel; RelativeTimeSuggestionHandler(Config config) { super(config); pRel = Pattern.compile(REGEX, Pattern.CASE_INSENSITIVE); } class RelativeDayItem extends SuggestionValue.LocalItemItem { boolean isPartial; RelativeDayItem(int value, boolean isPartial) { super(value); this.isPartial = isPartial; } } @Override public void handle(Context context, String input, String lastToken, SuggestionValue suggestionValue) { Matcher m = pRel.matcher(input); String text; if (m.find()) { if ((text = m.group(1)) != null) { suggestionValue.appendSuggestion(SuggestionValue.RELATIVE_DAY, new RelativeDayItem(0, !"today".equalsIgnoreCase(text.trim()))); } else if ((text = m.group(2)) != null) { suggestionValue.appendSuggestion(SuggestionValue.RELATIVE_DAY, new RelativeDayItem(1, !"tomorrow".equalsIgnoreCase(text.trim()))); } else if (m.group(3) != null){ suggestionValue.appendSuggestion(SuggestionValue.RELATIVE_DAY, new RelativeDayItem(2, false)); } else { suggestionValue.appendSuggestion(SuggestionValue.RELATIVE_DAY, new RelativeDayItem(10, false)); } } super.handle(context, input, lastToken, suggestionValue); } @Override public void build(Context context, SuggestionValue suggestionValue, List suggestionList) { RelativeDayItem relItem = suggestionValue.getRelDayItem(); if (relItem != null) { SuggestionValue.LocalItemItem todItem = suggestionValue.getTodItem(); TimeSuggestionHandler.TimeItem timeItem = suggestionValue.getTimeItem(); Value timeValue = null; if (todItem != null || timeItem != null) { if (todItem == null) { // timeItem is not null here int hour = timeItem.value / 60; int mins = timeItem.value % 60; timeValue = getTimeValue(context, hour, mins, null, null); } else { timeValue = getTimeValue(context, todItem, timeItem); } } // handle relative value if (relItem.value == 0) { // For today if (timeValue == null) { if (!relItem.isPartial) { int afternoonTime = AFTERNOON_TIME; // time is not specified Calendar cal = Calendar.getInstance(); int currentHourOfDay = cal.get(Calendar.HOUR_OF_DAY); int afternoonHour = afternoonTime / 60; int afternoonMins = afternoonTime % 60; int eveningHour = 12 + EVENING_TIME; if (currentHourOfDay < 23) { timeValue = getTimeValue(context, currentHourOfDay + 1, 0, null, null); // Current time is less than 11PM suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } if (currentHourOfDay < afternoonHour && currentHourOfDay + 1 != afternoonHour) { timeValue = getTimeValue(context, afternoonHour, afternoonMins, null, null); suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } if (currentHourOfDay < eveningHour && currentHourOfDay +1 != eveningHour) { timeValue = getTimeValue(context, eveningHour, 0, "pm", null); suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } } else { suggestionList.add(new SuggestionRow(context.getString(R.string.today), SuggestionRow.PARTIAL_VALUE)); } } else { Calendar cal = Calendar.getInstance(); if (timeValue.value.before(cal)) { // time past increment time by 12 hours if AM/PM is not specified otherwise by 24 if (timeItem.isAmPmPresent) { timeValue.value.add(Calendar.HOUR_OF_DAY, 24); suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " + DateTimeUtils.getDisplayTime(context, timeValue.value, config), (int)(timeValue.value.getTimeInMillis()/1000))); } else { timeValue.value.add(Calendar.HOUR_OF_DAY, 12); if (timeValue.value.before(cal)) { timeValue.value.add(Calendar.HOUR_OF_DAY, 12); suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " + DateTimeUtils.getDisplayTime(context, timeValue.value, config), (int)(timeValue.value.getTimeInMillis()/1000))); } else { if (cal.get(Calendar.DAY_OF_YEAR) == timeValue.value.get(Calendar.DAY_OF_YEAR)) { suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " + DateTimeUtils.getDisplayTime(context, timeValue.value, config), (int)(timeValue.value.getTimeInMillis()/1000))); } else { suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " + DateTimeUtils.getDisplayTime(context, timeValue.value, config), (int)(timeValue.value.getTimeInMillis()/1000))); } } } } else { suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } } } else if (relItem.value == 1) { if (timeValue == null) { if (!relItem.isPartial) { int morningTime = MORNING_TIME_WEEKDAY; timeValue = getTimeValue(context, morningTime / 60, morningTime % 60, null, null); // increment a day for tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 1); suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); int afternoonTime = AFTERNOON_TIME; timeValue = getTimeValue(context, afternoonTime / 60, afternoonTime % 60, null, null); // increment a day for tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 1); suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); int eveningHour = 12 + EVENING_TIME; timeValue = getTimeValue(context, eveningHour, 0, "pm", null); // increment a day for tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 1); suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } else { suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow), SuggestionRow.PARTIAL_VALUE)); } } else { // increment a day for tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 1); suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } } else if (relItem.value == 2) { if (timeValue == null) { int morningTime = MORNING_TIME_WEEKDAY; timeValue = getTimeValue(context, morningTime / 60, morningTime % 60, null, null); // increment two days for day after tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 2); suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); int afternoonTime = AFTERNOON_TIME; timeValue = getTimeValue(context, afternoonTime / 60, afternoonTime % 60, null, null); // increment two days for day after tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 2); suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); int eveningHour = 12 + EVENING_TIME; timeValue = getTimeValue(context, eveningHour, 0, "pm", null); // increment two days for day after tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 2); suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } else { // increment two days for day after tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 2); suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } } else if (relItem.value == 10) { if (timeValue == null) { timeValue = getTimeValue(context, 22, 0, "pm", null); suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } else { suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } } } else { super.build(context, suggestionValue, suggestionList); } } } ================================================ FILE: library/src/main/java/com/pv/datetimeseer/SeerFilter.java ================================================ package com.pv.datetimeseer; import android.content.Context; import android.text.TextUtils; import android.widget.Filter; import java.util.ArrayList; import java.util.List; /** * @author p-v */ public class SeerFilter extends Filter { public interface OnSuggestionPublishListener { void onSuggestionPublish(List suggestionList); } private InitialSuggestionHandler initialSuggestionHandler; private Context context; private OnSuggestionPublishListener onSuggestionPublishListener; public SeerFilter(Context context, Config config) { this.context = context; // initialize all handlers initialSuggestionHandler = new InitialSuggestionHandler(config); NumberRelativeTimeSuggestionHandler numberRelativeTimeSuggestionHandler = new NumberRelativeTimeSuggestionHandler(config); RelativeTimeSuggestionHandler relativeTimeSuggestionHandler = new RelativeTimeSuggestionHandler(config); DateSuggestionHandler dateSuggestionHandler = new DateSuggestionHandler(config); DOWSuggestionHandler dowSuggestionHandler = new DOWSuggestionHandler(config); TimeSuggestionHandler timeSuggestionHandler = new TimeSuggestionHandler(config); TODSuggestionHandler todSuggestionHandler = new TODSuggestionHandler(config); // build handler chain initialSuggestionHandler.setNextHandler(numberRelativeTimeSuggestionHandler); numberRelativeTimeSuggestionHandler.setNextHandler(relativeTimeSuggestionHandler); relativeTimeSuggestionHandler.setNextHandler(dateSuggestionHandler); dateSuggestionHandler.setNextHandler(dowSuggestionHandler); dowSuggestionHandler.setNextHandler(timeSuggestionHandler); timeSuggestionHandler.setNextHandler(todSuggestionHandler); // build builder chain initialSuggestionHandler.setNextBuilder(timeSuggestionHandler); timeSuggestionHandler.setNextBuilder(todSuggestionHandler); todSuggestionHandler.setNextBuilder(numberRelativeTimeSuggestionHandler); numberRelativeTimeSuggestionHandler.setNextBuilder(relativeTimeSuggestionHandler); relativeTimeSuggestionHandler.setNextBuilder(dateSuggestionHandler); dateSuggestionHandler.setNextBuilder(dowSuggestionHandler); } public SeerFilter(Context context) { this(context, null); } @Override protected FilterResults performFiltering(CharSequence constraint) { final FilterResults results = new FilterResults(); if (TextUtils.isEmpty(constraint)) { // Return empty results. return results; } String input = constraint.toString(); String[] splitString = input.split("\\s+"); // Stores information about the user input SuggestionValue suggestionValue = new SuggestionValue(); // Interpret user input and store values in suggestion value initialSuggestionHandler.handle(context, input, splitString[splitString.length - 1], suggestionValue); List suggestionList = new ArrayList<>(3); // Save values in instance so `SparseArrayCompat#get` method is not // called again and again in the builders suggestionValue.init(); // Build suggestion list base on the user input (i.e. the suggestion value) initialSuggestionHandler.build(context, suggestionValue, suggestionList); // update result results.values = suggestionList; results.count = suggestionList.size(); return results; } @Override @SuppressWarnings("unchecked") protected void publishResults(CharSequence constraint, FilterResults results) { List suggestionList = null; if(results != null && results.count > 0) { suggestionList = (List) results.values; } if (onSuggestionPublishListener != null) { onSuggestionPublishListener.onSuggestionPublish(suggestionList); } } public void setOnSuggestionPublishListener(OnSuggestionPublishListener onSuggestionPublishListener) { this.onSuggestionPublishListener = onSuggestionPublishListener; } } ================================================ FILE: library/src/main/java/com/pv/datetimeseer/SuggestionHandler.java ================================================ package com.pv.datetimeseer; import android.content.Context; import android.support.annotation.NonNull; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.List; import java.util.Locale; /** * Abstract Suggestion Handler * * Has all common methods use for displaying suggestions to the user * * The module is based on COR design pattern * * @author p-v */ abstract class SuggestionHandler { private SuggestionHandler nextHandler; private SuggestionHandler nextBuilder; Config config; static final int EVENING_TIME = 5; static final int AFTERNOON_TIME = 14 * 60; static final int MORNING_TIME_WEEKDAY = 9 * 60; static final int MORNING_TIME_WEEKEND = 10 * 60; static final int WEEKEND = Constants.Weekend.SATURDAY_SUNDAY; public SuggestionHandler(Config config) { if (config == null) { config = new Config.ConfigBuilder().build(); } this.config = config; } /** * Sets the next handler after the current handler * * @param nextHandler next handler after the current handler */ void setNextHandler(SuggestionHandler nextHandler) { this.nextHandler = nextHandler; } /** * Sets the next builder after the current builder * * @param nextBuilder next builder after the current builder */ void setNextBuilder(SuggestionHandler nextBuilder) { this.nextBuilder = nextBuilder; } /** * Build the suggestion list based on the suggestion value * * @param context The context to use. * @param suggestionValue The value used to build the suggestions. * @param suggestionList The list to add suggestions to. (pass empty the first time) */ public void build(Context context, SuggestionValue suggestionValue, List suggestionList) { if (nextBuilder != null) { nextBuilder.build(context, suggestionValue, suggestionList); } } /** * Interprets the input and converts it to SuggestionValue which can be used to build the * suggestion list * * @param context The context to use. * @param input User input. * @param lastToken Last token of the user input. * @param suggestionValue The value where all the related values are stored based on input (pass * empty the first time) */ public void handle(Context context, String input, String lastToken, SuggestionValue suggestionValue) { if (nextHandler != null) { nextHandler.handle(context, input, lastToken, suggestionValue); } } /** * Get time Value for the time passed * * @param context The context to use * @param hour Hour * @param mins Minutes * @param amPm am/pm String * * @return value which has display value and the real value */ final Value getTimeValue(Context context, int hour, int mins, String amPm, Calendar value) { if (value == null) { value = Calendar.getInstance(); } if (hour > 12) { if (amPm != null) { value.set(Calendar.HOUR_OF_DAY, hour); value.set(Calendar.MINUTE, mins); value.set(Calendar.SECOND, 0); value.set(Calendar.MILLISECOND, 0); String displayValue = DateTimeUtils.getDisplayTime(context, value, config); return new Value(displayValue, value); } else { value.set(Calendar.HOUR, hour % 12); value.set(Calendar.MINUTE, mins); value.set(Calendar.SECOND, 0); value.set(Calendar.MILLISECOND, 0); value.set(Calendar.AM_PM, Calendar.PM); String displayValue = DateTimeUtils.getDisplayTime(context, value, config); return new Value(displayValue, value); } } else if (hour < 12) { if (amPm != null) { value.set(Calendar.HOUR, hour); value.set(Calendar.MINUTE, mins); value.set(Calendar.SECOND, 0); value.set(Calendar.MILLISECOND, 0); value.set(Calendar.AM_PM, "pm".equals(amPm) ? Calendar.PM : Calendar.AM); String displayValue = DateTimeUtils.getDisplayTime(context, value, config); return new Value(displayValue, value); } else { value.set(Calendar.HOUR, hour % 12); value.set(Calendar.MINUTE, mins); value.set(Calendar.SECOND, 0); value.set(Calendar.MILLISECOND, 0); value.set(Calendar.AM_PM, Calendar.AM); String displayValue = DateTimeUtils.getDisplayTime(context, value, config); return new Value(displayValue, value); } } else { // 12 am/pm case if (amPm != null) { value.set(Calendar.HOUR_OF_DAY, hour); value.set(Calendar.MINUTE, mins); value.set(Calendar.SECOND, 0); value.set(Calendar.MILLISECOND, 0); String displayValue = DateTimeUtils.getDisplayTime(context, value, config); return new Value(displayValue, value); } else { value.set(Calendar.HOUR, hour % 12); value.set(Calendar.MINUTE, mins); value.set(Calendar.SECOND, 0); value.set(Calendar.MILLISECOND, 0); value.set(Calendar.AM_PM, Calendar.PM); String displayValue = DateTimeUtils.getDisplayTime(context, value, config); return new Value(displayValue, value); } } } /** * * Get time Value for the time/tod item passed * * Time of day item should never be null * * @param context The context to use * @param todItem Time of day item * @param timeItem Time item * @return return the Value having display and real value */ final Value getTimeValue(@NonNull Context context, @NonNull SuggestionValue.LocalItemItem todItem, TimeSuggestionHandler.TimeItem timeItem) { Value value = null; switch (todItem.value) { // Morning case TODSuggestionHandler.TOD_MORNING: if (timeItem == null) { Calendar cal = Calendar.getInstance(); if (DateTimeUtils.isWeekend(cal.get(Calendar.DAY_OF_WEEK), WEEKEND)) { int morningTime = MORNING_TIME_WEEKEND; int hour = morningTime / 60; int mins = morningTime % 60; value = getTimeValue(context, hour, mins, null, null); } else { int morningTime = MORNING_TIME_WEEKDAY; int hour = morningTime / 60; int mins = morningTime % 60; value = getTimeValue(context, hour, mins, null, null); } } else { int hour = timeItem.value / 60; int mins = timeItem.value % 60; if (hour < 12) { value = getTimeValue(context, hour, mins, "am", null); } else { value = getTimeValue(context, hour, mins, "pm", null); } } break; // Afternoon case TODSuggestionHandler.TOD_AFTERNOON: if (timeItem == null) { int afternoonTime = AFTERNOON_TIME; int hour = afternoonTime / 60; int mins = afternoonTime % 60; value = getTimeValue(context, hour, mins, null, null); } else { int hour = timeItem.value / 60; int mins = timeItem.value % 60; value = getTimeValue(context, hour, mins, "pm", null); } break; // Evening case TODSuggestionHandler.TOD_EVENING: if (timeItem == null) { value = getTimeValue(context, EVENING_TIME, 0, "pm", null); } else { int hour = timeItem.value / 60; int mins = timeItem.value % 60; value = getTimeValue(context, hour, mins, "pm", null); } break; // Night case TODSuggestionHandler.TOD_NIGHT: if (timeItem == null) { int nightTime = 10; value = getTimeValue(context, nightTime, 0, "pm", null); } else { int hour = timeItem.value / 60; int mins = timeItem.value % 60; int modHour = hour % 12; if (modHour >=0 && modHour < 4){ value = getTimeValue(context, hour, mins, "am", null); } else if (modHour >= 4 && modHour <= 6){ value = getTimeValue(context, 9, mins, "pm", null); } else { value = getTimeValue(context, hour, mins, "pm", null); } } break; default: break; } return value; } /** * Get the display string to be shown in the suggestions * * @param context The context to use * @param cal Calendar object to update * @param isRelative Is the time relative to the current time * @return Display string */ final String getDisplayDate(Context context, Calendar cal, boolean isRelative) { if (isRelative) { int days = DateTimeUtils.daysBetween(Calendar.getInstance(), cal); if (days == 0) { return context.getString(R.string.today) + ", " + DateTimeUtils.getDisplayTime(context, cal, config); } else if (days == 1) { return context.getString(R.string.tomorrow) + ", " + DateTimeUtils.getDisplayTime(context, cal, config); } else { return getDisplayDate(cal) + ", " + DateTimeUtils.getDisplayTime(context, cal, config); } } else { return getDisplayDate(cal); } } final String getDisplayDate(Context context, Calendar cal, String timeString) { int days = DateTimeUtils.daysBetween(Calendar.getInstance(), cal); if (days == 0) { return context.getString(R.string.today) + ", " + timeString; } else if (days == 1) { return context.getString(R.string.tomorrow) + ", " + timeString; } else { return getDisplayDate(cal) + ", " + timeString; } } /** * Get display date string from calendar in "EEEE, dd MMM" format, *
e.g. Mon, 12 Dec
Tue, 20 Jul * * @param cal Calendar to use * @return the display value */ final String getDisplayDate(Calendar cal) { if (Calendar.getInstance().get(Calendar.YEAR) == cal.get(Calendar.YEAR)) { DateFormat df = new SimpleDateFormat(config.getDateFormatWithoutYear(), Locale.ENGLISH); return df.format(cal.getTime()); } else { DateFormat df = new SimpleDateFormat(config.getDateFormatWithYear(), Locale.ENGLISH); return df.format(cal.getTime()); } } /** * Class used internally with all the handlers * to build and show suggestions */ protected class Value { String displayString; public Calendar value; public Value(String displayString, Calendar value) { this.displayString = displayString; this.value = value; } } } ================================================ FILE: library/src/main/java/com/pv/datetimeseer/SuggestionRow.java ================================================ package com.pv.datetimeseer; /** * @author p-v */ public class SuggestionRow { public static final int PARTIAL_VALUE = -999; private String displayValue; /** * Time in */ private int value; public SuggestionRow(String displayValue, int value) { this.displayValue = displayValue; this.value = value; } /** * Use this to get more results when the value is set to PARTIAL_VALUE. * * @return the display value.
* */ public String getDisplayValue() { return displayValue; } /** * * @return the time in seconds */ public int getValue() { return value; } } ================================================ FILE: library/src/main/java/com/pv/datetimeseer/SuggestionValue.java ================================================ package com.pv.datetimeseer; import android.support.v4.util.SparseArrayCompat; /** * Values regarding the user input * * @author p-v */ class SuggestionValue extends SparseArrayCompat { static final int RELATIVE_DAY = 0x01; static final int DAY_OF_WEEK = 0x02; static final int TIME_OF_DAY = 0x04; static final int MONTH = 0x08; static final int NUMBER = 0x10; static final int TIME = 0x20; static final int DATE = 0x40; static final int DAY_OF_WEEK_NEXT = 0x80; static final int RELATIVE_DAY_NUMBER = 0x0100; static final int OTHER = 0x0200; private TimeSuggestionHandler.TimeItem timeItem; private LocalItemItem todItem; private RelativeTimeSuggestionHandler.RelativeDayItem relDayItem; private NumberRelativeTimeSuggestionHandler.RelativeDayNumItem relativeDayNumItem; private LocalItemItem dowItem; private LocalItemItem nextDowItem; private LocalItemItem monthItem; private DateSuggestionHandler.DateItem dateItem; private LocalItemItem numberItem; private LocalItemItem otherItem; void init() { relDayItem = (RelativeTimeSuggestionHandler.RelativeDayItem) this.get(RELATIVE_DAY); relativeDayNumItem = (NumberRelativeTimeSuggestionHandler.RelativeDayNumItem) this.get(RELATIVE_DAY_NUMBER); dowItem = this.get(DAY_OF_WEEK); nextDowItem = this.get(DAY_OF_WEEK_NEXT); monthItem = this.get(MONTH); dateItem = (DateSuggestionHandler.DateItem) this.get(DATE); todItem = this.get(TIME_OF_DAY); timeItem = (TimeSuggestionHandler.TimeItem) this.get(TIME); numberItem = this.get(NUMBER); otherItem = this.get(OTHER); } public void appendSuggestion(int flag, int value) { super.append(flag, new LocalItemItem(value)); } public void appendSuggestion(int flag, LocalItemItem item) { super.append(flag, item); } public static class LocalItemItem { public int value; public LocalItemItem(int value) { this.value = value; } } public TimeSuggestionHandler.TimeItem getTimeItem() { return timeItem; } public LocalItemItem getTodItem() { return todItem; } public RelativeTimeSuggestionHandler.RelativeDayItem getRelDayItem() { return relDayItem; } public LocalItemItem getDowItem() { return dowItem; } public LocalItemItem getNextDowItem() { return nextDowItem; } public LocalItemItem getMonthItem() { return monthItem; } public DateSuggestionHandler.DateItem getDateItem() { return dateItem; } public NumberRelativeTimeSuggestionHandler.RelativeDayNumItem getRelativeDayNumItem() { return relativeDayNumItem; } public LocalItemItem getNumberItem() { return numberItem; } public LocalItemItem getOtherItem() { return otherItem; } } ================================================ FILE: library/src/main/java/com/pv/datetimeseer/TODSuggestionHandler.java ================================================ package com.pv.datetimeseer; import android.content.Context; import java.util.Calendar; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author p-v */ class TODSuggestionHandler extends SuggestionHandler { static final int TOD_MORNING = 1; static final int TOD_AFTERNOON = 2; static final int TOD_EVENING = 3; static final int TOD_NIGHT = 4; 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"; private Pattern pTod; TODSuggestionHandler(Config config) { super(config); pTod = Pattern.compile(REGEX, Pattern.CASE_INSENSITIVE); } @Override public void handle(Context context, String input, String lastToken, SuggestionValue suggestionValue) { Matcher matcher = pTod.matcher(input); if (matcher.find()) { int value; if (matcher.group(1) != null) { value = TOD_MORNING; } else if (matcher.group(2) != null) { value = TOD_AFTERNOON; } else if(matcher.group(3) != null) { value = TOD_EVENING; } else { value = TOD_NIGHT; } suggestionValue.appendSuggestion(SuggestionValue.TIME_OF_DAY, value); } super.handle(context, input, lastToken, suggestionValue); } @Override public void build(Context context, SuggestionValue suggestionValue, List suggestionList) { SuggestionValue.LocalItemItem relItem = suggestionValue.getRelDayItem(); SuggestionValue.LocalItemItem dowItem = suggestionValue.getDowItem(); SuggestionValue.LocalItemItem nextDowItem = suggestionValue.getNextDowItem(); SuggestionValue.LocalItemItem monthItem = suggestionValue.getMonthItem(); SuggestionValue.LocalItemItem dateItem = suggestionValue.getDateItem(); TimeSuggestionHandler.TimeItem timeItem = suggestionValue.getTimeItem(); NumberRelativeTimeSuggestionHandler.RelativeDayNumItem relNumItem = suggestionValue.getRelativeDayNumItem(); SuggestionValue.LocalItemItem todItem = suggestionValue.getTodItem(); // Ignoring time timeItem. Using it later in the method boolean hasOnlyTime = relItem == null && dowItem == null && nextDowItem == null && monthItem == null && dateItem == null && relNumItem == null && todItem != null; if (hasOnlyTime) { Value timeValue = getTimeValue(context, todItem, timeItem); if (timeValue != null) { if (timeItem == null || !timeItem.isAmPmPresent) { suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); // increment a day for tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 1); suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); // increment a day for day after tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 1); suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } else { // Time present with AM/PM int hour = timeItem.value / 60; int mins = timeItem.value % 60; timeValue = getTimeValue(context, hour, mins, null, null); suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); // increment a day for tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 1); suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); // increment a day for day after tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 1); suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } } } else { super.build(context, suggestionValue, suggestionList); } } } ================================================ FILE: library/src/main/java/com/pv/datetimeseer/TimeSuggestionHandler.java ================================================ package com.pv.datetimeseer; import android.content.Context; import java.util.Calendar; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Suggestion handler for handling time information.
* Handles strings like 6:30, 6 40, 3pm etc. * * @author p-v */ class TimeSuggestionHandler extends SuggestionHandler { 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"; private Pattern timePattern; /** * Time item class. */ class TimeItem extends SuggestionValue.LocalItemItem { boolean isAmPmPresent; TimeItem(int value, boolean amPmPresent) { super(value); this.isAmPmPresent = amPmPresent; } } TimeSuggestionHandler(Config config) { super(config); timePattern = Pattern.compile(TIME_RGX, Pattern.CASE_INSENSITIVE); } @Override public void handle(Context context, String input, String lastToken, SuggestionValue suggestionValue) { DateSuggestionHandler.DateItem dateSuggestion = (DateSuggestionHandler.DateItem) suggestionValue.get(SuggestionValue.DATE); NumberRelativeTimeSuggestionHandler.RelativeDayNumItem numItem = (NumberRelativeTimeSuggestionHandler.RelativeDayNumItem) suggestionValue.get(SuggestionValue.RELATIVE_DAY_NUMBER); StringBuilder builder = new StringBuilder(input); if (numItem != null) { builder.replace(numItem.startIdx, numItem.endIdx, ""); } else if (dateSuggestion != null) { builder.replace(dateSuggestion.startIdx, dateSuggestion.endIdx, ""); } Matcher matcher = timePattern.matcher(builder.toString()); while (matcher.find()) { int hourOfDay = Integer.parseInt(matcher.group(1)); int mins = 0; String minsStr = matcher.group(2); String amPm = matcher.group(3); if (minsStr != null) { mins = Integer.parseInt(minsStr); } if (hourOfDay < 12) { if (amPm != null && amPm.matches("(?i)^p.*")) { hourOfDay = hourOfDay + 12; } } else if (hourOfDay == 12) { if (amPm != null && amPm.matches("(?i)^a.*")) { hourOfDay = 0; } } int minsInDay = hourOfDay * 60 + mins; suggestionValue.appendSuggestion(SuggestionValue.TIME, new TimeItem(minsInDay, amPm != null)); } super.handle(context, input, lastToken, suggestionValue); } @Override public void build(Context context, SuggestionValue suggestionValue, List suggestionList) { SuggestionValue.LocalItemItem relItem = suggestionValue.getRelDayItem(); SuggestionValue.LocalItemItem dowItem = suggestionValue.getDowItem(); SuggestionValue.LocalItemItem nextDowItem = suggestionValue.getNextDowItem(); SuggestionValue.LocalItemItem monthItem = suggestionValue.getMonthItem(); SuggestionValue.LocalItemItem dateItem = suggestionValue.getDateItem(); SuggestionValue.LocalItemItem todItem = suggestionValue.getTodItem(); SuggestionValue.LocalItemItem relItemNum = suggestionValue.getRelativeDayNumItem(); TimeItem timeItem = suggestionValue.getTimeItem(); boolean hasOnlyTime = relItem == null && dowItem == null && nextDowItem == null && monthItem == null && dateItem == null && todItem == null && relItemNum == null && timeItem != null; if (hasOnlyTime) { Value timeValue; int hour = timeItem.value / 60; int mins = timeItem.value % 60; Calendar cal = Calendar.getInstance(); final int currentHourOfDay = cal.get(Calendar.HOUR_OF_DAY); final int currentMinsOfHour = cal.get(Calendar.MINUTE); if (timeItem.isAmPmPresent) { timeValue = getTimeValue(context, hour, mins, null, null); if (!(currentHourOfDay > hour || currentHourOfDay == hour && currentMinsOfHour > mins)) { suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } // increment a day for tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 1); suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); // increment a day for day after tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 1); suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value)+ ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } else { // if the current time is more than the entered time if (currentHourOfDay > hour || currentHourOfDay == hour && currentMinsOfHour > mins) { timeValue = getTimeValue(context, hour, mins, null, null); int calculatedHourForToday = timeValue.value.get(Calendar.HOUR_OF_DAY); int calculatedMinsForToday = timeValue.value.get(Calendar.MINUTE); // Check if the calculated time for today has past if (calculatedHourForToday > currentHourOfDay || calculatedHourForToday == currentHourOfDay && calculatedMinsForToday > currentMinsOfHour) { suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } // increment a day for tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 1); suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); // increment a day for day after tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 1); suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value)+ ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } else { timeValue = getTimeValue(context, hour, mins, null, null); suggestionList.add(new SuggestionRow(context.getString(R.string.today) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); // increment a day for tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 1); suggestionList.add(new SuggestionRow(context.getString(R.string.tomorrow) + ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); // increment a day for day after tomorrow timeValue.value.add(Calendar.DAY_OF_YEAR, 1); suggestionList.add(new SuggestionRow(getDisplayDate(timeValue.value)+ ", " + timeValue.displayString, (int)(timeValue.value.getTimeInMillis()/1000))); } } } else { super.build(context, suggestionValue, suggestionList); } } } ================================================ FILE: library/src/main/res/values/strings.xml ================================================ DateTimeSeer Today Tomorrow 1 month ago %d months ago 1 year ago %d years ago %d hours %d hour %d minutes %d minute 1 day %d days ================================================ FILE: library/src/test/java/com/pv/datetimeseer/ExampleUnitTest.java ================================================ package com.pv.datetimeseer; import org.junit.Test; import static org.junit.Assert.*; /** * Example local unit test, which will execute on the development machine (host). * * @see Testing documentation */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: settings.gradle ================================================ include ':app', ':library'