brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From henev...@apache.org
Subject [05/10] incubator-brooklyn git commit: switch Time api around dates to be based on Calendar so we preserve time zone
Date Mon, 15 Jun 2015 07:41:29 GMT
switch Time api around dates to be based on Calendar so we preserve time zone


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/51ba0aaf
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/51ba0aaf
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/51ba0aaf

Branch: refs/heads/master
Commit: 51ba0aaf173259881476b88ce37456a5e0665d0a
Parents: c968211
Author: Alex Heneveld <alex.heneveld@cloudsoftcorp.com>
Authored: Tue Jun 9 11:17:22 2015 +0100
Committer: Alex Heneveld <alex.heneveld@cloudsoftcorp.com>
Committed: Wed Jun 10 18:38:35 2015 +0100

----------------------------------------------------------------------
 .../brooklyn/rest/resources/UsageResource.java  |  14 +--
 .../rest/resources/UsageResourceTest.java       | 124 +++++++++---------
 .../src/main/java/brooklyn/util/time/Time.java  | 126 ++++++++++++++-----
 .../test/java/brooklyn/util/time/TimeTest.java  |  34 ++++-
 4 files changed, 187 insertions(+), 111 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/51ba0aaf/usage/rest-server/src/main/java/brooklyn/rest/resources/UsageResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/UsageResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/UsageResource.java
index 4251712..08c0668 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/UsageResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/UsageResource.java
@@ -21,7 +21,6 @@ package brooklyn.rest.resources;
 import static brooklyn.rest.util.WebResourceUtils.notFound;
 
 import java.net.URI;
-import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.List;
 import java.util.Set;
@@ -41,6 +40,7 @@ import brooklyn.rest.domain.UsageStatistic;
 import brooklyn.rest.domain.UsageStatistics;
 import brooklyn.rest.transform.ApplicationTransformer;
 import brooklyn.util.exceptions.UserFacingException;
+import brooklyn.util.text.Strings;
 import brooklyn.util.time.Time;
 
 import com.google.common.base.Objects;
@@ -57,14 +57,6 @@ public class UsageResource extends AbstractBrooklynRestResource implements
Usage
 
     private static final Set<Lifecycle> WORKING_LIFECYCLES = ImmutableSet.of(Lifecycle.RUNNING,
Lifecycle.CREATED, Lifecycle.STARTING);
 
-    // SimpleDateFormat is not thread-safe, so give one to each thread
-    private static final ThreadLocal<SimpleDateFormat> DATE_FORMATTER = new ThreadLocal<SimpleDateFormat>(){
-        @Override
-        protected SimpleDateFormat initialValue() {
-            return new SimpleDateFormat(DATE_FORMAT);
-        }
-    };
-    
     @Override
     public List<UsageStatistics> listApplicationsUsage(@Nullable String start, @Nullable
String end) {
         log.debug("REST call to get application usage for all applications: dates {} ->
{}", new Object[] {start, end});
@@ -256,10 +248,10 @@ public class UsageResource extends AbstractBrooklynRestResource implements
Usage
     }
 
     private Date parseDate(String toParse, Date def) {
-        return (toParse == null) ? def : Time.parseDate(toParse, DATE_FORMATTER.get());
+        return Strings.isBlank(toParse) ? def : Time.parseDate(toParse);
     }
     
     private String format(Date date) {
-        return DATE_FORMATTER.get().format(date);
+        return Time.makeDateString(date, Time.DATE_FORMAT_ISO8601_NO_MILLIS, Time.TIME_ZONE_UTC);
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/51ba0aaf/usage/rest-server/src/test/java/brooklyn/rest/resources/UsageResourceTest.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/resources/UsageResourceTest.java
b/usage/rest-server/src/test/java/brooklyn/rest/resources/UsageResourceTest.java
index ad67c51..533b220 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/resources/UsageResourceTest.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/resources/UsageResourceTest.java
@@ -21,10 +21,9 @@ package brooklyn.rest.resources;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
 import java.util.Arrays;
-import java.util.Date;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
@@ -56,6 +55,7 @@ import brooklyn.rest.testing.BrooklynRestResourceTest;
 import brooklyn.rest.testing.mocks.RestMockSimpleEntity;
 import brooklyn.test.entity.TestApplication;
 import brooklyn.util.repeat.Repeater;
+import brooklyn.util.time.Time;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -70,8 +70,7 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
 
     private static final long TIMEOUT_MS = 10*1000;
     
-    private Date testStartTime;
-    private DateFormat format = new SimpleDateFormat(AbstractBrooklynRestResource.DATE_FORMAT);
+    private Calendar testStartTime;
     
     private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").
             entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()))).
