Skip to content

Commit

Permalink
DateTime support in DateFormatter. #476
Browse files Browse the repository at this point in the history
  • Loading branch information
Gabor Keszthelyi committed Nov 17, 2017
1 parent a29d04c commit 9fad3e6
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 7 deletions.
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ TARGET_SDK_VERSION=25
SUPPORT_LIBRARY_VERSION=25.0.1
CONTENTPAL_VERSION=fc8cca91
ROBOLECTRIC_VERSION=3.1.4
JEMS_VERSION=1.13
JEMS_VERSION=1.13
ANDROID_TEST_RUNNER_VERSION=0.5
8 changes: 8 additions & 0 deletions opentasks/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ android {
targetSdkVersion TARGET_SDK_VERSION.toInteger()
versionCode gitCommitNo() * 10 // spread version code to allow inserting versions if necessary
versionName version
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
Expand Down Expand Up @@ -65,4 +66,11 @@ dependencies {
compile 'org.dmfs:jems:' + JEMS_VERSION

testCompile 'junit:junit:4.12'

androidTestCompile ('com.android.support.test:runner:' + ANDROID_TEST_RUNNER_VERSION) {
exclude group: 'com.android.support', module: 'support-annotations'
}
androidTestCompile ('com.android.support.test:rules:' + ANDROID_TEST_RUNNER_VERSION) {
exclude group: 'com.android.support', module: 'support-annotations'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* 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.utils;

import android.support.test.runner.AndroidJUnit4;
import android.text.format.Time;

import org.dmfs.rfc5545.DateTime;
import org.dmfs.rfc5545.Duration;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.TimeZone;


/**
* Test for {@link DateFormatter#toTime(DateTime)} method.
*
* @author Gabor Keszthelyi
*/
@RunWith(AndroidJUnit4.class)
public class DateTimeToTimeConversionTest
{

@Test
public void test_toTime_withVariousDateTimes()
{
assertCorrectlyConverted(DateTime.now());

assertCorrectlyConverted(DateTime.now(TimeZone.getTimeZone("UTC+04:00")));

assertCorrectlyConverted(DateTime.nowAndHere());

assertCorrectlyConverted(new DateTime(1509473781000L));

assertCorrectlyConverted(new DateTime(1509473781000L).addDuration(new Duration(1, 1, 0)));

assertCorrectlyConverted(DateTime.now(TimeZone.getTimeZone("UTC+04:00")).shiftTimeZone(TimeZone.getTimeZone("UTC+05:00")));

// Floating, all-day
assertCorrectlyConverted(DateTime.now().toAllDay());

// Not DST (March 2017 in Hungary):
assertCorrectlyConverted(new DateTime(TimeZone.getTimeZone("Europe/Budapest"), 2017, 2 - 1, 7, 15, 0, 0));
assertCorrectlyConverted(new DateTime(2017, 2 - 1, 7, 15, 0, 0).shiftTimeZone(TimeZone.getTimeZone("Europe/Budapest")));
assertCorrectlyConverted(new DateTime(2017, 2 - 1, 7, 15, 0, 0).swapTimeZone(TimeZone.getTimeZone("Europe/Budapest")));

// DST (July 2017 in Hungary):
assertCorrectlyConverted(new DateTime(TimeZone.getTimeZone("Europe/Budapest"), 2017, 7 - 1, 7, 15, 0, 0));
assertCorrectlyConverted(new DateTime(2017, 7 - 1, 7, 15, 0, 0).shiftTimeZone(TimeZone.getTimeZone("Europe/Budapest")));
assertCorrectlyConverted(new DateTime(2017, 7 - 1, 7, 15, 0, 0).swapTimeZone(TimeZone.getTimeZone("Europe/Budapest")));
}


@Test(expected = IllegalArgumentException.class)
public void test_toTime_forFloatingButNotAllDayDateTime_throwsSinceItIsNotSupported()
{
new DateFormatter(null).toTime(new DateTime(2017, 7 - 1, 7, 15, 0, 0));
}


private void assertCorrectlyConverted(DateTime dateTime)
{
Time time = new DateFormatter(null).toTime(dateTime);
if (!isEquivalentDateTimeAndTime(dateTime, time))
{
throw new AssertionError(String.format("DateTime=%s and Time=%s are not equivalent", dateTime, time));
}
}


/**
* Contains the definition/requirement of when a {@link DateTime} and {@link Time} is considered equivalent in this project.
*/
private boolean isEquivalentDateTimeAndTime(DateTime dateTime, Time time)
{
// android.text.Time doesn't seem to store in millis precision, there is a 1000 multiplier used there internally
// when calculating millis, so we can only compare in this precision:
boolean millisMatch =
dateTime.getTimestamp() / 1000
==
time.toMillis(false) / 1000;

boolean allDaysMatch = time.allDay == dateTime.isAllDay();

boolean timeZoneMatch =
// If DateTime is floating, all-day then if the all-day flag is matched with Time (checked earlier)
// then we consider the Time's timezone matching, we ignore that basically,
// because Time always has a time zone, and there is no other way to represent all-day date-times with Time.
(dateTime.isFloating() && dateTime.isAllDay())
||
// This is the regular case with non-floating DateTime
(dateTime.getTimeZone() != null && time.timezone.equals(dateTime.getTimeZone().getID()));

return millisMatch && allDaysMatch && timeZoneMatch;
}

}
4 changes: 2 additions & 2 deletions opentasks/src/main/java/org/dmfs/tasks/ViewTaskFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ public void loadUri(Uri uri)

if ((oldUri == null) != (uri == null))
{
/*
/*
* getActivity().invalidateOptionsMenu() doesn't work in Android 2.x so use the compat lib
*/
ActivityCompat.invalidateOptionsMenu(getActivity());
Expand Down Expand Up @@ -491,7 +491,7 @@ public void onModelLoaded(Model model)
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
{
/*
/*
* Don't show any options if we don't have a task to show.
*/
if (mTaskUri != null)
Expand Down
63 changes: 59 additions & 4 deletions opentasks/src/main/java/org/dmfs/tasks/utils/DateFormatter.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.VisibleForTesting;
import android.text.format.DateUtils;
import android.text.format.Time;

import org.dmfs.rfc5545.DateTime;
import org.dmfs.tasks.R;

import java.text.DateFormat;
Expand Down Expand Up @@ -199,9 +201,9 @@ public boolean useRelative(Time now, Time date)


/**
* The formatter we use for due dates other than today.
* The format we use for due dates other than today.
*/
private final DateFormat mDateFormatter = DateFormat.getDateInstance(SimpleDateFormat.MEDIUM);
private final DateFormat mDateFormat = DateFormat.getDateInstance(SimpleDateFormat.MEDIUM);

/**
* A context to load resource string.
Expand Down Expand Up @@ -240,6 +242,17 @@ public String format(Time date, DateFormatContext dateContext)
}


/**
* Same as {@link #format(Time, DateFormatContext)} just with {@link DateTime}s.
* ({@link Time} will eventually be replaced with {@link DateTime} in the project)
*/
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public String format(DateTime date, DateFormatContext dateContext)
{
return format(toTime(date), dateContext);
}


/**
* Format the given due date. The result depends on the current date and on the all-day flag of the due date.
*
Expand Down Expand Up @@ -294,6 +307,17 @@ else if (delta < 24 * 60 * 60 * 1000)
}


/**
* Same as {@link #format(Time, Time, DateFormatContext)} just with {@link DateTime}s.
* ({@link Time} will eventually be replaced with {@link DateTime} in the project)
*/
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public String format(DateTime date, DateTime now, DateFormatContext dateContext)
{
return format(toTime(date), toTime(now), dateContext);
}


@SuppressLint("NewApi")
private String formatAllDay(Time date, Time now, DateFormatContext dateContext)
{
Expand All @@ -305,8 +329,8 @@ private String formatAllDay(Time date, Time now, DateFormatContext dateContext)
}
else
{
mDateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
return mDateFormatter.format(new Date(date.toMillis(false)));
mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return mDateFormat.format(new Date(date.toMillis(false)));
}
}

Expand All @@ -315,4 +339,35 @@ private String formatNonAllDay(Time date, Time now, DateFormatContext dateContex
{
return DateUtils.formatDateTime(mContext, date.toMillis(false), dateContext.getDateUtilsFlags(now, date));
}


/**
* {@link Time} will eventually be replaced with {@link DateTime} in the project.
* This conversion function is only needed in the transition period.
*/
@VisibleForTesting
Time toTime(DateTime dateTime)
{
if (dateTime.isFloating() && !dateTime.isAllDay())
{
throw new IllegalArgumentException("Cannot support floating DateTime that is not all-day, can't represent it with Time");
}

// Time always needs a TimeZone (default ctor falls back to TimeZone.getDefault())
String timeZoneId = dateTime.getTimeZone() == null ? TimeZone.getDefault().getID() : dateTime.getTimeZone().getID();
Time time = new Time(timeZoneId);

time.set(dateTime.getTimestamp());

// TODO Would using time.set(monthDay, month, year) be better?
if (dateTime.isAllDay())
{
time.allDay = true;
// This is needed as per time.allDay docs:
time.hour = 0;
time.minute = 0;
time.second = 0;
}
return time;
}
}

0 comments on commit 9fad3e6

Please sign in to comment.