Using UTC instead of LOCAL time.

Former-commit-id: 21831d97d7f616313e0e90bf8ae69e04dea57685
This commit is contained in:
Daniel Collin 2015-12-18 10:00:27 +01:00
parent 4d9589dc9a
commit 5036ec90e9
6 changed files with 461 additions and 382 deletions

View file

@ -63,8 +63,9 @@ public class HalContext {
referenceDB.exec("SELECT * FROM conf", new PropertiesSQLResult()); referenceDB.exec("SELECT * FROM conf", new PropertiesSQLResult());
// Check DB version // Check DB version
logger.fine("DB version: "+ dbConf.getProperty(PROPERTY_DB_VERSION)); logger.fine("DB version: "+ dbConf.getProperty(PROPERTY_DB_VERSION));
if(dbConf.getProperty(PROPERTY_DB_VERSION) == null || int defaultDBVersion = Integer.parseInt(defaultDBConf.getProperty(PROPERTY_DB_VERSION));
defaultDBConf.getProperty(PROPERTY_DB_VERSION).compareTo(dbConf.getProperty(PROPERTY_DB_VERSION)) > 0) { int dbVersion = Integer.parseInt(dbConf.getProperty(PROPERTY_DB_VERSION));
if(dbConf.getProperty(PROPERTY_DB_VERSION) == null || defaultDBVersion > dbVersion ) {
logger.info("Starting DB upgrade..."); logger.info("Starting DB upgrade...");
File backupDB = FileUtil.getNextFile(dbFile); File backupDB = FileUtil.getNextFile(dbFile);
logger.fine("Backing up DB to: "+ backupDB); logger.fine("Backing up DB to: "+ backupDB);

View file

@ -45,11 +45,11 @@ public class DataAggregatorDaemon extends TimerTask implements HalDaemon {
logger.fine("The sensor is of type: " + sensor.getType()); logger.fine("The sensor is of type: " + sensor.getType());
if(sensor.getType().equals("PowerMeter")){ if(sensor.getType().equals("PowerMeter")){
logger.fine("aggregating raw data to five minute periods"); logger.fine("aggregating raw data to five minute periods");
aggregateRawData(sensor.getId(), TimeUtility.FIVE_MINUTES_IN_MS, 5, sensor.getAggregationMethod()); aggregateRawData(sensor, TimeUtility.FIVE_MINUTES_IN_MS, 5);
logger.fine("aggregating five minute periods into hour periods"); logger.fine("aggregating five minute periods into hour periods");
aggrigateAggregatedData(sensor.getId(), TimeUtility.FIVE_MINUTES_IN_MS, TimeUtility.HOUR_IN_MS, 12, sensor.getAggregationMethod()); aggrigateAggregatedData(sensor, TimeUtility.FIVE_MINUTES_IN_MS, TimeUtility.HOUR_IN_MS);
logger.fine("aggregating one hour periods into one day periods"); logger.fine("aggregating one hour periods into one day periods");
aggrigateAggregatedData(sensor.getId(), TimeUtility.HOUR_IN_MS, TimeUtility.DAY_IN_MS, 24, sensor.getAggregationMethod()); aggrigateAggregatedData(sensor, TimeUtility.HOUR_IN_MS, TimeUtility.DAY_IN_MS);
}else{ }else{
logger.fine("The sensor type is not supported by the aggregation daemon. Ignoring"); logger.fine("The sensor type is not supported by the aggregation daemon. Ignoring");
} }
@ -60,7 +60,9 @@ public class DataAggregatorDaemon extends TimerTask implements HalDaemon {
* @param sensorId The sensor for to aggregate data * @param sensorId The sensor for to aggregate data
* @param toPeriodSizeInMs The period length in ms to aggregate to * @param toPeriodSizeInMs The period length in ms to aggregate to
*/ */
private void aggregateRawData(long sensorId, long toPeriodSizeInMs, int expectedSampleCount, AggregationMethod aggrMethod){ private void aggregateRawData(HalSensor sensor, long toPeriodSizeInMs, int expectedSampleCount){
long sensorId = sensor.getId();
AggregationMethod aggrMethod = sensor.getAggregationMethod();
DBConnection db = HalContext.getDB(); DBConnection db = HalContext.getDB();
PreparedStatement stmt = null; PreparedStatement stmt = null;
try { try {
@ -73,8 +75,8 @@ public class DataAggregatorDaemon extends TimerTask implements HalDaemon {
Long maxTimestampFoundForSensor = DBConnection.exec(stmt, new SimpleSQLResult<Long>()); Long maxTimestampFoundForSensor = DBConnection.exec(stmt, new SimpleSQLResult<Long>());
if(maxTimestampFoundForSensor == null) if(maxTimestampFoundForSensor == null)
maxTimestampFoundForSensor = 0l; maxTimestampFoundForSensor = 0l;
long currentPeriodStartTimestamp = TimeUtility.getTimestampPeriodStart(toPeriodSizeInMs, System.currentTimeMillis()); long currentPeriodStartTimestamp = TimeUtility.getTimestampPeriodStart_UTC(toPeriodSizeInMs, System.currentTimeMillis());
logger.fine("Calculating periods... (from:"+ maxTimestampFoundForSensor +", to:"+ currentPeriodStartTimestamp +")"); logger.fine("Calculating periods... (from:"+ maxTimestampFoundForSensor +", to:"+ currentPeriodStartTimestamp +") with expected sample count: " + expectedSampleCount);
stmt = db.getPreparedStatement("SELECT *, 1 AS confidence, timestamp AS timestamp_start FROM sensor_data_raw" stmt = db.getPreparedStatement("SELECT *, 1 AS confidence, timestamp AS timestamp_start FROM sensor_data_raw"
+" WHERE sensor_id == ?" +" WHERE sensor_id == ?"
+ " AND ? < timestamp" + " AND ? < timestamp"
@ -95,7 +97,10 @@ public class DataAggregatorDaemon extends TimerTask implements HalDaemon {
* @param fromPeriodSizeInMs The period length in ms to aggregate from * @param fromPeriodSizeInMs The period length in ms to aggregate from
* @param toPeriodSizeInMs The period length in ms to aggregate to * @param toPeriodSizeInMs The period length in ms to aggregate to
*/ */
private void aggrigateAggregatedData(long sensorId, long fromPeriodSizeInMs, long toPeriodSizeInMs, int expectedSampleCount, AggregationMethod aggrMethod){ private void aggrigateAggregatedData(HalSensor sensor, long fromPeriodSizeInMs, long toPeriodSizeInMs){
long sensorId = sensor.getId();
AggregationMethod aggrMethod = sensor.getAggregationMethod();
int expectedSampleCount = (int)Math.ceil((double)toPeriodSizeInMs / (double)fromPeriodSizeInMs);
DBConnection db = HalContext.getDB(); DBConnection db = HalContext.getDB();
PreparedStatement stmt = null; PreparedStatement stmt = null;
try { try {
@ -108,8 +113,8 @@ public class DataAggregatorDaemon extends TimerTask implements HalDaemon {
Long maxTimestampFoundForSensor = DBConnection.exec(stmt, new SimpleSQLResult<Long>()); Long maxTimestampFoundForSensor = DBConnection.exec(stmt, new SimpleSQLResult<Long>());
if(maxTimestampFoundForSensor == null) if(maxTimestampFoundForSensor == null)
maxTimestampFoundForSensor = 0l; maxTimestampFoundForSensor = 0l;
long currentPeriodStartTimestamp = TimeUtility.getTimestampPeriodStart(toPeriodSizeInMs, System.currentTimeMillis()); long currentPeriodStartTimestamp = TimeUtility.getTimestampPeriodStart_UTC(toPeriodSizeInMs, System.currentTimeMillis());
logger.fine("Calculating periods... (from:"+ maxTimestampFoundForSensor +", to:"+ currentPeriodStartTimestamp +")"); logger.fine("Calculating periods... (from:"+ maxTimestampFoundForSensor +", to:"+ currentPeriodStartTimestamp +") with expected sample count: " + expectedSampleCount);
stmt = db.getPreparedStatement("SELECT * FROM sensor_data_aggr" stmt = db.getPreparedStatement("SELECT * FROM sensor_data_aggr"
+" WHERE sensor_id == ?" +" WHERE sensor_id == ?"
@ -160,7 +165,7 @@ public class DataAggregatorDaemon extends TimerTask implements HalDaemon {
throw new IllegalArgumentException("found entry for aggregation for the wrong sensorId (expecting: "+sensorId+", but was: "+result.getInt("sensor_id")+")"); throw new IllegalArgumentException("found entry for aggregation for the wrong sensorId (expecting: "+sensorId+", but was: "+result.getInt("sensor_id")+")");
} }
long timestamp = result.getLong("timestamp_start"); long timestamp = result.getLong("timestamp_start");
long periodTimestamp = TimeUtility.getTimestampPeriodStart(this.aggrTimeInMs, timestamp); long periodTimestamp = TimeUtility.getTimestampPeriodStart_UTC(this.aggrTimeInMs, timestamp);
if(currentPeriodTimestamp != 0 && periodTimestamp != currentPeriodTimestamp){ if(currentPeriodTimestamp != 0 && periodTimestamp != currentPeriodTimestamp){
float aggrConfidence = confidenceSum / (float)this.expectedSampleCount; float aggrConfidence = confidenceSum / (float)this.expectedSampleCount;
float data = -1; float data = -1;

View file

@ -42,15 +42,14 @@ public class DataDeletionDaemon extends TimerTask implements HalDaemon {
public void cleanupSensor(HalSensor sensor) { public void cleanupSensor(HalSensor sensor) {
logger.fine("The sensor is of type: " + sensor.getType()); logger.fine("The sensor is of type: " + sensor.getType());
if(sensor.getType().equals("PowerMeter")){ if(sensor.getType().equals("PowerMeter")){ //TODO: use instanceof instead
//if(sensor.isInternal()){ //TODO if(sensor.getUser().isExternal()){
cleanupExternalSensorData(sensor.getId(), TimeUtility.FIVE_MINUTES_IN_MS, TimeUtility.DAY_IN_MS);
cleanupExternalSensorData(sensor.getId(), TimeUtility.DAY_IN_MS, TimeUtility.WEEK_IN_MS);
}else{
cleanupInternalSensorData(sensor.getId(), TimeUtility.HOUR_IN_MS, TimeUtility.FIVE_MINUTES_IN_MS, TimeUtility.DAY_IN_MS); 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); 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{ }else{
logger.fine("The sensor type is not supported by the cleanup deamon. Ignoring"); logger.fine("The sensor type is not supported by the cleanup deamon. Ignoring");
} }
@ -69,7 +68,6 @@ public class DataDeletionDaemon extends TimerTask implements HalDaemon {
try { try {
Long maxDBTimestamp = null; Long maxDBTimestamp = null;
// delete too old 5 minute periods that already have been aggregated into hours
stmt = db.getPreparedStatement("SELECT MAX(timestamp_end) FROM sensor_data_aggr" 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(1, sensorId);
@ -117,32 +115,6 @@ public class DataDeletionDaemon extends TimerTask implements HalDaemon {
} }
} }
/**
* 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<Long>{ private class AggregateDataDeleter implements SQLResultHandler<Long>{
private long sensorId = -1; private long sensorId = -1;

View file

@ -3,6 +3,7 @@ package se.koc.hal.plugin.localsensor;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import zutil.db.DBConnection; import zutil.db.DBConnection;
@ -11,24 +12,25 @@ import zutil.log.LogUtil;
import com.pi4j.io.gpio.GpioController; import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory; import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioPinDigitalInput; import com.pi4j.io.gpio.GpioPinDigitalInput;
import com.pi4j.io.gpio.Pin;
import com.pi4j.io.gpio.PinPullResistance; import com.pi4j.io.gpio.PinPullResistance;
import com.pi4j.io.gpio.PinState; import com.pi4j.io.gpio.PinState;
import com.pi4j.io.gpio.RaspiPin; import com.pi4j.io.gpio.RaspiPin;
import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent; import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent;
import com.pi4j.io.gpio.event.GpioPinListenerDigital; import com.pi4j.io.gpio.event.GpioPinListenerDigital;
public class ImpulseTracker implements Runnable { public class RPiImpulseCountSensor implements Runnable {
private static final Logger logger = LogUtil.getLogger(); private static final Logger logger = LogUtil.getLogger();
private static final int IMPULSE_REPORT_TIMEOUT = 60000; //one minute private static final int REPORT_TIMEOUT = 60_000; //one minute
private long nanoSecondsSleep = IMPULSE_REPORT_TIMEOUT * 1000000L; private long nanoSecondsSleep = REPORT_TIMEOUT * 1_000_000L;
private Integer impulseCount = 0; private volatile Integer impulseCount = 0;
private ExecutorService executorPool; private ExecutorService executorPool;
private final DBConnection db; private final DBConnection db;
private final int sensorId; private final int sensorId;
public static void main(String args[]) throws Exception { public static void main(String args[]) throws Exception {
new ImpulseTracker(2); new RPiImpulseCountSensor(2, RaspiPin.GPIO_02);
} }
/** /**
@ -36,15 +38,24 @@ public class ImpulseTracker implements Runnable {
* @param sensorId The ID of this sensor. Will be written to the DB * @param sensorId The ID of this sensor. Will be written to the DB
* @throws Exception * @throws Exception
*/ */
public ImpulseTracker(int sensorId) throws Exception{ public RPiImpulseCountSensor(int sensorId, Pin pin) throws Exception{
this.sensorId = sensorId; this.sensorId = sensorId;
// create gpio controller // create gpio controller
final GpioController gpio = GpioFactory.getInstance(); GpioController gpio = null;
try{
gpio = GpioFactory.getInstance();
}catch(IllegalArgumentException e){
logger.log(Level.SEVERE, "", e);
throw e;
}catch(UnsatisfiedLinkError e){
logger.log(Level.SEVERE, "", e);
throw e;
}
// provision gpio pin #02 as an input pin with its internal pull up resistor enabled // provision gpio pin as an input pin with its internal pull up resistor enabled
final GpioPinDigitalInput irLightSensor = gpio.provisionDigitalInputPin(RaspiPin.GPIO_02, PinPullResistance.PULL_UP); final GpioPinDigitalInput irLightSensor = gpio.provisionDigitalInputPin(pin, PinPullResistance.PULL_UP);
// create and register gpio pin listener. May require the program to be run as sudo if the GPIO pin has not been exported // create and register gpio pin listener. May require the program to be run as sudo if the GPIO pin has not been exported
irLightSensor.addListener(new GpioPinListenerDigital() { irLightSensor.addListener(new GpioPinListenerDigital() {
@ -95,7 +106,7 @@ public class ImpulseTracker implements Runnable {
long estimatedNanoTimeSpent = System.nanoTime() - startTime; //this is where the loop ends long estimatedNanoTimeSpent = System.nanoTime() - startTime; //this is where the loop ends
startTime = System.nanoTime(); //this is where the loop starts from now on startTime = System.nanoTime(); //this is where the loop starts from now on
if(estimatedNanoTimeSpent > 0){ //if no overflow if(estimatedNanoTimeSpent > 0){ //if no overflow
long nanoSecondsTooMany = estimatedNanoTimeSpent - (IMPULSE_REPORT_TIMEOUT*1000000L); long nanoSecondsTooMany = estimatedNanoTimeSpent - (REPORT_TIMEOUT*1000000L);
//System.out.println("the look took ~" + estimatedNanoTimeSpent + "ns. That is " + nanoSecondsTooMany/1000000L + "ms off"); //System.out.println("the look took ~" + estimatedNanoTimeSpent + "ns. That is " + nanoSecondsTooMany/1000000L + "ms off");
nanoSecondsSleep -= nanoSecondsTooMany / 3; //divide by constant to take into account varaiations im loop time nanoSecondsSleep -= nanoSecondsTooMany / 3; //divide by constant to take into account varaiations im loop time
} }
@ -129,7 +140,7 @@ public class ImpulseTracker implements Runnable {
@Override @Override
public void run() { public void run() {
try { try {
db.exec("INSERT INTO sensor_data_raw(timestamp, sensor_id, data) VALUES("+timestamp_end+", "+ImpulseTracker.this.sensorId+", "+data+")"); db.exec("INSERT INTO sensor_data_raw(timestamp, sensor_id, data) VALUES("+timestamp_end+", "+RPiImpulseCountSensor.this.sensorId+", "+data+")");
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); e.printStackTrace();
} }

View file

@ -10,26 +10,33 @@ public class TimeUtility {
public static final long DAY_IN_MS = HOUR_IN_MS * 24; public static final long DAY_IN_MS = HOUR_IN_MS * 24;
public static final long WEEK_IN_MS = DAY_IN_MS * 7; public static final long WEEK_IN_MS = DAY_IN_MS * 7;
public static long getTimestampPeriodStart_UTC(long periodLengthInMs, long timestamp) throws NumberFormatException{
if(periodLengthInMs < 0 || timestamp < 0)
throw new NumberFormatException("argument must be positive");
return timestamp - (timestamp % periodLengthInMs);
}
/** /**
* Get the timstamp for the given timestamp floored with the period length. The result should point to the beginning of the timestamps period. * 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 periodLengthInMs The periods length to floor the timestamp with
* @param timestamp The timestamp to floor. * @param timestamp The timestamp to floor.
* @return * @return
*/ */
public static long getTimestampPeriodStart(long periodLengthInMs, long timestamp){ public static long getTimestampPeriodStart_LOCAL(long periodLengthInMs, long timestamp) throws NumberFormatException{
if(periodLengthInMs < DAY_IN_MS){ //simple math if the period is less than a day long if(periodLengthInMs < 0 || timestamp < 0)
return timestamp - (timestamp % periodLengthInMs); throw new NumberFormatException("argument must be positive");
}else{
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(timestamp); cal.setTimeInMillis(timestamp);
boolean clear = false; boolean clear = false;
int days = millisecondsToDays(periodLengthInMs); int days = getDaysFromTimestamp(periodLengthInMs);
if(days > 0){ if(days > 0){
int currentDay = cal.get(Calendar.DAY_OF_YEAR); int currentDay = cal.get(Calendar.DAY_OF_YEAR);
cal.set(Calendar.DAY_OF_YEAR, (currentDay/days)*days); cal.set(Calendar.DAY_OF_YEAR, (currentDay/days)*days);
clear = true; clear = true;
} }
int hours = millisecondsToHourOfDay(periodLengthInMs); int hours = getHourOfDayFromTimestamp(periodLengthInMs);
if(hours > 0){ if(hours > 0){
int currentHour = cal.get(Calendar.HOUR_OF_DAY); int currentHour = cal.get(Calendar.HOUR_OF_DAY);
cal.set(Calendar.HOUR_OF_DAY, (currentHour/hours)*hours); cal.set(Calendar.HOUR_OF_DAY, (currentHour/hours)*hours);
@ -37,7 +44,7 @@ public class TimeUtility {
}else if(clear){ }else if(clear){
cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.HOUR_OF_DAY, 0);
} }
int minutes = millisecondsToMinuteOfHour(periodLengthInMs); int minutes = getMinuteOfHourFromTimestamp(periodLengthInMs);
if(minutes > 0){ if(minutes > 0){
int currentMinute = cal.get(Calendar.MINUTE); int currentMinute = cal.get(Calendar.MINUTE);
cal.set(Calendar.MINUTE, (currentMinute/minutes)*minutes); cal.set(Calendar.MINUTE, (currentMinute/minutes)*minutes);
@ -45,7 +52,7 @@ public class TimeUtility {
}else if(clear){ }else if(clear){
cal.set(Calendar.MINUTE, 0); cal.set(Calendar.MINUTE, 0);
} }
int seconds = millisecondsToSecondOfMinute(periodLengthInMs); int seconds = getSecondOfMinuteFromTimestamp(periodLengthInMs);
if(seconds > 0){ if(seconds > 0){
int currentSecond = cal.get(Calendar.SECOND); int currentSecond = cal.get(Calendar.SECOND);
cal.set(Calendar.SECOND, (currentSecond/seconds)*seconds); cal.set(Calendar.SECOND, (currentSecond/seconds)*seconds);
@ -53,7 +60,7 @@ public class TimeUtility {
}else if(clear){ }else if(clear){
cal.set(Calendar.SECOND, 0); cal.set(Calendar.SECOND, 0);
} }
int milliseconds = millisecondsToMillisecondInSecond(periodLengthInMs); int milliseconds = getMillisecondInSecondFromTimestamp(periodLengthInMs);
if(milliseconds > 0){ if(milliseconds > 0){
int currentMillisecond = cal.get(Calendar.MILLISECOND); int currentMillisecond = cal.get(Calendar.MILLISECOND);
cal.set(Calendar.MILLISECOND, (currentMillisecond/milliseconds)*milliseconds); cal.set(Calendar.MILLISECOND, (currentMillisecond/milliseconds)*milliseconds);
@ -62,39 +69,50 @@ public class TimeUtility {
} }
return cal.getTimeInMillis(); return cal.getTimeInMillis();
} }
}
public static int millisecondsToMillisecondInSecond(long ms){ public static int getMillisecondInSecondFromTimestamp(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
return (int) (ms % SECOND_IN_MS); return (int) (ms % SECOND_IN_MS);
} }
public static int millisecondsToSecondOfMinute(long ms){ public static int getSecondOfMinuteFromTimestamp(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
return (int) ((ms % MINUTES_IN_MS) / SECOND_IN_MS); return (int) ((ms % MINUTES_IN_MS) / SECOND_IN_MS);
} }
public static int millisecondsToMinuteOfHour(long ms){ public static int getMinuteOfHourFromTimestamp(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
return (int) ((ms % HOUR_IN_MS) / MINUTES_IN_MS); return (int) ((ms % HOUR_IN_MS) / MINUTES_IN_MS);
} }
public static int millisecondsToHourOfDay(long ms){ public static int getHourOfDayFromTimestamp(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
return (int) ((ms % DAY_IN_MS) / HOUR_IN_MS); return (int) ((ms % DAY_IN_MS) / HOUR_IN_MS);
} }
public static int millisecondsToDays(long ms){ public static int getDaysFromTimestamp(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
return (int) (ms / DAY_IN_MS); return (int) (ms / DAY_IN_MS);
} }
public static String msToString(long ms){ public static String msToString(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
String retval = ""; String retval = "";
int days = millisecondsToDays(ms); int days = getDaysFromTimestamp(ms);
retval += days + "days+"; retval += days + "days+";
int hours = millisecondsToHourOfDay(ms); int hours = getHourOfDayFromTimestamp(ms);
retval += (hours<10?"0"+hours:hours); retval += (hours<10?"0"+hours:hours);
int minutes = millisecondsToMinuteOfHour(ms); int minutes = getMinuteOfHourFromTimestamp(ms);
retval += ":" + (minutes<10?"0"+minutes:minutes); retval += ":" + (minutes<10?"0"+minutes:minutes);
int seconds = millisecondsToSecondOfMinute(ms); int seconds = getSecondOfMinuteFromTimestamp(ms);
retval += ":" + (seconds<10?"0"+seconds:seconds); retval += ":" + (seconds<10?"0"+seconds:seconds);
int milliseconds = millisecondsToMillisecondInSecond(ms); int milliseconds = getMillisecondInSecondFromTimestamp(ms);
retval += "." + (milliseconds<100?"0"+(milliseconds<10?"0"+milliseconds:milliseconds):milliseconds); retval += "." + (milliseconds<100?"0"+(milliseconds<10?"0"+milliseconds:milliseconds):milliseconds);
return retval; return retval;
} }

118
test/se/koc/hal/util/TimeUtilityTest.java Executable file → Normal file
View file

@ -9,19 +9,20 @@ import org.junit.Test;
import se.koc.hal.util.TimeUtility; import se.koc.hal.util.TimeUtility;
public class TimeUtilityTest { public class TimeUtilityTest {
private long currentTime; private long currentTime_UTC;
private Calendar referenceCalendar; private Calendar referenceCalendar_LOCAL;
@Before @Before
public void setup(){ public void setup(){
currentTime = System.currentTimeMillis(); currentTime_UTC = System.currentTimeMillis();
referenceCalendar = Calendar.getInstance(); referenceCalendar_LOCAL = Calendar.getInstance();
referenceCalendar.setTimeInMillis(currentTime); referenceCalendar_LOCAL.setTimeInMillis(currentTime_UTC);
} }
// Test flooring LOCAL time to the closes day
@Test @Test
public void testDayStartForCurrentTime(){ public void testDayStart_LOCAL_ForCurrentTime(){
long thisPeriodStartedAt = TimeUtility.getTimestampPeriodStart(TimeUtility.DAY_IN_MS, currentTime); long thisPeriodStartedAt = TimeUtility.getTimestampPeriodStart_LOCAL(TimeUtility.DAY_IN_MS, currentTime_UTC);
Calendar testCalendar = Calendar.getInstance(); Calendar testCalendar = Calendar.getInstance();
testCalendar.setTimeInMillis(thisPeriodStartedAt); testCalendar.setTimeInMillis(thisPeriodStartedAt);
@ -29,48 +30,106 @@ public class TimeUtilityTest {
assertEquals("second is wrong", 0, testCalendar.get(Calendar.SECOND)); assertEquals("second is wrong", 0, testCalendar.get(Calendar.SECOND));
assertEquals("minute is wrong", 0, testCalendar.get(Calendar.MINUTE)); assertEquals("minute is wrong", 0, testCalendar.get(Calendar.MINUTE));
assertEquals("hour is wrong", 0, testCalendar.get(Calendar.HOUR_OF_DAY)); assertEquals("hour is wrong", 0, testCalendar.get(Calendar.HOUR_OF_DAY));
assertEquals("day is wrong", referenceCalendar.get(Calendar.DAY_OF_YEAR), testCalendar.get(Calendar.DAY_OF_YEAR)); assertEquals("day is wrong", referenceCalendar_LOCAL.get(Calendar.DAY_OF_YEAR), testCalendar.get(Calendar.DAY_OF_YEAR));
} }
// Test flooring LOCAL time to the closes hour
@Test @Test
public void testHourStartForCurrentTime(){ public void testHourStart_LOCAL_ForCurrentTime(){
long thisPeriodStartedAt = TimeUtility.getTimestampPeriodStart(TimeUtility.HOUR_IN_MS, currentTime); long thisPeriodStartedAt = TimeUtility.getTimestampPeriodStart_LOCAL(TimeUtility.HOUR_IN_MS, currentTime_UTC);
Calendar testCalendar = Calendar.getInstance(); Calendar testCalendar = Calendar.getInstance();
testCalendar.setTimeInMillis(thisPeriodStartedAt); testCalendar.setTimeInMillis(thisPeriodStartedAt);
assertEquals("millisecond is wrong", 0, testCalendar.get(Calendar.MILLISECOND)); assertEquals("millisecond is wrong", 0, testCalendar.get(Calendar.MILLISECOND));
assertEquals("second is wrong", 0, testCalendar.get(Calendar.SECOND)); assertEquals("second is wrong", 0, testCalendar.get(Calendar.SECOND));
assertEquals("minute is wrong", 0, testCalendar.get(Calendar.MINUTE)); assertEquals("minute is wrong", 0, testCalendar.get(Calendar.MINUTE));
assertEquals("hour is wrong", referenceCalendar.get(Calendar.HOUR_OF_DAY), testCalendar.get(Calendar.HOUR_OF_DAY)); assertEquals("hour is wrong", referenceCalendar_LOCAL.get(Calendar.HOUR_OF_DAY), testCalendar.get(Calendar.HOUR_OF_DAY));
assertEquals("day is wrong", referenceCalendar.get(Calendar.DAY_OF_YEAR), testCalendar.get(Calendar.DAY_OF_YEAR)); assertEquals("day is wrong", referenceCalendar_LOCAL.get(Calendar.DAY_OF_YEAR), testCalendar.get(Calendar.DAY_OF_YEAR));
} }
// Test flooring LOCAL time to the closes minute
@Test @Test
public void testMinuteStartForCurrentTime(){ public void testMinuteStart_LOCAL_ForCurrentTime(){
long thisPeriodStartedAt = TimeUtility.getTimestampPeriodStart(TimeUtility.MINUTES_IN_MS, currentTime); long thisPeriodStartedAt = TimeUtility.getTimestampPeriodStart_LOCAL(TimeUtility.MINUTES_IN_MS, currentTime_UTC);
Calendar testCalendar = Calendar.getInstance(); Calendar testCalendar = Calendar.getInstance();
testCalendar.setTimeInMillis(thisPeriodStartedAt); testCalendar.setTimeInMillis(thisPeriodStartedAt);
assertEquals("millisecond is wrong", 0, testCalendar.get(Calendar.MILLISECOND)); assertEquals("millisecond is wrong", 0, testCalendar.get(Calendar.MILLISECOND));
assertEquals("second is wrong", 0, testCalendar.get(Calendar.SECOND)); assertEquals("second is wrong", 0, testCalendar.get(Calendar.SECOND));
assertEquals("minute is wrong", referenceCalendar.get(Calendar.MINUTE), testCalendar.get(Calendar.MINUTE)); assertEquals("minute is wrong", referenceCalendar_LOCAL.get(Calendar.MINUTE), testCalendar.get(Calendar.MINUTE));
assertEquals("hour is wrong", referenceCalendar.get(Calendar.HOUR_OF_DAY), testCalendar.get(Calendar.HOUR_OF_DAY)); assertEquals("hour is wrong", referenceCalendar_LOCAL.get(Calendar.HOUR_OF_DAY), testCalendar.get(Calendar.HOUR_OF_DAY));
assertEquals("day is wrong", referenceCalendar.get(Calendar.DAY_OF_YEAR), testCalendar.get(Calendar.DAY_OF_YEAR)); assertEquals("day is wrong", referenceCalendar_LOCAL.get(Calendar.DAY_OF_YEAR), testCalendar.get(Calendar.DAY_OF_YEAR));
} }
// Test flooring LOCAL time to the closes second
@Test @Test
public void testSecondStartForCurrentTime(){ public void testSecondStart_LOCAL_ForCurrentTime(){
long thisPeriodStartedAt = TimeUtility.getTimestampPeriodStart(TimeUtility.SECOND_IN_MS, currentTime); long thisPeriodStartedAt = TimeUtility.getTimestampPeriodStart_LOCAL(TimeUtility.SECOND_IN_MS, currentTime_UTC);
Calendar testCalendar = Calendar.getInstance(); Calendar testCalendar = Calendar.getInstance();
testCalendar.setTimeInMillis(thisPeriodStartedAt); testCalendar.setTimeInMillis(thisPeriodStartedAt);
assertEquals("millisecond is wrong", 0, testCalendar.get(Calendar.MILLISECOND)); assertEquals("millisecond is wrong", 0, testCalendar.get(Calendar.MILLISECOND));
assertEquals("second is wrong", referenceCalendar.get(Calendar.SECOND), testCalendar.get(Calendar.SECOND)); assertEquals("second is wrong", referenceCalendar_LOCAL.get(Calendar.SECOND), testCalendar.get(Calendar.SECOND));
assertEquals("minute is wrong", referenceCalendar.get(Calendar.MINUTE), testCalendar.get(Calendar.MINUTE)); assertEquals("minute is wrong", referenceCalendar_LOCAL.get(Calendar.MINUTE), testCalendar.get(Calendar.MINUTE));
assertEquals("hour is wrong", referenceCalendar.get(Calendar.HOUR_OF_DAY), testCalendar.get(Calendar.HOUR_OF_DAY)); assertEquals("hour is wrong", referenceCalendar_LOCAL.get(Calendar.HOUR_OF_DAY), testCalendar.get(Calendar.HOUR_OF_DAY));
assertEquals("day is wrong", referenceCalendar.get(Calendar.DAY_OF_YEAR), testCalendar.get(Calendar.DAY_OF_YEAR)); assertEquals("day is wrong", referenceCalendar_LOCAL.get(Calendar.DAY_OF_YEAR), testCalendar.get(Calendar.DAY_OF_YEAR));
} }
// Test flooring UTC time to the closes day
@Test
public void testDayStart_UTC_ForCurrentTime(){
long thisPeriodStartedAt = TimeUtility.getTimestampPeriodStart_UTC(TimeUtility.DAY_IN_MS, currentTime_UTC);
assertEquals("millisecond is wrong", 0, TimeUtility.getMillisecondInSecondFromTimestamp(thisPeriodStartedAt));
assertEquals("second is wrong", 0, TimeUtility.getSecondOfMinuteFromTimestamp(thisPeriodStartedAt));
assertEquals("minute is wrong", 0, TimeUtility.getMinuteOfHourFromTimestamp(thisPeriodStartedAt));
assertEquals("hour is wrong", 0, TimeUtility.getHourOfDayFromTimestamp(thisPeriodStartedAt));
assertEquals("day is wrong", TimeUtility.getDaysFromTimestamp(currentTime_UTC), TimeUtility.getDaysFromTimestamp(thisPeriodStartedAt));
}
// Test flooring UTC time to the closes hour
@Test
public void testHourStart_UTC_ForCurrentTime(){
long thisPeriodStartedAt = TimeUtility.getTimestampPeriodStart_UTC(TimeUtility.HOUR_IN_MS, currentTime_UTC);
Calendar testCalendar = Calendar.getInstance();
testCalendar.setTimeInMillis(thisPeriodStartedAt);
assertEquals("millisecond is wrong", 0, TimeUtility.getMillisecondInSecondFromTimestamp(thisPeriodStartedAt));
assertEquals("second is wrong", 0, TimeUtility.getSecondOfMinuteFromTimestamp(thisPeriodStartedAt));
assertEquals("minute is wrong", 0, TimeUtility.getMinuteOfHourFromTimestamp(thisPeriodStartedAt));
assertEquals("hour is wrong", TimeUtility.getHourOfDayFromTimestamp(currentTime_UTC), TimeUtility.getHourOfDayFromTimestamp(thisPeriodStartedAt));
assertEquals("day is wrong", TimeUtility.getDaysFromTimestamp(currentTime_UTC), TimeUtility.getDaysFromTimestamp(thisPeriodStartedAt));
}
// Test flooring UTC time to the closes minute
@Test
public void testMinuteStart_UTC_ForCurrentTime(){
long thisPeriodStartedAt = TimeUtility.getTimestampPeriodStart_UTC(TimeUtility.MINUTES_IN_MS, currentTime_UTC);
Calendar testCalendar = Calendar.getInstance();
testCalendar.setTimeInMillis(thisPeriodStartedAt);
assertEquals("millisecond is wrong", 0, TimeUtility.getMillisecondInSecondFromTimestamp(thisPeriodStartedAt));
assertEquals("second is wrong", 0, TimeUtility.getSecondOfMinuteFromTimestamp(thisPeriodStartedAt));
assertEquals("minute is wrong", TimeUtility.getMinuteOfHourFromTimestamp(currentTime_UTC), TimeUtility.getMinuteOfHourFromTimestamp(thisPeriodStartedAt));
assertEquals("hour is wrong", TimeUtility.getHourOfDayFromTimestamp(currentTime_UTC), TimeUtility.getHourOfDayFromTimestamp(thisPeriodStartedAt));
assertEquals("day is wrong", TimeUtility.getDaysFromTimestamp(currentTime_UTC), TimeUtility.getDaysFromTimestamp(thisPeriodStartedAt));
}
// Test flooring UTC time to the closes second
@Test
public void testSecondStart_UTC_ForCurrentTime(){
long thisPeriodStartedAt = TimeUtility.getTimestampPeriodStart_UTC(TimeUtility.SECOND_IN_MS, currentTime_UTC);
Calendar testCalendar = Calendar.getInstance();
testCalendar.setTimeInMillis(thisPeriodStartedAt);
assertEquals("millisecond is wrong", 0, TimeUtility.getMillisecondInSecondFromTimestamp(thisPeriodStartedAt));
assertEquals("second is wrong", TimeUtility.getSecondOfMinuteFromTimestamp(currentTime_UTC), TimeUtility.getSecondOfMinuteFromTimestamp(thisPeriodStartedAt));
assertEquals("minute is wrong", TimeUtility.getMinuteOfHourFromTimestamp(currentTime_UTC), TimeUtility.getMinuteOfHourFromTimestamp(thisPeriodStartedAt));
assertEquals("hour is wrong", TimeUtility.getHourOfDayFromTimestamp(currentTime_UTC), TimeUtility.getHourOfDayFromTimestamp(thisPeriodStartedAt));
assertEquals("day is wrong", TimeUtility.getDaysFromTimestamp(currentTime_UTC), TimeUtility.getDaysFromTimestamp(thisPeriodStartedAt));
}
// Test printing converting milliseconds to text
@Test @Test
public void testMsToString(){ public void testMsToString(){
//low values //low values
@ -90,9 +149,22 @@ public class TimeUtilityTest {
assertEquals("0days+23:00:00.000", TimeUtility.msToString(TimeUtility.HOUR_IN_MS*23)); 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)); assertEquals("369days+00:00:00.000", TimeUtility.msToString(TimeUtility.DAY_IN_MS*369));
//high overflow values
assertEquals("0days+00:00:01.999", TimeUtility.msToString(1999));
assertEquals("0days+00:02:39.000", TimeUtility.msToString(TimeUtility.SECOND_IN_MS*159));
assertEquals("0days+02:39:00.000", TimeUtility.msToString(TimeUtility.MINUTES_IN_MS*159));
assertEquals("5days+03:00:00.000", TimeUtility.msToString(TimeUtility.HOUR_IN_MS*123));
//combinations //combinations
long ms = (TimeUtility.DAY_IN_MS*999) + (TimeUtility.HOUR_IN_MS*23) + (TimeUtility.MINUTES_IN_MS*59) + (TimeUtility.SECOND_IN_MS*59) + 999; 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)); assertEquals("999days+23:59:59.999", TimeUtility.msToString(ms));
} }
// Test printing converting milliseconds to text for a negative time
@Test(expected=NumberFormatException.class)
public void testMsToStringForNegativeArgument(){
//low values
TimeUtility.msToString(-1);
}
} }