@@ -82,20 +81,20 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
     public void setUpMethod() {
         ((ManagementContextInternal)getManagementContext()).getStorage().remove(LocalUsageManager.APPLICATION_USAGE_KEY);
         ((ManagementContextInternal)getManagementContext()).getStorage().remove(LocalUsageManager.LOCATION_USAGE_KEY);
-        testStartTime = new Date();
+        testStartTime = new GregorianCalendar();
     }
 
     @Test
     public void testListApplicationUsages() throws Exception {
         // Create an app
-        Date preStart = new Date();
+        Calendar preStart = new GregorianCalendar();
         String appId = createApp(simpleSpec);
-        Date postStart = new Date();
+        Calendar postStart = new GregorianCalendar();
         
         // We will retrieve usage from one millisecond after start; this guarantees to not
be  
         // told about both STARTING+RUNNING, which could otherwise happen if they are in
the 
         // same milliscond.
-        Date afterPostStart = new Date(postStart.getTime()+1);
+        Calendar afterPostStart = Time.newCalendarFromMillisSinceEpochUtc(postStart.getTime().getTime()+1);
         
         // Check that app's usage is returned
         ClientResponse response = client().resource("/v1/usage/applications").get(ClientResponse.class);
@@ -104,15 +103,15 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         UsageStatistics usage = Iterables.getOnlyElement(usages);
         assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart),
postStart);
 
-        // check app ignored if endDate before app started
-        response = client().resource("/v1/usage/applications?start="+0+"&end="+(preStart.getTime()-1)).get(ClientResponse.class);
+        // check app ignored if endCalendar before app started
+        response = client().resource("/v1/usage/applications?start="+0+"&end="+(preStart.getTime().getTime()-1)).get(ClientResponse.class);
         assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
         usages = response.getEntity(new GenericType<List<UsageStatistics>>()
{});
         assertTrue(Iterables.isEmpty(usages), "usages="+usages);
         
         // Wait, so that definitely asking about things that have happened (not things in
the future, 
         // or events that are happening this exact same millisecond)
-        waitForFuture(afterPostStart.getTime());
+        waitForFuture(afterPostStart.getTime().getTime());
 
         // Check app start + end date truncated, even if running for longer (i.e. only tell
us about this time window).
         // Note that start==end means we get a snapshot of the apps in use at that exact
time.
@@ -121,7 +120,7 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         // The comparison does use the milliseconds passed in the REST call though.
         // The rounding down result should be the same as roundDown(afterPostStart), because
that is the time-window
         // we asked for.
-        response = client().resource("/v1/usage/applications?start="+afterPostStart.getTime()+"&end="+afterPostStart.getTime()).get(ClientResponse.class);
+        response = client().resource("/v1/usage/applications?start="+afterPostStart.getTime().getTime()+"&end="+afterPostStart.getTime().getTime()).get(ClientResponse.class);
         assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
         usages = response.getEntity(new GenericType<List<UsageStatistics>>()
{});
         usage = Iterables.getOnlyElement(usages);
@@ -129,9 +128,9 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         assertAppUsageTimesTruncated(usage, roundDown(afterPostStart), roundDown(afterPostStart));
 
         // Delete the app
-        Date preDelete = new Date();
+        Calendar preDelete = new GregorianCalendar();
         deleteApp(appId);
-        Date postDelete = new Date();
+        Calendar postDelete = new GregorianCalendar();
 
         // Deleted app still returned, if in time range
         response = client().resource("/v1/usage/applications").get(ClientResponse.class);
@@ -141,7 +140,7 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING, Status.DESTROYED),
roundDown(preStart), postDelete);
         assertAppUsage(ImmutableList.copyOf(usage.getStatistics()).subList(2, 3), appId,
ImmutableList.of(Status.DESTROYED), roundDown(preDelete), postDelete);
 
-        long afterPostDelete = postDelete.getTime()+1;
+        long afterPostDelete = postDelete.getTime().getTime()+1;
         waitForFuture(afterPostDelete);
         
         response = client().resource("/v1/usage/applications?start=" + afterPostDelete).get(ClientResponse.class);
