================================================
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.

**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
================================================
16dp16dp
================================================
FILE: app/src/main/res/values/strings.xml
================================================
DateTimeSeerSampleAwesomeness 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
================================================
DateTimeSeerTodayTomorrow1 month ago%d months ago1 year ago%d years ago%d hours%d hour%d minutes%d minute1 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'