diff --git a/opentasks/build.gradle b/opentasks/build.gradle
index 9b5884782..73aa5cfa0 100644
--- a/opentasks/build.gradle
+++ b/opentasks/build.gradle
@@ -46,6 +46,7 @@ android {
dependencies {
implementation project(':opentasks-provider')
+ implementation project(':opentaskspal')
implementation 'com.android.support:appcompat-v7:' + SUPPORT_LIBRARY_VERSION
implementation 'com.android.support:design:' + SUPPORT_LIBRARY_VERSION
implementation('org.dmfs:android-xml-magic:0.1.1') {
diff --git a/opentasks/src/main/java/org/dmfs/tasks/model/ContentSet.java b/opentasks/src/main/java/org/dmfs/tasks/model/ContentSet.java
index 1e32f0f21..3784b960c 100644
--- a/opentasks/src/main/java/org/dmfs/tasks/model/ContentSet.java
+++ b/opentasks/src/main/java/org/dmfs/tasks/model/ContentSet.java
@@ -24,6 +24,7 @@
import android.os.Parcelable;
import android.util.Log;
+import org.dmfs.opentaskspal.jems.procedure.Procedure;
import org.dmfs.tasks.utils.AsyncContentLoader;
import org.dmfs.tasks.utils.ContentValueMapper;
import org.dmfs.tasks.utils.OnContentLoadedListener;
@@ -523,6 +524,28 @@ public void finishBulkUpdate()
}
+ /**
+ * Execute a bulk update for this {@link ContentSet}.
+ *
+ * Shortcut for using {@link #startBulkUpdate()} and {@link #finishBulkUpdate()}.
+ *
+ * @param body
+ * do the changes to {@link ContentSet} here
+ */
+ public void bulkUpdate(Procedure body)
+ {
+ startBulkUpdate();
+ try
+ {
+ body.process(this);
+ }
+ finally
+ {
+ finishBulkUpdate();
+ }
+ }
+
+
/**
* Remove the value with the given key from the ContentSet. This is actually replacing the value by null.
*
diff --git a/opentasks/src/main/java/org/dmfs/tasks/model/adapters/CombinedDateTimeFieldAdapter.java b/opentasks/src/main/java/org/dmfs/tasks/model/adapters/CombinedDateTimeFieldAdapter.java
new file mode 100644
index 000000000..6a5c31dc6
--- /dev/null
+++ b/opentasks/src/main/java/org/dmfs/tasks/model/adapters/CombinedDateTimeFieldAdapter.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2017 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.tasks.model.adapters;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.dmfs.opentaskspal.datetime.CombinedDateTime;
+import org.dmfs.opentaskspal.datetime.general.OptionalTimeZone;
+import org.dmfs.opentaskspal.datetimefields.DateTimeFields;
+import org.dmfs.opentaskspal.datetimefields.adapters.DateTimeDateTimeFields;
+import org.dmfs.opentaskspal.readdata.cursor.CursorCombinedDateTime;
+import org.dmfs.opentaskspal.utils.binarybooleans.BinaryIntBoolean;
+import org.dmfs.rfc5545.DateTime;
+import org.dmfs.tasks.model.ContentSet;
+import org.dmfs.tasks.model.OnContentChangeListener;
+import org.dmfs.tasks.model.datetime.ContentSetCombinedDateTime;
+import org.dmfs.tasks.model.datetime.ContentSetDateTimeFields;
+
+
+/**
+ * Knows how to load and store combined date-time values as {@link DateTime} from/to {@link Cursor}, {@link ContentSet}, {@link ContentValues}.
+ *
+ * Combined date-time values are stored as three values: timestamp, time zone, all-day flag
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class CombinedDateTimeFieldAdapter extends FieldAdapter
+{
+ private final String mTimestampField;
+ private final String mTimeZoneField;
+ private final String mIsAllDayField;
+
+
+ public CombinedDateTimeFieldAdapter(@NonNull String timestampField,
+ @NonNull String timeZoneField,
+ @NonNull String isAllDayField)
+ {
+ mTimestampField = timestampField;
+ mTimeZoneField = timeZoneField;
+ mIsAllDayField = isAllDayField;
+ }
+
+
+ @Override
+ public DateTime get(@NonNull ContentSet values)
+ {
+ return new ContentSetCombinedDateTime(values, mTimestampField, mTimeZoneField, mIsAllDayField).value(null);
+ }
+
+
+ @Override
+ public DateTime get(@NonNull Cursor cursor)
+ {
+ return new CursorCombinedDateTime(cursor, mTimestampField, mTimeZoneField, mIsAllDayField).value(null);
+ }
+
+
+ /**
+ * Provides a default value for 'now' that respects the time zone and all-day values if they've been already selected
+ * (i.e. they are present in the provided {@link ContentSet}.
+ *
+ *
If all-day flag is true, an all-day {@link DateTime} for today is returned.
+ * If time zone is provided, a {@link DateTime} for the current time instance with that time zone is returned
+ * If time zone is not provided, a floating {@link DateTime} for the current time instance is returned
+ *
+ * Note: it is up to the presentation layer to apply local time zone when needed
+ */
+ @Override
+ public DateTime getDefault(@NonNull ContentSet values)
+ {
+ DateTimeFields fields = new ContentSetDateTimeFields(values, mTimestampField, mTimeZoneField, mIsAllDayField);
+ return new CombinedDateTime(System.currentTimeMillis(),
+ new OptionalTimeZone(fields.timeZoneId()),
+ new BinaryIntBoolean(fields.isAllDay())).value();
+ }
+
+
+ @Override
+ public void set(@NonNull ContentSet values, @Nullable DateTime dateTime)
+ {
+ values.bulkUpdate(contentSet ->
+ {
+ if (dateTime != null)
+ {
+ DateTimeFields dateTimeFields = new DateTimeDateTimeFields(dateTime);
+ values.put(mTimestampField, dateTimeFields.timestamp());
+ values.put(mTimeZoneField, dateTimeFields.timeZoneId());
+ values.put(mIsAllDayField, dateTimeFields.isAllDay());
+ }
+ else
+ {
+ // write timestamp only, other fields may still use all-day and timezone
+ values.put(mTimestampField, (Long) null);
+ }
+ });
+ }
+
+
+ @Override
+ public void set(@NonNull ContentValues values, @Nullable DateTime dateTime)
+ {
+ // TODO Remove semantic code duplication with the above
+ // Update type with isNewValue(), isClear(), isNoChange(), T newValue() might help here
+ if (dateTime != null)
+ {
+ DateTimeFields dateTimeFields = new DateTimeDateTimeFields(dateTime);
+ values.put(mTimestampField, dateTimeFields.timestamp());
+ values.put(mTimeZoneField, dateTimeFields.timeZoneId());
+ values.put(mIsAllDayField, dateTimeFields.isAllDay());
+ }
+ else
+ {
+ // write timestamp only, other fields may still use all-day and timezone
+ values.put(mTimestampField, (Long) null);
+ }
+ }
+
+
+ @Override
+ public void registerListener(ContentSet values, OnContentChangeListener listener, boolean initalNotification)
+ {
+ values.addOnChangeListener(listener, mTimestampField, initalNotification);
+ values.addOnChangeListener(listener, mTimeZoneField, initalNotification);
+ values.addOnChangeListener(listener, mIsAllDayField, initalNotification);
+ }
+
+
+ @Override
+ public void unregisterListener(ContentSet values, OnContentChangeListener listener)
+ {
+ values.removeOnChangeListener(listener, mTimestampField);
+ values.removeOnChangeListener(listener, mTimeZoneField);
+ values.removeOnChangeListener(listener, mIsAllDayField);
+ }
+}
diff --git a/opentasks/src/main/java/org/dmfs/tasks/model/adapters/TimestampDateTimeFieldAdapter.java b/opentasks/src/main/java/org/dmfs/tasks/model/adapters/TimestampDateTimeFieldAdapter.java
new file mode 100644
index 000000000..7b4e54854
--- /dev/null
+++ b/opentasks/src/main/java/org/dmfs/tasks/model/adapters/TimestampDateTimeFieldAdapter.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.tasks.model.adapters;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.dmfs.opentaskspal.datetime.general.OptionalDateTimeTimestamp;
+import org.dmfs.opentaskspal.datetime.general.OptionalTimestampDateTime;
+import org.dmfs.opentaskspal.readdata.cursor.LongCursorColumnValue;
+import org.dmfs.rfc5545.DateTime;
+import org.dmfs.tasks.model.ContentSet;
+import org.dmfs.tasks.model.OnContentChangeListener;
+
+
+/**
+ * Knows how to load and store a {@link DateTime} value that doesn't use timezone and the all-day flag, just the timestamp.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class TimestampDateTimeFieldAdapter extends FieldAdapter
+{
+ private final String mTimestampField;
+
+
+ public TimestampDateTimeFieldAdapter(@NonNull String timestampField)
+ {
+ mTimestampField = timestampField;
+ }
+
+
+ @Override
+ public DateTime get(@NonNull ContentSet values)
+ {
+ return new OptionalTimestampDateTime(values.getAsLong(mTimestampField)).value(null);
+ }
+
+
+ @Override
+ public DateTime get(@NonNull Cursor cursor)
+ {
+ return new OptionalTimestampDateTime(new LongCursorColumnValue(cursor, mTimestampField)).value(null);
+ }
+
+
+ @Override
+ public DateTime getDefault(@NonNull ContentSet values)
+ {
+ return DateTime.now();
+ }
+
+
+ @Override
+ public void set(@NonNull ContentSet values, @Nullable DateTime value)
+ {
+ values.bulkUpdate(contentSet -> contentSet.put(mTimestampField, new OptionalDateTimeTimestamp(value).value(null)));
+ }
+
+
+ @Override
+ public void set(@NonNull ContentValues values, @Nullable DateTime value)
+ {
+ values.put(mTimestampField, new OptionalDateTimeTimestamp(value).value(null));
+ }
+
+
+ @Override
+ public void registerListener(@NonNull ContentSet values, @NonNull OnContentChangeListener listener, boolean initialNotification)
+ {
+ values.addOnChangeListener(listener, mTimestampField, initialNotification);
+ }
+
+
+ @Override
+ public void unregisterListener(@NonNull ContentSet values, @NonNull OnContentChangeListener listener)
+ {
+ values.removeOnChangeListener(listener, mTimestampField);
+ }
+}
diff --git a/opentasks/src/main/java/org/dmfs/tasks/model/datetime/ContentSetCombinedDateTime.java b/opentasks/src/main/java/org/dmfs/tasks/model/datetime/ContentSetCombinedDateTime.java
new file mode 100644
index 000000000..8ddb853de
--- /dev/null
+++ b/opentasks/src/main/java/org/dmfs/tasks/model/datetime/ContentSetCombinedDateTime.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.tasks.model.datetime;
+
+import android.support.annotation.NonNull;
+
+import org.dmfs.opentaskspal.datetime.OptionalCombinedDateTime;
+import org.dmfs.optional.decorators.DelegatingOptional;
+import org.dmfs.rfc5545.DateTime;
+import org.dmfs.tasks.model.ContentSet;
+
+
+/**
+ * Combined date-time from a {@link ContentSet}.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class ContentSetCombinedDateTime extends DelegatingOptional
+{
+ public ContentSetCombinedDateTime(@NonNull ContentSet contentSet,
+ @NonNull String timestampKey,
+ @NonNull String timeZoneKey,
+ @NonNull String isAllDayKey)
+ {
+ super(new OptionalCombinedDateTime(
+ new ContentSetDateTimeFields(contentSet, timestampKey, timeZoneKey, isAllDayKey)));
+ }
+}
diff --git a/opentasks/src/main/java/org/dmfs/tasks/model/datetime/ContentSetDateTimeFields.java b/opentasks/src/main/java/org/dmfs/tasks/model/datetime/ContentSetDateTimeFields.java
new file mode 100644
index 000000000..23d8c0e9c
--- /dev/null
+++ b/opentasks/src/main/java/org/dmfs/tasks/model/datetime/ContentSetDateTimeFields.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.tasks.model.datetime;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.dmfs.opentaskspal.datetimefields.DateTimeFields;
+import org.dmfs.opentaskspal.datetimefields.decorators.DelegatingDateTimeFields;
+import org.dmfs.opentaskspal.datetimefields.decorators.Evaluated;
+import org.dmfs.tasks.model.ContentSet;
+
+
+/**
+ * {@link DateTimeFields} from a {@link ContentSet}.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class ContentSetDateTimeFields extends DelegatingDateTimeFields
+{
+
+ public ContentSetDateTimeFields(@NonNull ContentSet contentSet,
+ @NonNull String timestampKey,
+ @NonNull String timeZoneKey,
+ @NonNull String isAllDayKey)
+ {
+ // Evaluated eagerly because ContentSet is mutable, values in it may change
+ super(new Evaluated(new LazyContentSetDateTimeFields(contentSet, timestampKey, timeZoneKey, isAllDayKey)));
+ }
+
+
+ private static final class LazyContentSetDateTimeFields implements DateTimeFields
+ {
+ private final ContentSet mContentSet;
+ private final String mTimestampKey;
+ private final String mTimeZoneKey;
+ private final String mIsAllDayKey;
+
+
+ public LazyContentSetDateTimeFields(@NonNull ContentSet contentSet,
+ @NonNull String timestampKey,
+ @NonNull String timeZoneKey,
+ @NonNull String isAllDayKey)
+ {
+ mContentSet = contentSet;
+ mTimestampKey = timestampKey;
+ mTimeZoneKey = timeZoneKey;
+ mIsAllDayKey = isAllDayKey;
+ }
+
+
+ @Nullable
+ @Override
+ public Long timestamp()
+ {
+ return mContentSet.getAsLong(mTimestampKey);
+ }
+
+
+ @Nullable
+ @Override
+ public String timeZoneId()
+ {
+ return mContentSet.getAsString(mTimeZoneKey);
+ }
+
+
+ @Nullable
+ @Override
+ public Integer isAllDay()
+ {
+ return mContentSet.getAsInteger(mIsAllDayKey);
+ }
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/CombinedDateTime.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/CombinedDateTime.java
new file mode 100644
index 000000000..941dd2e43
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/CombinedDateTime.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.datetime;
+
+import android.support.annotation.NonNull;
+
+import org.dmfs.jems.single.Single;
+import org.dmfs.jems.single.decorators.DelegatingSingle;
+import org.dmfs.jems.single.elementary.ValueSingle;
+import org.dmfs.optional.Optional;
+import org.dmfs.rfc5545.DateTime;
+import org.dmfs.tasks.contract.TaskContract;
+
+import java.util.TimeZone;
+
+
+/**
+ * {@link Single} for a {@link DateTime} value composed from the three related fields' values (timestamp, timezone, all-day)
+ * interpreted according to {@link TaskContract}.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class CombinedDateTime extends DelegatingSingle
+{
+ public CombinedDateTime(@NonNull Single timestamp,
+ @NonNull Optional timeZone,
+ @NonNull Single isAllDay)
+ {
+ super(() ->
+ isAllDay.value() ?
+ new DateTime(timestamp.value()).toAllDay()
+ :
+ // Null time zone means floating time
+ new DateTime(timeZone.value(null), timestamp.value()));
+ }
+
+
+ public CombinedDateTime(@NonNull Long timestamp,
+ @NonNull Optional timeZone,
+ @NonNull Single isAllDay)
+ {
+ this(new ValueSingle<>(timestamp), timeZone, isAllDay);
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/OptionalCombinedDateTime.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/OptionalCombinedDateTime.java
new file mode 100644
index 000000000..b4fd86aca
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/OptionalCombinedDateTime.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.datetime;
+
+import android.support.annotation.NonNull;
+
+import org.dmfs.jems.optional.decorators.Mapped;
+import org.dmfs.jems.single.Single;
+import org.dmfs.opentaskspal.datetime.general.OptionalTimeZone;
+import org.dmfs.opentaskspal.datetimefields.DateTimeFields;
+import org.dmfs.opentaskspal.utils.binarybooleans.BinaryIntBoolean;
+import org.dmfs.optional.NullSafe;
+import org.dmfs.optional.Optional;
+import org.dmfs.optional.decorators.DelegatingOptional;
+import org.dmfs.rfc5545.DateTime;
+import org.dmfs.tasks.contract.TaskContract;
+
+import java.util.TimeZone;
+
+
+/**
+ * {@link Optional} {@link DateTime} value composed from the three related fields' values (timestamp, timezone, all-day)
+ * interpreted according to {@link TaskContract}.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class OptionalCombinedDateTime extends DelegatingOptional
+{
+
+ public OptionalCombinedDateTime(Optional timestamp,
+ Optional timeZone,
+ Single isAllDay)
+ {
+ super(new Mapped<>(
+ ts -> new CombinedDateTime(ts, timeZone, isAllDay).value(),
+ timestamp)
+ );
+ }
+
+
+ public OptionalCombinedDateTime(@NonNull DateTimeFields fields)
+ {
+ this(new NullSafe<>(fields.timestamp()),
+ new OptionalTimeZone(fields.timeZoneId()),
+ new BinaryIntBoolean(fields.isAllDay()));
+ }
+
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalDateTimeTimestamp.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalDateTimeTimestamp.java
new file mode 100644
index 000000000..cc0fc6be2
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalDateTimeTimestamp.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.datetime.general;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.dmfs.jems.optional.decorators.Mapped;
+import org.dmfs.optional.NullSafe;
+import org.dmfs.optional.Optional;
+import org.dmfs.optional.decorators.DelegatingOptional;
+import org.dmfs.rfc5545.DateTime;
+
+
+/**
+ * {@link Optional} for a {@link Long} timestamp of a provided optional {@link DateTime}.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class OptionalDateTimeTimestamp extends DelegatingOptional
+{
+ public OptionalDateTimeTimestamp(@NonNull Optional dateTime)
+ {
+ super(new Mapped<>(DateTime::getTimestamp, dateTime));
+ }
+
+
+ public OptionalDateTimeTimestamp(@Nullable DateTime dateTime)
+ {
+ this(new NullSafe<>(dateTime));
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalTimeZone.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalTimeZone.java
new file mode 100644
index 000000000..bd42c3a14
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalTimeZone.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.datetime.general;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.dmfs.jems.optional.decorators.Mapped;
+import org.dmfs.optional.NullSafe;
+import org.dmfs.optional.Optional;
+import org.dmfs.optional.decorators.DelegatingOptional;
+
+import java.util.TimeZone;
+
+
+/**
+ * {@link Optional} {@link TimeZone} for an optional time zone id.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class OptionalTimeZone extends DelegatingOptional
+{
+ public OptionalTimeZone(@NonNull Optional timeZoneId)
+ {
+ super(new Mapped<>(TimeZone::getTimeZone, timeZoneId));
+ }
+
+
+ public OptionalTimeZone(@Nullable String timeZoneId)
+ {
+ this(new NullSafe<>(timeZoneId));
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalTimestampDateTime.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalTimestampDateTime.java
new file mode 100644
index 000000000..bfaedcdea
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/OptionalTimestampDateTime.java
@@ -0,0 +1,49 @@
+
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.datetime.general;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.dmfs.jems.optional.decorators.Mapped;
+import org.dmfs.optional.NullSafe;
+import org.dmfs.optional.Optional;
+import org.dmfs.optional.decorators.DelegatingOptional;
+import org.dmfs.rfc5545.DateTime;
+
+
+/**
+ * {@link Optional} for {@link DateTime} created from an optional timestamp.
+ *
+ * UTC is used to interpret pure timestamp date-times. Formatter for UI can convert to local time zone before displaying.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class OptionalTimestampDateTime extends DelegatingOptional
+{
+ public OptionalTimestampDateTime(@NonNull Optional timestamp)
+ {
+ super(new Mapped<>(ts -> new TimestampDateTime(ts).value(), timestamp));
+ }
+
+
+ public OptionalTimestampDateTime(@Nullable Long timestamp)
+ {
+ this(new NullSafe<>(timestamp));
+ }
+}
\ No newline at end of file
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/TimeZones.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/TimeZones.java
new file mode 100644
index 000000000..1594af47e
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/TimeZones.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.datetime.general;
+
+import java.util.TimeZone;
+
+
+/**
+ * Time zone constants.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class TimeZones
+{
+ public static final TimeZone UTC = TimeZone.getTimeZone("UTC");
+
+ public static final TimeZone LOCAL = TimeZone.getDefault();
+
+
+ private TimeZones()
+ {
+
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/TimestampDateTime.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/TimestampDateTime.java
new file mode 100644
index 000000000..89d4ded57
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetime/general/TimestampDateTime.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.datetime.general;
+
+import android.support.annotation.NonNull;
+
+import org.dmfs.jems.single.Single;
+import org.dmfs.jems.single.decorators.DelegatingSingle;
+import org.dmfs.jems.single.elementary.ValueSingle;
+import org.dmfs.rfc5545.DateTime;
+
+
+/**
+ * {@link Single} for {@link DateTime} created from a timestamp.
+ *
+ * UTC is used to interpret pure timestamp date-times. Formatter for UI can convert to local time zone before displaying.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class TimestampDateTime extends DelegatingSingle
+{
+ public TimestampDateTime(@NonNull Single timestamp)
+ {
+ super(() -> new DateTime(timestamp.value()));
+ }
+
+
+ public TimestampDateTime(@NonNull Long timestamp)
+ {
+ this(new ValueSingle<>(timestamp));
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/DateTimeFields.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/DateTimeFields.java
new file mode 100644
index 000000000..a51e0c389
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/DateTimeFields.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.datetimefields;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.support.annotation.Nullable;
+
+
+/**
+ * The three values that together represent a combined date-time in the provider. (Start or due date-time in practice.)
+ * Corresponds one-to-one to the actual values in the database,
+ * and thus to the values in {@link Cursor}, {@link ContentValues}, and ContentSet.
+ *
+ * @author Gabor Keszthelyi
+ */
+public interface DateTimeFields
+{
+
+ /**
+ * The timestamp. ({@code null} if it's empty)
+ */
+ @Nullable
+ Long timestamp();
+
+ /**
+ * Time zone id. ({@code null} if it's empty)
+ */
+ @Nullable
+ String timeZoneId();
+
+ /**
+ * All-day flag of the date-time. 1 for true, 0 for false.
+ *
+ * ({@code null} if it's empty, which is always interpreted as 0-false)
+ */
+ @Nullable
+ Integer isAllDay();
+
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/CursorDateTimeFields.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/CursorDateTimeFields.java
new file mode 100644
index 000000000..d3015ba89
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/CursorDateTimeFields.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.datetimefields.adapters;
+
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.dmfs.opentaskspal.datetimefields.DateTimeFields;
+import org.dmfs.opentaskspal.datetimefields.decorators.DelegatingDateTimeFields;
+import org.dmfs.opentaskspal.datetimefields.decorators.Evaluated;
+import org.dmfs.opentaskspal.readdata.cursor.IntCursorColumnValue;
+import org.dmfs.opentaskspal.readdata.cursor.LongCursorColumnValue;
+import org.dmfs.opentaskspal.readdata.cursor.StringCursorColumnValue;
+
+
+/**
+ * {@link DateTimeFields} from a {@link Cursor}.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class CursorDateTimeFields extends DelegatingDateTimeFields
+{
+ public CursorDateTimeFields(@NonNull Cursor cursor,
+ @NonNull String timestampColumn,
+ @NonNull String timeZoneColumn,
+ @NonNull String isAllDayColumn)
+ {
+ // Evaluated eagerly to not capture Cursor
+ super(new Evaluated(new LazyCursorDateTimeFields(cursor, timestampColumn, timeZoneColumn, isAllDayColumn)));
+ }
+
+
+ private static final class LazyCursorDateTimeFields implements DateTimeFields
+ {
+ private final Cursor mCursor;
+ private final String mTimestampColumn;
+ private final String mTimeZoneColumn;
+ private final String mIsAllDayColumn;
+
+
+ private LazyCursorDateTimeFields(@NonNull Cursor cursor,
+ @NonNull String timestampColumn,
+ @NonNull String timeZoneColumn,
+ @NonNull String isAllDayColumn)
+ {
+ mCursor = cursor;
+ mTimestampColumn = timestampColumn;
+ mTimeZoneColumn = timeZoneColumn;
+ mIsAllDayColumn = isAllDayColumn;
+ }
+
+
+ @Nullable
+ @Override
+ public Long timestamp()
+ {
+ return new LongCursorColumnValue(mCursor, mTimestampColumn).value(null);
+ }
+
+
+ @Nullable
+ @Override
+ public String timeZoneId()
+ {
+ return new StringCursorColumnValue(mCursor, mTimeZoneColumn).value(null);
+ }
+
+
+ @Nullable
+ @Override
+ public Integer isAllDay()
+ {
+ return new IntCursorColumnValue(mCursor, mIsAllDayColumn).value(null);
+ }
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/DateTimeDateTimeFields.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/DateTimeDateTimeFields.java
new file mode 100644
index 000000000..c1dc872b4
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/DateTimeDateTimeFields.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.datetimefields.adapters;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.dmfs.opentaskspal.datetimefields.DateTimeFields;
+import org.dmfs.rfc5545.DateTime;
+
+import java.util.TimeZone;
+
+
+/**
+ * {@link DateTimeFields} corresponding to the given {@link DateTime}.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class DateTimeDateTimeFields implements DateTimeFields
+{
+ private final DateTime mDateTime;
+
+
+ public DateTimeDateTimeFields(@NonNull DateTime dateTime)
+ {
+ mDateTime = dateTime;
+ }
+
+
+ @Override
+ public Long timestamp()
+ {
+ return mDateTime.getTimestamp();
+ }
+
+
+ @Nullable
+ @Override
+ public String timeZoneId()
+ {
+ TimeZone timeZone = mDateTime.getTimeZone();
+ return timeZone == null ? null : timeZone.getID();
+ }
+
+
+ @Override
+ public Integer isAllDay()
+ {
+ return mDateTime.isAllDay() ? 1 : 0;
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/RowDataSnapshotDateTimeFields.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/RowDataSnapshotDateTimeFields.java
new file mode 100644
index 000000000..425859202
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/adapters/RowDataSnapshotDateTimeFields.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.datetimefields.adapters;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.dmfs.android.contentpal.RowDataSnapshot;
+import org.dmfs.opentaskspal.datetimefields.DateTimeFields;
+
+
+/**
+ * {@link DateTimeFields} from a {@link RowDataSnapshot}.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class RowDataSnapshotDateTimeFields implements DateTimeFields
+{
+ private final RowDataSnapshot> mRowDataSnapshot;
+ private final String mTimestampKey;
+ private final String mTimeZoneKey;
+ private final String mIsAllDayKey;
+
+
+ public RowDataSnapshotDateTimeFields(@NonNull RowDataSnapshot> rowDataSnapshot,
+ @NonNull String timestampKey,
+ @NonNull String timeZoneKey,
+ @NonNull String isAllDayKey)
+ {
+ mRowDataSnapshot = rowDataSnapshot;
+ mTimestampKey = timestampKey;
+ mTimeZoneKey = timeZoneKey;
+ mIsAllDayKey = isAllDayKey;
+ }
+
+
+ @Nullable
+ @Override
+ public Long timestamp()
+ {
+ return mRowDataSnapshot.data(mTimestampKey, Long::valueOf).value(null);
+ }
+
+
+ @Nullable
+ @Override
+ public String timeZoneId()
+ {
+ return mRowDataSnapshot.data(mTimeZoneKey, s -> s).value(null);
+ }
+
+
+ @Nullable
+ @Override
+ public Integer isAllDay()
+ {
+ return mRowDataSnapshot.data(mIsAllDayKey, Integer::valueOf).value(null);
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/decorators/DelegatingDateTimeFields.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/decorators/DelegatingDateTimeFields.java
new file mode 100644
index 000000000..87765ebd6
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/decorators/DelegatingDateTimeFields.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.datetimefields.decorators;
+
+import android.support.annotation.Nullable;
+
+import org.dmfs.opentaskspal.datetimefields.DateTimeFields;
+
+
+/**
+ * Abstract {@link DateTimeFields} that delegates to the given {@link DateTimeFields}.
+ *
+ * @author Gabor Keszthelyi
+ */
+public abstract class DelegatingDateTimeFields implements DateTimeFields
+{
+ private final DateTimeFields mDateTimeFields;
+
+
+ protected DelegatingDateTimeFields(DateTimeFields delegate)
+ {
+ mDateTimeFields = delegate;
+ }
+
+
+ @Nullable
+ @Override
+ public final Long timestamp()
+ {
+ return mDateTimeFields.timestamp();
+ }
+
+
+ @Nullable
+ @Override
+ public final String timeZoneId()
+ {
+ return mDateTimeFields.timeZoneId();
+ }
+
+
+ @Nullable
+ @Override
+ public final Integer isAllDay()
+ {
+ return mDateTimeFields.isAllDay();
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/decorators/Evaluated.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/decorators/Evaluated.java
new file mode 100644
index 000000000..00d2278c7
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/datetimefields/decorators/Evaluated.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.datetimefields.decorators;
+
+import android.support.annotation.Nullable;
+
+import org.dmfs.opentaskspal.datetimefields.DateTimeFields;
+
+
+/**
+ * Eager evaluation decorator for {@link DateTimeFields}.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class Evaluated implements DateTimeFields
+{
+ private final Long mTimestamp;
+ private final String mTimeZoneId;
+ private final Integer mIsAllDay;
+
+
+ public Evaluated(DateTimeFields delegate)
+ {
+ mTimestamp = delegate.timestamp();
+ mTimeZoneId = delegate.timeZoneId();
+ mIsAllDay = delegate.isAllDay();
+ }
+
+
+ @Nullable
+ @Override
+ public Long timestamp()
+ {
+ return mTimestamp;
+ }
+
+
+ @Nullable
+ @Override
+ public String timeZoneId()
+ {
+ return mTimeZoneId;
+ }
+
+
+ @Nullable
+ @Override
+ public Integer isAllDay()
+ {
+ return mIsAllDay;
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/optional/Conditional.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/optional/Conditional.java
new file mode 100644
index 000000000..607a7a034
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/optional/Conditional.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.jems.optional;
+
+import org.dmfs.jems.predicate.Predicate;
+import org.dmfs.jems.single.Single;
+import org.dmfs.jems.single.elementary.ValueSingle;
+import org.dmfs.optional.Optional;
+import org.dmfs.optional.Present;
+
+import java.util.NoSuchElementException;
+
+import static org.dmfs.optional.Absent.absent;
+
+
+/**
+ * {@link Optional} that is present with the value of the provided target if it satisfies the given {@link Predicate},
+ * otherwise it is absent.
+ *
+ * @author Gabor Keszthelyi
+ * @deprecated use from jems when available
+ */
+@Deprecated
+public final class Conditional implements Optional
+{
+ private final Predicate mPredicate;
+ private final Single mTargetSingle;
+
+ private Optional mCachedDelegate;
+
+
+ public Conditional(Predicate predicate, Single targetSingle)
+ {
+ mPredicate = predicate;
+ mTargetSingle = targetSingle;
+ }
+
+
+ public Conditional(Predicate predicate, T targetValue)
+ {
+ this(predicate, new ValueSingle<>(targetValue));
+ }
+
+
+ @Override
+ public boolean isPresent()
+ {
+ return cachedDelegate().isPresent();
+ }
+
+
+ @Override
+ public T value(T defaultValue)
+ {
+ return cachedDelegate().value(defaultValue);
+ }
+
+
+ @Override
+ public T value() throws NoSuchElementException
+ {
+ return cachedDelegate().value();
+ }
+
+
+ private Optional cachedDelegate()
+ {
+ if (mCachedDelegate == null)
+ {
+ T targetValue = mTargetSingle.value();
+ mCachedDelegate = mPredicate.satisfiedBy(targetValue) ? new Present<>(targetValue) : absent();
+ }
+ return mCachedDelegate;
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/optional/Evaluated.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/optional/Evaluated.java
new file mode 100644
index 000000000..f2638282d
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/optional/Evaluated.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.jems.optional;
+
+import org.dmfs.optional.Optional;
+
+import java.util.NoSuchElementException;
+
+
+/**
+ * Eagerly evaluation {@link Optional} decorator.
+ *
+ * @author Gabor Keszthelyi
+ * @deprecated use it from jems when available
+ */
+@Deprecated
+public final class Evaluated implements Optional
+{
+ private final boolean mIsPresent;
+ private final T mValue;
+
+
+ public Evaluated(Optional delegate)
+ {
+ mIsPresent = delegate.isPresent();
+ mValue = mIsPresent ? delegate.value() : null;
+ }
+
+
+ @Override
+ public boolean isPresent()
+ {
+ return mIsPresent;
+ }
+
+
+ @Override
+ public T value(T defaultValue)
+ {
+ return mIsPresent ? mValue : defaultValue;
+ }
+
+
+ @Override
+ public T value() throws NoSuchElementException
+ {
+ if (!mIsPresent)
+ {
+ throw new NoSuchElementException("Value absent");
+ }
+ return mValue;
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/procedure/Procedure.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/procedure/Procedure.java
new file mode 100644
index 000000000..df8178b29
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/jems/procedure/Procedure.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.jems.procedure;
+
+/**
+ * @author Gabor Keszthelyi
+ * @deprecated use it from jems when available
+ */
+@Deprecated
+public interface Procedure
+{
+ void process(T t);
+
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/EffectiveDueDate.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/EffectiveDueDate.java
index e5cf713c3..d41c46dc4 100644
--- a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/EffectiveDueDate.java
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/EffectiveDueDate.java
@@ -41,7 +41,7 @@ public final class EffectiveDueDate extends DelegatingOptional
{
public static final Projection PROJECTION = new Composite<>(
new MultiProjection<>(Tasks.DUE, Tasks.DTSTART),
- TaskDateTime.PROJECTION,
+ TaskComposedDateTime.PROJECTION,
TaskDuration.PROJECTION);
@@ -49,9 +49,9 @@ public EffectiveDueDate(@NonNull RowDataSnapshot rowDataSnapshot)
{
super(new FirstPresent<>(
new Seq<>(
- new TaskDateTime(Tasks.DUE, rowDataSnapshot),
+ new TaskComposedDateTime(Tasks.DUE, rowDataSnapshot),
new Zipped<>(
- new TaskDateTime(Tasks.DTSTART, rowDataSnapshot),
+ new TaskComposedDateTime(Tasks.DTSTART, rowDataSnapshot),
new TaskDuration(rowDataSnapshot),
DateTime::addDuration))));
}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/RowDataSnapshotComposedDateTime.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/RowDataSnapshotComposedDateTime.java
new file mode 100644
index 000000000..075a8919c
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/RowDataSnapshotComposedDateTime.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.readdata;
+
+import android.support.annotation.NonNull;
+
+import org.dmfs.android.contentpal.RowDataSnapshot;
+import org.dmfs.opentaskspal.datetime.OptionalCombinedDateTime;
+import org.dmfs.opentaskspal.datetimefields.adapters.RowDataSnapshotDateTimeFields;
+import org.dmfs.optional.Optional;
+import org.dmfs.optional.decorators.DelegatingOptional;
+import org.dmfs.rfc5545.DateTime;
+
+
+/**
+ * An {@link Optional} combined date-time from a {@link RowDataSnapshot}.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class RowDataSnapshotComposedDateTime extends DelegatingOptional
+{
+ public RowDataSnapshotComposedDateTime(@NonNull RowDataSnapshot> rowDataSnapshot,
+ @NonNull String timestampColumn,
+ @NonNull String timeZoneColumn,
+ @NonNull String isAllDayColumn)
+ {
+ super(new OptionalCombinedDateTime(
+ new RowDataSnapshotDateTimeFields(rowDataSnapshot, timestampColumn, timeZoneColumn, isAllDayColumn)));
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/EffectiveTimezone.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/TaskComposedDateTime.java
similarity index 52%
rename from opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/EffectiveTimezone.java
rename to opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/TaskComposedDateTime.java
index 502a50a1f..fdbe6735d 100644
--- a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/EffectiveTimezone.java
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/TaskComposedDateTime.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 dmfs GmbH
+ * Copyright 2018 dmfs GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,29 +20,24 @@
import org.dmfs.android.contentpal.Projection;
import org.dmfs.android.contentpal.RowDataSnapshot;
-import org.dmfs.android.contentpal.projections.SingleColProjection;
-import org.dmfs.jems.single.Single;
-import org.dmfs.jems.single.decorators.DelegatingSingle;
-import org.dmfs.jems.single.elementary.ValueSingle;
+import org.dmfs.android.contentpal.projections.MultiProjection;
+import org.dmfs.optional.decorators.DelegatingOptional;
+import org.dmfs.rfc5545.DateTime;
import org.dmfs.tasks.contract.TaskContract.Tasks;
-import java.util.TimeZone;
-
/**
- * The {@link Single} effective {@link TimeZone} of a task. If the task has no TimeZone, i.e. is floating, this will return the local {@link TimeZone}.
+ * Composed date-time of a task corresponding to the given timestamp column in the provided {@link RowDataSnapshot}.
*
- * @author Marten Gajda
* @author Gabor Keszthelyi
*/
-public final class EffectiveTimezone extends DelegatingSingle
+public final class TaskComposedDateTime extends DelegatingOptional
{
- public static final Projection PROJECTION = new SingleColProjection<>(Tasks.TZ);
+ public static final Projection PROJECTION = new MultiProjection<>(Tasks.TZ, Tasks.IS_ALLDAY);
- public EffectiveTimezone(@NonNull RowDataSnapshot rowData)
+ public TaskComposedDateTime(@NonNull String timestampColumn, @NonNull RowDataSnapshot rowDataSnapshot)
{
- super(new ValueSingle<>(rowData.data(Tasks.TZ, TimeZone::getTimeZone).value()));
+ super(new RowDataSnapshotComposedDateTime(rowDataSnapshot, timestampColumn, Tasks.TZ, Tasks.IS_ALLDAY));
}
-
}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/TaskDateTime.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/TaskDateTime.java
deleted file mode 100644
index 2b5879b97..000000000
--- a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/TaskDateTime.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2017 dmfs GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.dmfs.opentaskspal.readdata;
-
-import android.support.annotation.NonNull;
-
-import org.dmfs.android.contentpal.Projection;
-import org.dmfs.android.contentpal.RowDataSnapshot;
-import org.dmfs.android.contentpal.projections.Composite;
-import org.dmfs.android.contentpal.projections.SingleColProjection;
-import org.dmfs.optional.Optional;
-import org.dmfs.optional.decorators.DelegatingOptional;
-import org.dmfs.optional.decorators.Mapped;
-import org.dmfs.rfc5545.DateTime;
-import org.dmfs.tasks.contract.TaskContract.Tasks;
-
-
-/**
- * An {@link Optional} of a specific {@link DateTime} value of a task.
- *
- * @author Marten Gajda
- * @author Gabor Keszthelyi
- */
-public final class TaskDateTime extends DelegatingOptional
-{
- public static final Projection PROJECTION = new Composite<>(
- new SingleColProjection<>(Tasks.IS_ALLDAY),
- EffectiveTimezone.PROJECTION);
-
-
- public TaskDateTime(@NonNull String columnName, @NonNull final RowDataSnapshot rowData)
- {
- super(new Mapped<>(
-
- (Long timeStamp) -> rowData.data(Tasks.IS_ALLDAY, "1"::equals).value(false) ?
- new DateTime(timeStamp).toAllDay() :
- new DateTime(timeStamp).shiftTimeZone(new EffectiveTimezone(rowData).value()),
-
- rowData.data(columnName, Long::valueOf)));
- }
-}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/CursorCombinedDateTime.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/CursorCombinedDateTime.java
new file mode 100644
index 000000000..0dfc3fa12
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/CursorCombinedDateTime.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.readdata.cursor;
+
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+
+import org.dmfs.opentaskspal.datetime.OptionalCombinedDateTime;
+import org.dmfs.opentaskspal.datetimefields.adapters.CursorDateTimeFields;
+import org.dmfs.optional.decorators.DelegatingOptional;
+import org.dmfs.rfc5545.DateTime;
+
+
+/**
+ * Combined date-time from a {@link Cursor}.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class CursorCombinedDateTime extends DelegatingOptional
+{
+ public CursorCombinedDateTime(@NonNull Cursor cursor,
+ @NonNull String timestampColumn,
+ @NonNull String timeZoneColumn,
+ @NonNull String isAllDayColumn)
+ {
+ super(new OptionalCombinedDateTime(
+ new CursorDateTimeFields(cursor, timestampColumn, timeZoneColumn, isAllDayColumn)));
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/IntCursorColumnValue.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/IntCursorColumnValue.java
new file mode 100644
index 000000000..babd85b8a
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/IntCursorColumnValue.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.readdata.cursor;
+
+import android.database.Cursor;
+
+import org.dmfs.optional.Optional;
+import org.dmfs.optional.decorators.DelegatingOptional;
+
+
+/**
+ * {@link Optional} for a {@link Integer} value in a {@link Cursor}.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class IntCursorColumnValue extends DelegatingOptional
+{
+ public IntCursorColumnValue(Cursor cursor, String columnName)
+ {
+ super(new OptionalCursorColumnValue<>(cursor, columnName, Cursor::getInt));
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/LongCursorColumnValue.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/LongCursorColumnValue.java
new file mode 100644
index 000000000..728b4e793
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/LongCursorColumnValue.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.readdata.cursor;
+
+import android.database.Cursor;
+
+import org.dmfs.optional.Optional;
+import org.dmfs.optional.decorators.DelegatingOptional;
+
+
+/**
+ * {@link Optional} for a {@link Long} value in a {@link Cursor}.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class LongCursorColumnValue extends DelegatingOptional
+{
+ public LongCursorColumnValue(Cursor cursor, String columnName)
+ {
+ super(new OptionalCursorColumnValue<>(cursor, columnName, Cursor::getLong));
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/OptionalCursorColumnValue.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/OptionalCursorColumnValue.java
new file mode 100644
index 000000000..9adbdad51
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/OptionalCursorColumnValue.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.readdata.cursor;
+
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+
+import org.dmfs.jems.function.BiFunction;
+import org.dmfs.jems.optional.decorators.Mapped;
+import org.dmfs.opentaskspal.jems.optional.Conditional;
+import org.dmfs.opentaskspal.jems.optional.Evaluated;
+import org.dmfs.optional.Optional;
+import org.dmfs.optional.decorators.DelegatingOptional;
+
+
+/**
+ * {@link Optional} for a value from a {@link Cursor}.
+ *
+ * Handles the cases where return values for primitive types cannot be assessed whether they represent absent value
+ * (like 0 for {@link Cursor#getInt(int)}) by checking {@link Cursor#isNull(int)} first.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class OptionalCursorColumnValue extends DelegatingOptional
+{
+ public OptionalCursorColumnValue(@NonNull Cursor cursor,
+ @NonNull String columnName,
+ @NonNull BiFunction getFunction)
+ {
+ // Eager evaluation to avoid capturing Cursor
+ super(new Evaluated<>(
+ new Mapped<>(columnIndex -> getFunction.value(cursor, columnIndex),
+ new Conditional(cursor::isNull,
+ cursor.getColumnIndexOrThrow(columnName))
+ ))
+ );
+ }
+}
+
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/StringCursorColumnValue.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/StringCursorColumnValue.java
new file mode 100644
index 000000000..84059afc2
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/readdata/cursor/StringCursorColumnValue.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.readdata.cursor;
+
+import android.database.Cursor;
+
+import org.dmfs.optional.Optional;
+import org.dmfs.optional.decorators.DelegatingOptional;
+
+
+/**
+ * * {@link Optional} for a {@link String} value in a {@link Cursor}.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class StringCursorColumnValue extends DelegatingOptional
+{
+ public StringCursorColumnValue(Cursor cursor, String columnName)
+ {
+ super(new OptionalCursorColumnValue<>(cursor, columnName, Cursor::getString));
+ }
+}
diff --git a/opentaskspal/src/main/java/org/dmfs/opentaskspal/utils/binarybooleans/BinaryIntBoolean.java b/opentaskspal/src/main/java/org/dmfs/opentaskspal/utils/binarybooleans/BinaryIntBoolean.java
new file mode 100644
index 000000000..d9f508137
--- /dev/null
+++ b/opentaskspal/src/main/java/org/dmfs/opentaskspal/utils/binarybooleans/BinaryIntBoolean.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.utils.binarybooleans;
+
+import android.support.annotation.Nullable;
+
+import org.dmfs.jems.single.Single;
+
+
+/**
+ * Adapter from binary {@link Integer} values 0 and 1 to {@link Boolean}.
+ * {@code null} is considered as {@code false}.
+ *
+ * (Also any other invalid value is considered as {@code false} as well.)
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class BinaryIntBoolean implements Single
+{
+ private static final Integer ONE = 1;
+
+ private final Integer mIntValue;
+
+
+ public BinaryIntBoolean(@Nullable Integer intValue)
+ {
+ mIntValue = intValue;
+ }
+
+
+ @Override
+ public Boolean value()
+ {
+ return ONE.equals(mIntValue);
+ }
+}
diff --git a/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/EffectiveDueDateTest.java b/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/EffectiveDueDateTest.java
index 24eabd921..d9960493d 100644
--- a/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/EffectiveDueDateTest.java
+++ b/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/EffectiveDueDateTest.java
@@ -17,18 +17,17 @@
package org.dmfs.opentaskspal.readdata;
import org.dmfs.android.contentpal.RowDataSnapshot;
-import org.dmfs.jems.hamcrest.matchers.AbsentMatcher;
import org.dmfs.optional.Present;
import org.dmfs.rfc5545.DateTime;
import org.dmfs.rfc5545.Duration;
import org.dmfs.tasks.contract.TaskContract.Tasks;
import org.junit.Test;
-import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
+import static org.dmfs.jems.hamcrest.matchers.AbsentMatcher.isAbsent;
import static org.dmfs.jems.mockito.doubles.TestDoubles.failingMock;
import static org.dmfs.optional.Absent.absent;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -54,8 +53,8 @@ public void test_whenDueIsPresent_shouldUseThat()
doReturn(new Present<>(timestamp)).when(mockData).data(eq(Tasks.DUE), any());
doReturn(absent()).when(mockData).data(eq(Tasks.DTSTART), any());
doReturn(absent()).when(mockData).data(eq(Tasks.DURATION), any());
- doReturn(new Present<>(false)).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
- doReturn(new Present<>(TimeZone.getTimeZone("UTC"))).when(mockData).data(eq(Tasks.TZ), any());
+ doReturn(new Present<>(0)).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
+ doReturn(new Present<>("UTC")).when(mockData).data(eq(Tasks.TZ), any());
DateTime actual = new EffectiveDueDate(mockData).value();
assertEquals(timestamp, actual.getTimestamp());
@@ -69,10 +68,10 @@ public void test_whenDueIsAbsent_startIsAbsent_durationIsAbsent_shouldBeAbsent()
doReturn(absent()).when(mockData).data(eq(Tasks.DUE), any());
doReturn(absent()).when(mockData).data(eq(Tasks.DTSTART), any());
doReturn(absent()).when(mockData).data(eq(Tasks.DURATION), any());
- doReturn(new Present<>(false)).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
- doReturn(new Present<>(TimeZone.getTimeZone("UTC"))).when(mockData).data(eq(Tasks.TZ), any());
+ doReturn(new Present<>(0)).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
+ doReturn(new Present<>("UTC")).when(mockData).data(eq(Tasks.TZ), any());
- assertThat(new EffectiveDueDate(mockData), AbsentMatcher.isAbsent());
+ assertThat(new EffectiveDueDate(mockData), isAbsent());
}
@@ -81,12 +80,12 @@ public void test_whenDueIsAbsent_startIsPresent_durationIsAbsent_shouldBeAbsent(
{
RowDataSnapshot mockData = failingMock(RowDataSnapshot.class);
doReturn(absent()).when(mockData).data(eq(Tasks.DUE), any());
- doReturn(new Present<>(234234)).when(mockData).data(eq(Tasks.DTSTART), any());
+ doReturn(new Present<>(234234L)).when(mockData).data(eq(Tasks.DTSTART), any());
doReturn(absent()).when(mockData).data(eq(Tasks.DURATION), any());
- doReturn(new Present<>(false)).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
- doReturn(new Present<>(TimeZone.getTimeZone("UTC"))).when(mockData).data(eq(Tasks.TZ), any());
+ doReturn(new Present<>(0)).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
+ doReturn(new Present<>("UTC")).when(mockData).data(eq(Tasks.TZ), any());
- assertThat(new EffectiveDueDate(mockData), AbsentMatcher.isAbsent());
+ assertThat(new EffectiveDueDate(mockData), isAbsent());
}
@@ -97,10 +96,10 @@ public void test_whenDueIsAbsent_startIsAbsent_durationIsPresent_shouldBeAbsent(
doReturn(absent()).when(mockData).data(eq(Tasks.DUE), any());
doReturn(absent()).when(mockData).data(eq(Tasks.DTSTART), any());
doReturn(new Present<>(Duration.parse("P7W"))).when(mockData).data(eq(Tasks.DURATION), any());
- doReturn(new Present<>(false)).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
- doReturn(new Present<>(TimeZone.getTimeZone("UTC"))).when(mockData).data(eq(Tasks.TZ), any());
+ doReturn(new Present<>(0)).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
+ doReturn(new Present<>("UTC")).when(mockData).data(eq(Tasks.TZ), any());
- assertThat(new EffectiveDueDate(mockData), AbsentMatcher.isAbsent());
+ assertThat(new EffectiveDueDate(mockData), isAbsent());
}
@@ -113,8 +112,8 @@ public void test_whenDueIsAbsent_startIsPresent_durationIsPresent_shouldUseStart
doReturn(absent()).when(mockData).data(eq(Tasks.DUE), any());
doReturn(new Present<>(timestamp)).when(mockData).data(eq(Tasks.DTSTART), any());
doReturn(new Present<>(Duration.parse("PT2H"))).when(mockData).data(eq(Tasks.DURATION), any());
- doReturn(new Present<>(false)).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
- doReturn(new Present<>(TimeZone.getTimeZone("Europe/Berlin"))).when(mockData).data(eq(Tasks.TZ), any());
+ doReturn(new Present<>(0)).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
+ doReturn(new Present<>("Europe/Berlin")).when(mockData).data(eq(Tasks.TZ), any());
DateTime actual = new EffectiveDueDate(mockData).value();
assertEquals(timestamp + TimeUnit.HOURS.toMillis(2), actual.getTimestamp());
diff --git a/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/TaskDateTimeTest.java b/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/RowSnapshotCombinedDateTimeTest.java
similarity index 68%
rename from opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/TaskDateTimeTest.java
rename to opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/RowSnapshotCombinedDateTimeTest.java
index 699de6a3d..431cb5fbf 100644
--- a/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/TaskDateTimeTest.java
+++ b/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/RowSnapshotCombinedDateTimeTest.java
@@ -17,17 +17,15 @@
package org.dmfs.opentaskspal.readdata;
import org.dmfs.android.contentpal.RowDataSnapshot;
-import org.dmfs.jems.hamcrest.matchers.AbsentMatcher;
import org.dmfs.optional.Present;
import org.dmfs.rfc5545.DateTime;
import org.dmfs.tasks.contract.TaskContract.Tasks;
import org.junit.Test;
-import java.util.TimeZone;
-
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.dmfs.jems.hamcrest.matchers.AbsentMatcher.isAbsent;
import static org.dmfs.jems.mockito.doubles.TestDoubles.failingMock;
import static org.dmfs.optional.Absent.absent;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -37,11 +35,11 @@
/**
- * Unit test for {@link TaskDateTime}.
+ * Unit test for {@link RowDataSnapshotComposedDateTime}.
*
* @author Gabor Keszthelyi
*/
-public final class TaskDateTimeTest
+public final class RowSnapshotCombinedDateTimeTest
{
@Test
@@ -49,8 +47,10 @@ public void test_whenColumnValueIsAbsent_shouldBeAbsent()
{
RowDataSnapshot mockData = failingMock(RowDataSnapshot.class);
doReturn(absent()).when(mockData).data(eq(Tasks.DTSTART), any());
+ doReturn(absent()).when(mockData).data(eq(Tasks.TZ), any());
+ doReturn(absent()).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
- assertThat(new TaskDateTime(Tasks.DTSTART, mockData), AbsentMatcher.isAbsent());
+ assertThat(new RowDataSnapshotComposedDateTime(mockData, Tasks.DTSTART, Tasks.TZ, Tasks.IS_ALLDAY), isAbsent());
}
@@ -61,9 +61,10 @@ public void test_whenIsAllDayIsPresentAndTrue_shouldReturnAllDayDateTime()
RowDataSnapshot mockData = failingMock(RowDataSnapshot.class);
doReturn(new Present<>(timeStamp)).when(mockData).data(eq(Tasks.DTSTART), any());
- doReturn(new Present<>(true)).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
+ doReturn(new Present<>(1)).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
+ doReturn(new Present<>("UTC")).when(mockData).data(eq(Tasks.TZ), any());
- DateTime actual = new TaskDateTime(Tasks.DTSTART, mockData).value();
+ DateTime actual = new RowDataSnapshotComposedDateTime(mockData, Tasks.DTSTART, Tasks.TZ, Tasks.IS_ALLDAY).value();
assertTrue(actual.isAllDay());
assertEquals(new DateTime(timeStamp).toAllDay(), actual);
}
@@ -76,10 +77,10 @@ public void test_whenIsAllDayIsPresentAndFalse_shouldReturnNotAllDayDateTime()
RowDataSnapshot mockData = failingMock(RowDataSnapshot.class);
doReturn(new Present<>(timeStamp)).when(mockData).data(eq(Tasks.DTSTART), any());
- doReturn(new Present<>(false)).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
- doReturn(new Present<>(TimeZone.getTimeZone("UTC"))).when(mockData).data(eq(Tasks.TZ), any());
+ doReturn(new Present<>(0)).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
+ doReturn(new Present<>("UTC")).when(mockData).data(eq(Tasks.TZ), any());
- DateTime actual = new TaskDateTime(Tasks.DTSTART, mockData).value();
+ DateTime actual = new RowDataSnapshotComposedDateTime(mockData, Tasks.DTSTART, Tasks.TZ, Tasks.IS_ALLDAY).value();
assertFalse(actual.isAllDay());
assertEquals(timeStamp, actual.getTimestamp());
}
@@ -92,10 +93,10 @@ public void test_whenIsAllDayIsFalse_shouldReturnDateTimeWithTimeZoneShifted()
RowDataSnapshot mockData = failingMock(RowDataSnapshot.class);
doReturn(new Present<>(timeStamp)).when(mockData).data(eq(Tasks.DTSTART), any());
- doReturn(new Present<>(false)).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
- doReturn(new Present<>(TimeZone.getTimeZone("Europe/Berlin"))).when(mockData).data(eq(Tasks.TZ), any());
+ doReturn(new Present<>(0)).when(mockData).data(eq(Tasks.IS_ALLDAY), any());
+ doReturn(new Present<>("Europe/Berlin")).when(mockData).data(eq(Tasks.TZ), any());
- DateTime actual = new TaskDateTime(Tasks.DTSTART, mockData).value();
+ DateTime actual = new RowDataSnapshotComposedDateTime(mockData, Tasks.DTSTART, Tasks.TZ, Tasks.IS_ALLDAY).value();
assertFalse(actual.isAllDay());
assertEquals(timeStamp, actual.getTimestamp());
assertEquals("Europe/Berlin", actual.getTimeZone().getID());
diff --git a/opentaskspal/src/test/java/org/dmfs/opentaskspal/utils/binarybooleans/BinaryIntBooleanTest.java b/opentaskspal/src/test/java/org/dmfs/opentaskspal/utils/binarybooleans/BinaryIntBooleanTest.java
new file mode 100644
index 000000000..e821da5c9
--- /dev/null
+++ b/opentaskspal/src/test/java/org/dmfs/opentaskspal/utils/binarybooleans/BinaryIntBooleanTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018 dmfs GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.opentaskspal.utils.binarybooleans;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+
+/**
+ * Unit test for {@link BinaryIntBoolean}.
+ *
+ * @author Gabor Keszthelyi
+ */
+public final class BinaryIntBooleanTest
+{
+
+ @Test
+ public void test()
+ {
+ assertThat(new BinaryIntBoolean(1).value(), is(true));
+ assertThat(new BinaryIntBoolean(0).value(), is(false));
+ assertThat(new BinaryIntBoolean(null).value(), is(false));
+
+ // Invalid values, considered as false to avoid validation code:
+ assertThat(new BinaryIntBoolean(-1).value(), is(false));
+ assertThat(new BinaryIntBoolean(2).value(), is(false));
+ }
+
+}
\ No newline at end of file