@@ -159,9 +158,9 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
     @Test
     public void testGetApplicationUsage() throws Exception {
         // Create an app
-        Date preStart = new Date();
+        Calendar preStart = new GregorianCalendar();
         String appId = createApp(simpleSpec);
-        Date postStart = new Date();
+        Calendar postStart = new GregorianCalendar();
         
         // Normal request returns all
         ClientResponse response = client().resource("/v1/usage/applications/" + appId).get(ClientResponse.class);
@@ -198,9 +197,9 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         assertTrue(usage.getStatistics().isEmpty());
         
         // Delete the app
-        Date preDelete = new Date();
+        Calendar preDelete = new GregorianCalendar();
         deleteApp(appId);
-        Date postDelete = new Date();
+        Calendar postDelete = new GregorianCalendar();
 
         // Deleted app still returned, if in time range
         response = client().resource("/v1/usage/applications/" + appId).get(ClientResponse.class);
@@ -210,7 +209,7 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         assertAppUsage(ImmutableList.copyOf(usage.getStatistics()).subList(2, 3), appId,
ImmutableList.of(Status.DESTROYED), roundDown(preDelete), postDelete);
 
         // Deleted app not returned if terminated before time range begins
-        long afterPostDelete = postDelete.getTime()+1;
+        long afterPostDelete = postDelete.getTime().getTime()+1;
         waitForFuture(afterPostDelete);
         response = client().resource("/v1/usage/applications/" + appId +"?start=" + afterPostDelete).get(ClientResponse.class);
         assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
@@ -243,9 +242,9 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         TestApplication app = ApplicationBuilder.newManagedApp(TestApplication.class, getManagementContext());
         SoftwareProcessEntityTest.MyService entity = app.createAndManageChild(brooklyn.entity.proxying.EntitySpec.create(SoftwareProcessEntityTest.MyService.class));
         
-        Date preStart = new Date();
+        Calendar preStart = new GregorianCalendar();
         app.start(ImmutableList.of(location));
-        Date postStart = new Date();
+        Calendar postStart = new GregorianCalendar();
         Location machine = Iterables.getOnlyElement(entity.getLocations());
 
         // All machines
@@ -270,9 +269,9 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         SoftwareProcessEntityTest.MyService entity = app.createAndManageChild(brooklyn.entity.proxying.EntitySpec.create(SoftwareProcessEntityTest.MyService.class));
         String appId = app.getId();
         
-        Date preStart = new Date();
+        Calendar preStart = new GregorianCalendar();
         app.start(ImmutableList.of(location));
-        Date postStart = new Date();
+        Calendar postStart = new GregorianCalendar();
         Location machine = Iterables.getOnlyElement(entity.getLocations());
 
         // For running machine
@@ -283,9 +282,9 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         assertMachineUsage(usage, app.getId(), machine.getId(), ImmutableList.of(Status.ACCEPTED),
roundDown(preStart), postStart);
         
         // Stop the machine
-        Date preStop = new Date();
+        Calendar preStop = new GregorianCalendar();
         app.stop();
-        Date postStop = new Date();
+        Calendar postStop = new GregorianCalendar();
         
         // Deleted machine still returned, if in time range
         response = client().resource("/v1/usage/machines?application=" + appId).get(ClientResponse.class);
@@ -296,7 +295,7 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         assertMachineUsage(ImmutableList.copyOf(usage.getStatistics()).subList(1,2), appId,
machine.getId(), ImmutableList.of(Status.DESTROYED), roundDown(preStop), postStop);
 
         // Terminated machines ignored if terminated since start-time
-        long futureTime = postStop.getTime()+1;
+        long futureTime = postStop.getTime().getTime()+1;
         waitForFuture(futureTime);
         response = client().resource("/v1/usage/applications?start=" + futureTime).get(ClientResponse.class);
         assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
@@ -320,18 +319,18 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         waitForTask(deletionTask.getId());
     }
     
