From 9802c0827085fd95ef1e973def0bb1c99a22eb1c Mon Sep 17 00:00:00 2001 From: Daniel Collin Date: Mon, 14 Dec 2015 17:10:14 +0100 Subject: [PATCH] refactor TimeUtility class to make it testable + added a test class for TimeUtility Former-commit-id: 54d9cf2bf8da37bb723dd5defebbfd3057fef214 --- .../koc/hal/deamon/DataAggregatorDaemon.java | 48 +++--- src/se/koc/hal/deamon/DataDeletionDaemon.java | 114 ++++++++++---- src/se/koc/hal/deamon/TimeUtility.java | 141 +++++++++++------- test/se/koc/hal/deamon/TimeUtilityTest.java | 40 ++++- 4 files changed, 228 insertions(+), 115 deletions(-) diff --git a/src/se/koc/hal/deamon/DataAggregatorDaemon.java b/src/se/koc/hal/deamon/DataAggregatorDaemon.java index 2ac6dbf1..699f8769 100755 --- a/src/se/koc/hal/deamon/DataAggregatorDaemon.java +++ b/src/se/koc/hal/deamon/DataAggregatorDaemon.java @@ -67,20 +67,24 @@ public class DataAggregatorDaemon extends TimerTask implements HalDaemon { PreparedStatement stmt = null; try { - stmt = db.getPreparedStatement("SELECT MAX(timestamp_end) FROM sensor_data_aggr WHERE sensor_id == ?"); + stmt = db.getPreparedStatement("SELECT MAX(timestamp_end) FROM sensor_data_aggr" + + " WHERE sensor_id == ?" + + " AND timestamp_end-timestamp_start == ?"); stmt.setLong(1, sensorId); - Long maxDBTimestamp = DBConnection.exec(stmt, new SimpleSQLResult()); - if(maxDBTimestamp == null) - maxDBTimestamp = 0l; - - long minPeriodTimestamp = TimeUtility.getTimestampPeriodStart(toPeriodSizeInMs, System.currentTimeMillis()); - logger.fine("Calculating periods... (from:"+ maxDBTimestamp +", to:"+ minPeriodTimestamp +")"); + stmt.setLong(2, toPeriodSizeInMs-1); + Long maxTimestampFoundForSensor = DBConnection.exec(stmt, new SimpleSQLResult()); + if(maxTimestampFoundForSensor == null) + maxTimestampFoundForSensor = 0l; + long currentPeriodStartTimestamp = TimeUtility.getTimestampPeriodStart(toPeriodSizeInMs, System.currentTimeMillis()); + logger.fine("Calculating periods... (from:"+ maxTimestampFoundForSensor +", to:"+ currentPeriodStartTimestamp +")"); stmt = db.getPreparedStatement("SELECT *, 1 AS confidence, timestamp AS timestamp_start FROM sensor_data_raw" - +" WHERE sensor_id == ? AND ? < timestamp AND timestamp < ? " + +" WHERE sensor_id == ?" + + " AND ? < timestamp" + + " AND timestamp < ? " +" ORDER BY timestamp ASC"); stmt.setLong(1, sensorId); - stmt.setLong(2, maxDBTimestamp); - stmt.setLong(3, minPeriodTimestamp); + stmt.setLong(2, maxTimestampFoundForSensor); + stmt.setLong(3, currentPeriodStartTimestamp); DBConnection.exec(stmt, new DataAggregator(sensorId, toPeriodSizeInMs, expectedSampleCount, aggrMethod)); } catch (SQLException e) { logger.log(Level.SEVERE, null, e); @@ -99,21 +103,25 @@ public class DataAggregatorDaemon extends TimerTask implements HalDaemon { try { stmt = db.getPreparedStatement("SELECT MAX(timestamp_end) FROM sensor_data_aggr" - +" WHERE sensor_id == ? AND timestamp_end-timestamp_start == ?"); + +" WHERE sensor_id == ?" + + " AND timestamp_end-timestamp_start == ?"); stmt.setLong(1, sensorId); stmt.setLong(2, toPeriodSizeInMs-1); - Long maxDBTimestamp = DBConnection.exec(stmt, new SimpleSQLResult()); - if(maxDBTimestamp == null) - maxDBTimestamp = 0l; - long hourPeriodTimestamp = TimeUtility.getTimestampPeriodStart(toPeriodSizeInMs, System.currentTimeMillis()); - logger.fine("Calculating periods... (from:"+ maxDBTimestamp +", to:"+ hourPeriodTimestamp +")"); + Long maxTimestampFoundForSensor = DBConnection.exec(stmt, new SimpleSQLResult()); + if(maxTimestampFoundForSensor == null) + maxTimestampFoundForSensor = 0l; + long currentPeriodStartTimestamp = TimeUtility.getTimestampPeriodStart(toPeriodSizeInMs, System.currentTimeMillis()); + logger.fine("Calculating periods... (from:"+ maxTimestampFoundForSensor +", to:"+ currentPeriodStartTimestamp +")"); stmt = db.getPreparedStatement("SELECT * FROM sensor_data_aggr" - +" WHERE sensor_id == ? AND ? < timestamp_start AND timestamp_start < ? AND timestamp_end-timestamp_start == ?" + +" WHERE sensor_id == ?" + + " AND ? < timestamp_start" + + " AND timestamp_start < ?" + + " AND timestamp_end-timestamp_start == ?" +" ORDER BY timestamp_start ASC"); stmt.setLong(1, sensorId); - stmt.setLong(2, maxDBTimestamp); - stmt.setLong(3, hourPeriodTimestamp); + stmt.setLong(2, maxTimestampFoundForSensor); + stmt.setLong(3, currentPeriodStartTimestamp); stmt.setLong(4, fromPeriodSizeInMs-1); DBConnection.exec(stmt, new DataAggregator(sensorId, toPeriodSizeInMs, expectedSampleCount, aggrMethod)); } catch (SQLException e) { @@ -166,7 +174,7 @@ public class DataAggregatorDaemon extends TimerTask implements HalDaemon { preparedInsertStmt.setLong(2, ++highestSequenceId); preparedInsertStmt.setLong(3, currentPeriodTimestamp); preparedInsertStmt.setLong(4, currentPeriodTimestamp + this.aggrTimeInMs - 1); - preparedInsertStmt.setInt(5, (int)data); //TODO: make data float in DB + preparedInsertStmt.setInt(5, (int)data); //TODO: make data float in DB to handle aggrMethod.AVG where the data must be able to be saved as a float preparedInsertStmt.setFloat(6, aggrConfidence); preparedInsertStmt.addBatch(); diff --git a/src/se/koc/hal/deamon/DataDeletionDaemon.java b/src/se/koc/hal/deamon/DataDeletionDaemon.java index 3ba377d2..a277a1cc 100755 --- a/src/se/koc/hal/deamon/DataDeletionDaemon.java +++ b/src/se/koc/hal/deamon/DataDeletionDaemon.java @@ -30,7 +30,7 @@ public class DataDeletionDaemon extends TimerTask implements HalDaemon { List sensorList = Sensor.getSensors(HalContext.getDB()); for(Sensor sensor : sensorList){ logger.fine("Deleting old data for sensor id: " + sensor.getId()); - cleanupSensor(sensor.getId()); + cleanupSensor(sensor); } logger.fine("Data cleanup done"); } catch (SQLException e) { @@ -38,7 +38,30 @@ public class DataDeletionDaemon extends TimerTask implements HalDaemon { } } - public void cleanupSensor(long sensorId) { + public void cleanupSensor(Sensor sensor) { + logger.fine("The sensor is of type: " + sensor.getType()); + if(sensor.getType().equals("PowerMeter")){ + //if(sensor.isInternal()){ //TODO + cleanupInternalSensorData(sensor.getId(), TimeUtility.HOUR_IN_MS, TimeUtility.FIVE_MINUTES_IN_MS, TimeUtility.DAY_IN_MS); + cleanupInternalSensorData(sensor.getId(), TimeUtility.DAY_IN_MS, TimeUtility.HOUR_IN_MS, TimeUtility.WEEK_IN_MS); + //}else{ //TODO + //cleanupExternalSensorData(sensor.getId(), TimeUtility.FIVE_MINUTES_IN_MS, TimeUtility.DAY_IN_MS); + //cleanupExternalSensorData(sensor.getId(), TimeUtility.DAY_IN_MS, TimeUtility.WEEK_IN_MS); + //} + clearPeriodsOfWrongLenght(sensor.getId(), TimeUtility.FIVE_MINUTES_IN_MS, TimeUtility.HOUR_IN_MS, TimeUtility.WEEK_IN_MS); + }else{ + logger.fine("The sensor type is not supported by the cleanup deamon. Ignoring"); + } + } + + /** + * Will clear periods only if it has been aggregated and are too old. + * @param sensorId + * @Param referencePeriodlength Will only clear periods older than the newest period of this length. + * @Param clearPeriodlength Will clear periods with this length + * @param olderThan Data must be older than this many ms to be cleared from the DB + */ + private void cleanupInternalSensorData(long sensorId, long referencePeriodlength, long clearPeriodlength, long olderThan){ DBConnection db = HalContext.getDB(); PreparedStatement stmt = null; try { @@ -48,7 +71,7 @@ public class DataDeletionDaemon extends TimerTask implements HalDaemon { stmt = db.getPreparedStatement("SELECT MAX(timestamp_end) FROM sensor_data_aggr" +" WHERE sensor_id == ? AND timestamp_end-timestamp_start == ?"); stmt.setLong(1, sensorId); - stmt.setLong(2, TimeUtility.HOUR_IN_MS-1); + stmt.setLong(2, referencePeriodlength-1); maxDBTimestamp = DBConnection.exec(stmt, new SimpleSQLResult()); if(maxDBTimestamp == null) maxDBTimestamp = 0l; @@ -60,35 +83,65 @@ public class DataDeletionDaemon extends TimerTask implements HalDaemon { + "AND timestamp_end < ?"); stmt.setLong(1, sensorId); stmt.setLong(2, maxDBTimestamp); - stmt.setLong(3, TimeUtility.FIVE_MINUTES_IN_MS-1); - stmt.setLong(4, System.currentTimeMillis()-TimeUtility.DAY_IN_MS); - DBConnection.exec(stmt, new AggregateDataDeleter(sensorId)); - - // delete too old 1 hour periods that already have been aggregated into days - stmt = db.getPreparedStatement("SELECT MAX(timestamp_end) FROM sensor_data_aggr" - +" WHERE sensor_id == ? AND timestamp_end-timestamp_start == ?"); - stmt.setLong(1, sensorId); - stmt.setLong(2, TimeUtility.DAY_IN_MS-1); - maxDBTimestamp = DBConnection.exec(stmt, new SimpleSQLResult()); - if(maxDBTimestamp == null) - maxDBTimestamp = 0l; - - stmt = db.getPreparedStatement("SELECT * FROM sensor_data_aggr" - +" WHERE sensor_id == ? " - + "AND timestamp_end < ? " - + "AND timestamp_end-timestamp_start == ?" - + "AND timestamp_end < ?"); - stmt.setLong(1, sensorId); - stmt.setLong(2, maxDBTimestamp); - stmt.setLong(3, TimeUtility.HOUR_IN_MS-1); - stmt.setLong(4, System.currentTimeMillis()-TimeUtility.WEEK_IN_MS); + stmt.setLong(3, clearPeriodlength-1); + stmt.setLong(4, System.currentTimeMillis()-olderThan); DBConnection.exec(stmt, new AggregateDataDeleter(sensorId)); } catch (SQLException e) { logger.log(Level.SEVERE, null, e); } } - private class AggregateDataDeleter implements SQLResultHandler{ + /** + * Will clear periods if they are too old. + * @param sensorId + * @Param clearPeriodlength Will clear periods with this length + * @param olderThan Data must be older than this many ms to be cleared from the DB + */ + private void cleanupExternalSensorData(long sensorId, long clearPeriodlength, long olderThan){ + DBConnection db = HalContext.getDB(); + PreparedStatement stmt = null; + try { + + stmt = db.getPreparedStatement("SELECT * FROM sensor_data_aggr" + +" WHERE sensor_id == ? " + + "AND timestamp_end-timestamp_start == ?" + + "AND timestamp_end < ?"); + stmt.setLong(1, sensorId); + stmt.setLong(2, clearPeriodlength-1); + stmt.setLong(3, System.currentTimeMillis()-olderThan); + DBConnection.exec(stmt, new AggregateDataDeleter(sensorId)); + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + } + + /** + * Will delete all aggregated entries for a sensor id where the period length is not expected + * @param sensorId + * @param expectedPeriodLengths + */ + private void clearPeriodsOfWrongLenght(long sensorId, long... expectedPeriodLengths){ + DBConnection db = HalContext.getDB(); + PreparedStatement stmt = null; + try { + StringBuilder querry = new StringBuilder("SELECT * FROM sensor_data_aggr WHERE sensor_id == ?"); + for(int i = 0; i < expectedPeriodLengths.length; ++i){ + querry.append(" AND timestamp_end-timestamp_start != ?"); + } + stmt = db.getPreparedStatement(querry.toString()); + for(int i = 0; i < expectedPeriodLengths.length; ++i){ + stmt.setLong(i+1, expectedPeriodLengths[i]); + } + long deletedRows = DBConnection.exec(stmt, new AggregateDataDeleter(sensorId)); + if(deletedRows > 0){ + logger.severe("removed aggregated data with an unknown period length. Is the database corrupt?"); + } + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + } + + private class AggregateDataDeleter implements SQLResultHandler{ private long sensorId = -1; public AggregateDataDeleter(long sensorId){ @@ -96,7 +149,8 @@ public class DataDeletionDaemon extends TimerTask implements HalDaemon { } @Override - public Object handleQueryResult(Statement stmt, ResultSet result) throws SQLException { + public Long handleQueryResult(Statement stmt, ResultSet result) throws SQLException { + long count = 0; try{ HalContext.getDB().getConnection().setAutoCommit(false); PreparedStatement preparedDeleteStmt = HalContext.getDB().getPreparedStatement("DELETE FROM sensor_data_aggr WHERE sensor_id == ? AND sequence_id == ?"); @@ -104,20 +158,22 @@ public class DataDeletionDaemon extends TimerTask implements HalDaemon { if(sensorId != result.getInt("sensor_id")){ throw new IllegalArgumentException("Found entry for aggregation for the wrong sensorId (expecting: "+sensorId+", but was: "+result.getInt("sensor_id")+")"); } - logger.finer("Deleting sensor aggregate entry timestamp: "+ result.getLong("timestamp_start") +" - "+ result.getLong("timestamp_end")); + logger.finer("Deleting sensor aggregate entry timestamp: "+ result.getLong("timestamp_start") +" - "+ result.getLong("timestamp_end") + " (" + TimeUtility.msToString(result.getLong("timestamp_end")-result.getLong("timestamp_start")) + ")"); preparedDeleteStmt.setInt(1, result.getInt("sensor_id")); preparedDeleteStmt.setLong(2, result.getLong("sequence_id")); preparedDeleteStmt.addBatch(); + count++; } DBConnection.execBatch(preparedDeleteStmt); HalContext.getDB().getConnection().commit(); }catch(Exception e){ HalContext.getDB().getConnection().rollback(); + throw e; }finally{ HalContext.getDB().getConnection().setAutoCommit(true); } - return null; + return count; } } diff --git a/src/se/koc/hal/deamon/TimeUtility.java b/src/se/koc/hal/deamon/TimeUtility.java index a1c4f50f..862debb1 100644 --- a/src/se/koc/hal/deamon/TimeUtility.java +++ b/src/se/koc/hal/deamon/TimeUtility.java @@ -10,68 +10,93 @@ public class TimeUtility { public static final long DAY_IN_MS = HOUR_IN_MS * 24; public static final long WEEK_IN_MS = DAY_IN_MS * 7; + /** + * Get the timstamp for the given timestamp floored with the period length. The result should point to the beginning of the timestamps period. + * @param periodLengthInMs The periods length to floor the timestamp with + * @param timestamp The timestamp to floor. + * @return + */ public static long getTimestampPeriodStart(long periodLengthInMs, long timestamp){ - if(periodLengthInMs < DAY_IN_MS){ - long tmp = timestamp % periodLengthInMs; - return timestamp - tmp; + if(periodLengthInMs < DAY_IN_MS){ //simple math if the period is less than a day long + return timestamp - (timestamp % periodLengthInMs); }else{ - long tmp = periodLengthInMs; - int milliseconds = (int) (tmp % SECOND_IN_MS); - tmp -= milliseconds * SECOND_IN_MS; - int seconds = (int) ((tmp % MINUTES_IN_MS) / SECOND_IN_MS); - tmp -= seconds * MINUTES_IN_MS; - int minutes = (int) ((tmp % HOUR_IN_MS) / MINUTES_IN_MS); - tmp -= minutes * HOUR_IN_MS; - int hours = (int) ((tmp % DAY_IN_MS) / HOUR_IN_MS); - tmp -= hours * DAY_IN_MS; - int days = (int) (tmp / DAY_IN_MS); - return getTimestampPeriodStart(days, hours, minutes, seconds, milliseconds, timestamp); + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(timestamp); + boolean clear = false; + int days = millisecondsToDays(periodLengthInMs); + if(days > 0){ + int currentDay = cal.get(Calendar.DAY_OF_YEAR); + cal.set(Calendar.DAY_OF_YEAR, (currentDay/days)*days); + clear = true; + } + int hours = millisecondsToHourOfDay(periodLengthInMs); + if(hours > 0){ + int currentHour = cal.get(Calendar.HOUR_OF_DAY); + cal.set(Calendar.HOUR_OF_DAY, (currentHour/hours)*hours); + clear = true; + }else if(clear){ + cal.set(Calendar.HOUR_OF_DAY, 0); + } + int minutes = millisecondsToMinuteOfHour(periodLengthInMs); + if(minutes > 0){ + int currentMinute = cal.get(Calendar.MINUTE); + cal.set(Calendar.MINUTE, (currentMinute/minutes)*minutes); + clear = true; + }else if(clear){ + cal.set(Calendar.MINUTE, 0); + } + int seconds = millisecondsToSecondOfMinute(periodLengthInMs); + if(seconds > 0){ + int currentSecond = cal.get(Calendar.SECOND); + cal.set(Calendar.SECOND, (currentSecond/seconds)*seconds); + clear = true; + }else if(clear){ + cal.set(Calendar.SECOND, 0); + } + int milliseconds = millisecondsToMillisecondInSecond(periodLengthInMs); + if(milliseconds > 0){ + int currentMillisecond = cal.get(Calendar.MILLISECOND); + cal.set(Calendar.MILLISECOND, (currentMillisecond/milliseconds)*milliseconds); + }else if(clear){ + cal.set(Calendar.MILLISECOND, 0); + } + return cal.getTimeInMillis(); } } - - private static long getTimestampPeriodStart(int days, int hours, int minutes, int seconds, int milliseconds, long timestamp) { - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(timestamp); - boolean clear = false; - - if(days > 0){ - int currentDay = cal.get(Calendar.DAY_OF_YEAR); - cal.set(Calendar.DAY_OF_YEAR, (currentDay/days)*days); - clear = true; - } - - if(hours > 0){ - int currentHour = cal.get(Calendar.HOUR_OF_DAY); - cal.set(Calendar.HOUR_OF_DAY, (currentHour/hours)*hours); - clear = true; - }else if(clear){ - cal.set(Calendar.HOUR_OF_DAY, 0); - } - - if(minutes > 0){ - int currentMinute = cal.get(Calendar.MINUTE); - cal.set(Calendar.MINUTE, (currentMinute/minutes)*minutes); - clear = true; - }else if(clear){ - cal.set(Calendar.MINUTE, 0); - } - - if(seconds > 0){ - int currentSecond = cal.get(Calendar.SECOND); - cal.set(Calendar.SECOND, (currentSecond/seconds)*seconds); - clear = true; - }else if(clear){ - cal.set(Calendar.SECOND, 0); - } - - if(milliseconds > 0){ - int currentMillisecond = cal.get(Calendar.MILLISECOND); - cal.set(Calendar.MILLISECOND, (currentMillisecond/milliseconds)*milliseconds); - }else if(clear){ - cal.set(Calendar.MILLISECOND, 0); - } - - return cal.getTimeInMillis(); + + public static int millisecondsToMillisecondInSecond(long ms){ + return (int) (ms % SECOND_IN_MS); + } + + public static int millisecondsToSecondOfMinute(long ms){ + return (int) ((ms % MINUTES_IN_MS) / SECOND_IN_MS); + } + + public static int millisecondsToMinuteOfHour(long ms){ + return (int) ((ms % HOUR_IN_MS) / MINUTES_IN_MS); + } + + public static int millisecondsToHourOfDay(long ms){ + return (int) ((ms % DAY_IN_MS) / HOUR_IN_MS); + } + + public static int millisecondsToDays(long ms){ + return (int) (ms / DAY_IN_MS); + } + + public static String msToString(long ms){ + String retval = ""; + int days = millisecondsToDays(ms); + retval += days + "days+"; + int hours = millisecondsToHourOfDay(ms); + retval += (hours<10?"0"+hours:hours); + int minutes = millisecondsToMinuteOfHour(ms); + retval += ":" + (minutes<10?"0"+minutes:minutes); + int seconds = millisecondsToSecondOfMinute(ms); + retval += ":" + (seconds<10?"0"+seconds:seconds); + int milliseconds = millisecondsToMillisecondInSecond(ms); + retval += "." + (milliseconds<100?"0"+(milliseconds<10?"0"+milliseconds:milliseconds):milliseconds); + return retval; } } diff --git a/test/se/koc/hal/deamon/TimeUtilityTest.java b/test/se/koc/hal/deamon/TimeUtilityTest.java index 56ecc3c5..f686dd00 100644 --- a/test/se/koc/hal/deamon/TimeUtilityTest.java +++ b/test/se/koc/hal/deamon/TimeUtilityTest.java @@ -20,9 +20,9 @@ public class TimeUtilityTest { @Test public void testDayStartForCurrentTime(){ - long thisDayStartedAt = TimeUtility.getTimestampPeriodStart(TimeUtility.DAY_IN_MS, currentTime); + long thisPeriodStartedAt = TimeUtility.getTimestampPeriodStart(TimeUtility.DAY_IN_MS, currentTime); Calendar testCalendar = Calendar.getInstance(); - testCalendar.setTimeInMillis(thisDayStartedAt); + testCalendar.setTimeInMillis(thisPeriodStartedAt); assertEquals("millisecond is wrong", 0, testCalendar.get(Calendar.MILLISECOND)); assertEquals("second is wrong", 0, testCalendar.get(Calendar.SECOND)); @@ -33,9 +33,9 @@ public class TimeUtilityTest { @Test public void testHourStartForCurrentTime(){ - long thisDayStartedAt = TimeUtility.getTimestampPeriodStart(TimeUtility.HOUR_IN_MS, currentTime); + long thisPeriodStartedAt = TimeUtility.getTimestampPeriodStart(TimeUtility.HOUR_IN_MS, currentTime); Calendar testCalendar = Calendar.getInstance(); - testCalendar.setTimeInMillis(thisDayStartedAt); + testCalendar.setTimeInMillis(thisPeriodStartedAt); assertEquals("millisecond is wrong", 0, testCalendar.get(Calendar.MILLISECOND)); assertEquals("second is wrong", 0, testCalendar.get(Calendar.SECOND)); @@ -46,9 +46,9 @@ public class TimeUtilityTest { @Test public void testMinuteStartForCurrentTime(){ - long thisDayStartedAt = TimeUtility.getTimestampPeriodStart(TimeUtility.MINUTES_IN_MS, currentTime); + long thisPeriodStartedAt = TimeUtility.getTimestampPeriodStart(TimeUtility.MINUTES_IN_MS, currentTime); Calendar testCalendar = Calendar.getInstance(); - testCalendar.setTimeInMillis(thisDayStartedAt); + testCalendar.setTimeInMillis(thisPeriodStartedAt); assertEquals("millisecond is wrong", 0, testCalendar.get(Calendar.MILLISECOND)); assertEquals("second is wrong", 0, testCalendar.get(Calendar.SECOND)); @@ -59,9 +59,9 @@ public class TimeUtilityTest { @Test public void testSecondStartForCurrentTime(){ - long thisDayStartedAt = TimeUtility.getTimestampPeriodStart(TimeUtility.SECOND_IN_MS, currentTime); + long thisPeriodStartedAt = TimeUtility.getTimestampPeriodStart(TimeUtility.SECOND_IN_MS, currentTime); Calendar testCalendar = Calendar.getInstance(); - testCalendar.setTimeInMillis(thisDayStartedAt); + testCalendar.setTimeInMillis(thisPeriodStartedAt); assertEquals("millisecond is wrong", 0, testCalendar.get(Calendar.MILLISECOND)); assertEquals("second is wrong", referenceCalendar.get(Calendar.SECOND), testCalendar.get(Calendar.SECOND)); @@ -70,4 +70,28 @@ public class TimeUtilityTest { assertEquals("day is wrong", referenceCalendar.get(Calendar.DAY_OF_YEAR), testCalendar.get(Calendar.DAY_OF_YEAR)); } + @Test + public void testMsToString(){ + //low values + assertEquals("0days+00:00:00.000", TimeUtility.msToString(0)); + assertEquals("0days+00:00:00.001", TimeUtility.msToString(1)); + assertEquals("0days+00:00:01.000", TimeUtility.msToString(TimeUtility.SECOND_IN_MS)); + assertEquals("0days+00:01:00.000", TimeUtility.msToString(TimeUtility.MINUTES_IN_MS)); + assertEquals("0days+00:05:00.000", TimeUtility.msToString(TimeUtility.FIVE_MINUTES_IN_MS)); + assertEquals("0days+01:00:00.000", TimeUtility.msToString(TimeUtility.HOUR_IN_MS)); + assertEquals("1days+00:00:00.000", TimeUtility.msToString(TimeUtility.DAY_IN_MS)); + assertEquals("7days+00:00:00.000", TimeUtility.msToString(TimeUtility.WEEK_IN_MS)); + + //high values + assertEquals("0days+00:00:00.999", TimeUtility.msToString(999)); + assertEquals("0days+00:00:59.000", TimeUtility.msToString(TimeUtility.SECOND_IN_MS*59)); + assertEquals("0days+00:59:00.000", TimeUtility.msToString(TimeUtility.MINUTES_IN_MS*59)); + assertEquals("0days+23:00:00.000", TimeUtility.msToString(TimeUtility.HOUR_IN_MS*23)); + assertEquals("369days+00:00:00.000", TimeUtility.msToString(TimeUtility.DAY_IN_MS*369)); + + //combinations + long ms = (TimeUtility.DAY_IN_MS*999) + (TimeUtility.HOUR_IN_MS*23) + (TimeUtility.MINUTES_IN_MS*59) + (TimeUtility.SECOND_IN_MS*59) + 999; + assertEquals("999days+23:59:59.999", TimeUtility.msToString(ms)); + } + }