-    private void assertDateOrders(Object context, Date... dates) {
-        if (dates.length <= 1) return;
+    private void assertCalendarOrders(Object context, Calendar... Calendars) {
+        if (Calendars.length <= 1) return;
         
-        long[] times = new long[dates.length];
+        long[] times = new long[Calendars.length];
         for (int i = 0; i < times.length; i++) {
-            times[i] = millisSinceStart(dates[i]);
+            times[i] = millisSinceStart(Calendars[i]);
         }
-        String err = "context="+context+"; dates="+Arrays.toString(dates) + "; datesSanitized="+Arrays.toString(times);
+        String err = "context="+context+"; Calendars="+Arrays.toString(Calendars) + "; CalendarsSanitized="+Arrays.toString(times);
         
-        Date date = dates[0];
-        for (int i = 1; i < dates.length; i++) {
-            assertTrue(date.getTime() <= dates[i].getTime(), err);
+        Calendar Calendar = Calendars[0];
+        for (int i = 1; i < Calendars.length; i++) {
+            assertTrue(Calendar.getTime().getTime() <= Calendars[i].getTime().getTime(),
err);
         }
     }
     
@@ -353,56 +352,56 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         assertTrue(success, "task "+taskId+" not finished");
     }
 
-    private long millisSinceStart(Date time) {
-        return time.getTime() - testStartTime.getTime();
+    private long millisSinceStart(Calendar time) {
+        return time.getTime().getTime() - testStartTime.getTime().getTime();
     }
     
-    private Date roundDown(Date date) {
-        long time = date.getTime();
+    private Calendar roundDown(Calendar calendar) {
+        long time = calendar.getTime().getTime();
         long timeDown = ((long)(time / 1000)) * 1000;
-        return new Date(timeDown);
+        return Time.newCalendarFromMillisSinceEpochUtc(timeDown);
     }
     
     @SuppressWarnings("unused")
-    private Date roundUp(Date date) {
-        long time = date.getTime();
+    private Calendar roundUp(Calendar calendar) {
+        long time = calendar.getTime().getTime();
         long timeDown = ((long)(time / 1000)) * 1000;
         long timeUp = (time == timeDown) ? time : timeDown + 1000;
-        return new Date(timeUp);
+        return Time.newCalendarFromMillisSinceEpochUtc(timeUp);
     }
 
-    private void assertMachineUsage(UsageStatistics usage, String appId, String machineId,
List<Status> states, Date pre, Date post) throws Exception {
+    private void assertMachineUsage(UsageStatistics usage, String appId, String machineId,
List<Status> states, Calendar pre, Calendar post) throws Exception {
         assertUsage(usage.getStatistics(), appId, machineId, states, pre, post, false);
     }
     
-    private void assertMachineUsage(Iterable<UsageStatistic> usages, String appId,
String machineId, List<Status> states, Date pre, Date post) throws Exception {
+    private void assertMachineUsage(Iterable<UsageStatistic> usages, String appId,
String machineId, List<Status> states, Calendar pre, Calendar post) throws Exception
{
         assertUsage(usages, appId, machineId, states, pre, post, false);
     }
     
-    private void assertAppUsage(UsageStatistics usage, String appId, List<Status> states,
Date pre, Date post) throws Exception {
+    private void assertAppUsage(UsageStatistics usage, String appId, List<Status> states,
Calendar pre, Calendar post) throws Exception {
         assertUsage(usage.getStatistics(), appId, appId, states, pre, post, false);
     }
     
-    private void assertAppUsage(Iterable<UsageStatistic> usages, String appId, List<Status>
states, Date pre, Date post) throws Exception {
+    private void assertAppUsage(Iterable<UsageStatistic> usages, String appId, List<Status>
states, Calendar pre, Calendar post) throws Exception {
         assertUsage(usages, appId, appId, states, pre, post, false);
     }
 
-    private void assertUsage(Iterable<UsageStatistic> usages, String appId, String
id, List<Status> states, Date pre, Date post, boolean allowGaps) throws Exception {
+    private void assertUsage(Iterable<UsageStatistic> usages, String appId, String
id, List<Status> states, Calendar pre, Calendar post, boolean allowGaps) throws Exception
{
         String errMsg = "usages="+usages;
-        Date now = new Date();
-        Date lowerBound = pre;
-        Date strictStart = null;
+        Calendar now = new GregorianCalendar();
+        Calendar lowerBound = pre;
+        Calendar strictStart = null;
         
         assertEquals(Iterables.size(usages), states.size(), errMsg);
         for (int i = 0; i < Iterables.size(usages); i++) {
             UsageStatistic usage = Iterables.get(usages, i);
-            Date usageStart = format.parse(usage.getStart());
-            Date usageEnd = format.parse(usage.getEnd());
+            Calendar usageStart = Time.parseCalendar(usage.getStart());
+            Calendar usageEnd = Time.parseCalendar(usage.getEnd());
             assertEquals(usage.getId(), id, errMsg);
             assertEquals(usage.getApplicationId(), appId, errMsg);
             assertEquals(usage.getStatus(), states.get(i), errMsg);
-            assertDateOrders(usages, lowerBound, usageStart, post);
-            assertDateOrders(usages, usageEnd, now);
+            assertCalendarOrders(usages, lowerBound, usageStart, post);
+            assertCalendarOrders(usages, usageEnd, now);
             if (strictStart != null) {
                 assertEquals(usageStart, strictStart, errMsg);
             }
@@ -413,12 +412,13 @@ public class UsageResourceTest extends BrooklynRestResourceTest {
         }
     }
 
-    private void assertAppUsageTimesTruncated(UsageStatistics usages, Date strictStart, Date
strictEnd) throws Exception {
-        String errMsg = "usages="+usages+"; strictStart="+strictStart+"; strictEnd="+strictEnd;
-        Date usageStart = format.parse(Iterables.getFirst(usages.getStatistics(), null).getStart());
-        Date usageEnd = format.parse(Iterables.getLast(usages.getStatistics()).getStart());
-        assertEquals(usageStart, strictStart, errMsg);
-        assertEquals(usageEnd, strictEnd, errMsg);
+    private void assertAppUsageTimesTruncated(UsageStatistics usages, Calendar strictStart,
Calendar strictEnd) throws Exception {
+        String errMsg = "strictStart="+Time.makeDateString(strictStart)+"; strictEnd="+Time.makeDateString(strictEnd)+";usages="+usages;
+        Calendar usageStart = Time.parseCalendar(Iterables.getFirst(usages.getStatistics(),
null).getStart());
+        Calendar usageEnd = Time.parseCalendar(Iterables.getLast(usages.getStatistics()).getStart());
+        // time zones might be different - so must convert to date
+        assertEquals(usageStart.getTime(), strictStart.getTime(), "usageStart="+Time.makeDateString(usageStart)+";"+errMsg);
+        assertEquals(usageEnd.getTime(), strictEnd.getTime(), errMsg);
     }
     
     public static class DynamicLocalhostMachineProvisioningLocation extends LocalhostMachineProvisioningLocation
{

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/51ba0aaf/utils/common/src/main/java/brooklyn/util/time/Time.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/time/Time.java b/utils/common/src/main/java/brooklyn/util/time/Time.java
index 1c302d6..9e29ae2 100644
--- a/utils/common/src/main/java/brooklyn/util/time/Time.java
+++ b/utils/common/src/main/java/brooklyn/util/time/Time.java
@@ -55,6 +55,8 @@ public class Time {
     public static final String DATE_FORMAT_STAMP = "yyyyMMdd-HHmmssSSS";
     public static final String DATE_FORMAT_SIMPLE_STAMP = "yyyy-MM-dd-HHmm";
     public static final String DATE_FORMAT_OF_DATE_TOSTRING = "EEE MMM dd HH:mm:ss zzz yyyy";
+    public static final String DATE_FORMAT_ISO8601 = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
+    public static final String DATE_FORMAT_ISO8601_NO_MILLIS = "yyyy-MM-dd'T'HH:mm:ssZ";
 
     public static final long MILLIS_IN_SECOND = 1000;
     public static final long MILLIS_IN_MINUTE = 60*MILLIS_IN_SECOND;
@@ -62,15 +64,41 @@ public class Time {
     public static final long MILLIS_IN_DAY = 24*MILLIS_IN_HOUR;
     public static final long MILLIS_IN_YEAR = 365*MILLIS_IN_DAY;
     
-    /** returns the current time in {@value #DATE_FORMAT_PREFERRED} format,
-     * numeric big-endian but otherwise optimized for people to read, with spaces and colons
and dots */
+    /** GMT/UTC/Z time zone constant */
+    public static final TimeZone TIME_ZONE_UTC = TimeZone.getTimeZone("");
+    
+    /** as {@link #makeDateString(Date)} for current date/time */
     public static String makeDateString() {
         return makeDateString(System.currentTimeMillis());
     }
 
-    /** returns the time in {@value #DATE_FORMAT_PREFERRED} format, given a long (e.g. returned
by System.currentTimeMillis) */
+    /** as {@link #makeDateString(Date)} for long millis since UTC epock */
     public static String makeDateString(long date) {
-        return new SimpleDateFormat(DATE_FORMAT_PREFERRED).format(new Date(date));
+        return makeDateString(new Date(date), DATE_FORMAT_PREFERRED);
+    }
+    /** returns the time in {@value #DATE_FORMAT_PREFERRED} format for the given date;
+     * this format is numeric big-endian but otherwise optimized for people to read, with
spaces and colons and dots;
+     * time is local to the server and time zone is <i>not</i> included */
+    public static String makeDateString(Date date) {
+        return makeDateString(date, DATE_FORMAT_PREFERRED);
+    }
+    /** as {@link #makeDateString(Date, String, TimeZone)} for the local time zone */
+    public static String makeDateString(Date date, String format) {
+        return makeDateString(date, format, null);
+    }
+    /** as {@link #makeDateString(Date, String, TimeZone)} for the given time zone; consider
{@link TimeZone#GMT} */
+    public static String makeDateString(Date date, String format, TimeZone tz) {
+        SimpleDateFormat fmt = new SimpleDateFormat(format);
+        if (tz!=null) fmt.setTimeZone(tz);
+        return fmt.format(date);
+    }
+    /** as {@link #makeDateString(Date, String)} using {@link #DATE_FORMAT_PREFERRED_W_TZ}
*/
+    public static String makeDateString(Calendar date) {
+        return makeDateString(date.getTime(), DATE_FORMAT_PREFERRED_W_TZ);
+    }
+    /** as {@link #makeDateString(Date, String, TimeZone)} for the time zone of the given
calendar object */
+    public static String makeDateString(Calendar date, String format) {
+        return makeDateString(date.getTime(), format, date.getTimeZone());
     }
 
     public static Function<Long, String> toDateString() { return dateString; }
@@ -472,48 +500,72 @@ public class Time {
         }
     }
 
+    public static Calendar newCalendarFromMillisSinceEpochUtc(long timestamp) {
+        GregorianCalendar cal = new GregorianCalendar();
+        cal.setTimeInMillis(timestamp);
+        return cal;
+    }
+
+    public static Calendar newCalendarFromDate(Date date) {
+        return newCalendarFromMillisSinceEpochUtc(date.getTime());
+    }
+    
+    /** As {@link #parseCalendar(String)} but returning a {@link Date},
+     * (i.e. a record where the time zone has been applied and forgotten). */
+    public static Date parseDate(String input) {
+        if (input==null) return null;
+        return parseCalendarMaybe(input).get().getTime();
+    }
+
     /** Parses dates from string, accepting many formats including ISO-8601 and http://yaml.org/type/timestamp.html,

-     * e.g. 2015-06-15 16:00:00 +0000. Millis since eopch UTC is also supported.
+     * e.g. 2015-06-15 16:00:00 +0000.
+     * <p>
+     * Millis since epoch (1970) is also supported to represent the epoch (0) or dates in
this millenium,
+     * but to prevent ambiguity of e.g. "20150615", any other dates prior to the year 2001
are not accepted.
+     * (However if a type Long is supplied, e.g. from a YAML parse, it will always be treated
as millis since epoch.) 
+     * <p>
      * Other formats including locale-specific variants, e.g. recognising month names,
      * are supported but this may vary from platform to platform and may change between versions.
*/
-    public static Date parseDate(String input) {
+    public static Calendar parseCalendar(String input) {
         if (input==null) return null;
-        return parseDateMaybe(input).get();
+        return parseCalendarMaybe(input).get();
     }
     
-    /** as {@link #parseDate(String)} but returning a {@link Maybe} rather than throwing
or returning null */
-    public static Maybe<Date> parseDateMaybe(String input) {
+    /** as {@link #parseCalendar(String)} but returning a {@link Maybe} rather than throwing
or returning null */
+    public static Maybe<Calendar> parseCalendarMaybe(String input) {
         if (input==null) return Maybe.absent("value is null");
         input = input.trim();
-        Maybe<Date> result;
+        Maybe<Calendar> result;
 
-        result = parseDateUtc(input);
+        result = parseCalendarUtc(input);
         if (result.isPresent()) return result;
 
-        result = parseDateSimpleFlexibleFormatParser(input);
+        result = parseCalendarSimpleFlexibleFormatParser(input);
         if (result.isPresent()) return result;
         // return the error from this method
-        Maybe<Date> returnResult = result;
+        Maybe<Calendar> returnResult = result;
 
 //        // see natty method comments below
 //        Maybe<Date> result = parseDateNatty(input);
 //        if (result.isPresent()) return result;
 
-        result = parseDateFormat(input, new SimpleDateFormat(DATE_FORMAT_OF_DATE_TOSTRING));
+        result = parseCalendarFormat(input, new SimpleDateFormat(DATE_FORMAT_OF_DATE_TOSTRING));
         if (result.isPresent()) return result;
-        result = parseDateDefaultParse(input);
+        result = parseCalendarDefaultParse(input);
         if (result.isPresent()) return result;
 
         return returnResult;
     }
 
     @SuppressWarnings("deprecation")
-    private static Maybe<Date> parseDateDefaultParse(String input) {
+    private static Maybe<Calendar> parseCalendarDefaultParse(String input) {
         try {
             long ms = Date.parse(input);
             if (ms>=new Date(1999, 12, 25).getTime() && ms <= new Date(2200,
1, 2).getTime()) {
                 // accept default date parse for this century and next
-                return Maybe.of(new Date(ms));
+                GregorianCalendar c = new GregorianCalendar();
+                c.setTimeInMillis(ms);
+                return Maybe.of((Calendar)c);
             }
         } catch (Exception e) {
             Exceptions.propagateIfFatal(e);
@@ -521,12 +573,16 @@ public class Time {
         return Maybe.absent();
     }
 
-    private static Maybe<Date> parseDateUtc(String input) {
+    private static Maybe<Calendar> parseCalendarUtc(String input) {
+        input = input.trim();
         if (input.matches("\\d+")) {
-            Maybe<Date> result = Maybe.of(new Date(Long.parseLong(input)));
+            if ("0".equals(input)) {
+                // accept 0 as epoch UTC
+                return Maybe.of(newCalendarFromMillisSinceEpochUtc(0));
+            }
+            Maybe<Calendar> result = Maybe.of(newCalendarFromMillisSinceEpochUtc(Long.parseLong(input)));
             if (result.isPresent()) {
-                @SuppressWarnings("deprecation")
-                int year = result.get().getYear();
+                int year = result.get().get(Calendar.YEAR);
                 if (year >= 2000 && year < 2200) {
                     // only applicable for dates in this century
                     return result;
@@ -606,7 +662,7 @@ public class Time {
     }
     
     @SuppressWarnings("deprecation")
-    private static Maybe<Date> parseDateSimpleFlexibleFormatParser(String input) {
+    private static Maybe<Calendar> parseCalendarSimpleFlexibleFormatParser(String input)
{
         input = input.trim();
 
         String[] DATE_PATTERNS = new String[] {
@@ -757,14 +813,16 @@ public class Time {
                 }
             }
             
-            return Maybe.of(result.getTime());
+            return Maybe.of(result);
         }
         return Maybe.absent("Unknown date format '"+input+"'; try http://yaml.org/type/timestamp.html
format e.g. 2015-06-15 16:00:00 +0000");
     }
     
     public static TimeZone getTimeZone(String code) {
         if (code.indexOf('/')==-1) {
-            if ("Z".equals(code)) return getTimeZone("UTC");
+            if ("Z".equals(code)) return TIME_ZONE_UTC;
+            if ("UTC".equals(code)) return TIME_ZONE_UTC;
+            if ("GMT".equals(code)) return TIME_ZONE_UTC;
             
             // get the time zone -- most short codes aren't accepted, so accept (and prefer)
certain common codes
             if ("EST".equals(code)) return getTimeZone("America/New_York");
@@ -872,22 +930,22 @@ public class Time {
      * <p>
      * If no time zone supplied, this defaults to the TZ configured at the brooklyn server.
      * 
-     * @deprecated since 0.7.0 use {@link #parseDate(String)} for general or {@link #parseDateFormat(String,
DateFormat)} for a format,
-     * plus {@link #parseDateUtc(String)} if you want to accept UTC
+     * @deprecated since 0.7.0 use {@link #parseCalendar(String)} for general or {@link #parseCalendarFormat(String,
DateFormat)} for a format,
+     * plus {@link #parseCalendarUtc(String)} if you want to accept UTC
      */
     public static Date parseDateString(String dateString, DateFormat format) {
-        Maybe<Date> r = parseDateFormat(dateString, format);
-        if (r.isPresent()) return r.get();
+        Maybe<Calendar> r = parseCalendarFormat(dateString, format);
+        if (r.isPresent()) return r.get().getTime();
         
-        r = parseDateUtc(dateString);
-        if (r.isPresent()) return r.get();
+        r = parseCalendarUtc(dateString);
+        if (r.isPresent()) return r.get().getTime();
 
         throw new IllegalArgumentException("Date " + dateString + " cannot be parsed as UTC
millis or using format " + format);
     }
-    public static Maybe<Date> parseDateFormat(String dateString, String format) {
-        return parseDateFormat(dateString, new SimpleDateFormat(format));
+    public static Maybe<Calendar> parseCalendarFormat(String dateString, String format)
{
+        return parseCalendarFormat(dateString, new SimpleDateFormat(format));
     }
-    public static Maybe<Date> parseDateFormat(String dateString, DateFormat format)
{
+    public static Maybe<Calendar> parseCalendarFormat(String dateString, DateFormat
format) {
         if (dateString == null) { 
             throw new NumberFormatException("GeneralHelper.parseDateString cannot parse a
null string");
         }
@@ -898,7 +956,7 @@ public class Time {
         Date result = format.parse(dateString, p);
         if (result!=null) {
             // accept results even if the entire thing wasn't parsed, as enough was to match
the requested format
-            return Maybe.of(result);
+            return Maybe.of(newCalendarFromDate(result));
         }
         if (log.isTraceEnabled()) log.trace("Could not parse date "+dateString+" using format
"+format+": "+p);
         return Maybe.absent();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/51ba0aaf/utils/common/src/test/java/brooklyn/util/time/TimeTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/brooklyn/util/time/TimeTest.java b/utils/common/src/test/java/brooklyn/util/time/TimeTest.java
index 8ad9caa..5f16d5e 100644
--- a/utils/common/src/test/java/brooklyn/util/time/TimeTest.java
+++ b/utils/common/src/test/java/brooklyn/util/time/TimeTest.java
@@ -150,6 +150,17 @@ public class TimeTest {
         Assert.assertFalse(Time.hasElapsedSince(aFewSecondsAgo, Duration.TEN_SECONDS));
         Assert.assertTrue(Time.hasElapsedSince(-1, Duration.TEN_SECONDS));
     }
+    
+    @Test
+    public void testMakeDateString() {
+        String in1 = "2015-06-15T12:34:56";
+        Date d1 = Time.parseDate(in1);
+        Assert.assertEquals(Time.makeDateString(d1), in1.replace('T', ' ')+".000");
+        
+        String in2 = "2015-06-15T12:34:56Z";
+        Date d2 = Time.parseDate(in2);
+        Assert.assertEquals(Time.makeDateString(d2, Time.DATE_FORMAT_ISO8601, Time.getTimeZone("UTC")),
in1+".000+0000");
+    }
 
     @Test(groups="Integration")  //because it depends on TZ's set up and parsing months
     public void testTimeZones() {
@@ -238,19 +249,34 @@ public class TimeTest {
         assertDatesParseToEqual("20150604-080012.345", "2015-06-04-080012.345");
         assertDatesParseToEqual("2015-12-1", "2015-12-01-0000");
         assertDatesParseToEqual("1066-12-1", "1066-12-01-0000");
-        Assert.assertEquals(Time.parseDate("2012-2-29").getTime(), Time.parseDate("2012-3-1").getTime()
- 24*60*60*1000);
-        // perverse, but accepted for the time being:
-        Assert.assertEquals(Time.parseDate("2013-2-29").getTime(), Time.parseDate("2013-3-1").getTime());
         
         assertDatesParseToEqual("20150604T080012.345", "2015-06-04-080012.345");
         assertDatesParseToEqual("20150604T080012.345Z", "2015-06-04-080012.345+0000");
         assertDatesParseToEqual("20150604t080012.345 Z", "2015-06-04-080012.345+0000");
-        
+
+        // millis parse, and zero is epoch, but numbers which look like a date or datetime
take priority
+        assertDatesParseToEqual("0", "1970-1-1 UTC");
+        assertDatesParseToEqual("20150604", "2015-06-04");
+        assertDatesParseToEqual(""+Time.parseDate("20150604").getTime(), "2015-06-04");
+        assertDatesParseToEqual("20150604080012", "2015-06-04-080012");
+        assertDatesParseToEqual("0", "1970-1-1 UTC");
+
+        // leap year
+        Assert.assertEquals(Time.parseDate("2012-2-29").getTime(), Time.parseDate("2012-3-1").getTime()
- 24*60*60*1000);
+        // perverse, but accepted for the time being:
+        Assert.assertEquals(Time.parseDate("2013-2-29").getTime(), Time.parseDate("2013-3-1").getTime());
+
         // accept am and pm
         assertDatesParseToEqual("20150604 08:00:12.345a", "2015-06-04-080012.345");
         assertDatesParseToEqual("20150604 08:00:12.345 PM", "2015-06-04-200012.345");
         if (integration) assertDatesParseToEqual("20150604 08:00:12.345 am BST", "2015-06-04-080012.345
+0100");
         
+        // *calendar* parse includes time zone
+        Assert.assertEquals(Time.makeDateString(Time.parseCalendar("20150604 08:00:12.345a
+0100"),
+            Time.DATE_FORMAT_ISO8601), "2015-06-04T08:00:12.345+0100");
+        Assert.assertEquals(Time.makeDateString(Time.parseCalendar("20150604 08:00:12.345a
"+Time.TIME_ZONE_UTC.getID()),
+            Time.DATE_FORMAT_ISO8601), "2015-06-04T08:00:12.345+0000");
+        
         // accept month in words
         if (integration) {
             assertDatesParseToEqual("2015-Dec-1", "2015-12-01-0000");



Mime
View raw message