package water.rapids.ast.prims.time;
import org.joda.time.Chronology;
import org.joda.time.IllegalFieldValueException;
import org.joda.time.chrono.ISOChronology;
import water.Key;
import water.MRTask;
import water.fvec.Chunk;
import water.fvec.Frame;
import water.fvec.NewChunk;
import water.fvec.Vec;
import water.rapids.Val;
import water.rapids.ast.AstBuiltin;
import water.rapids.vals.ValFrame;
import water.util.ArrayUtils;
import java.util.ArrayList;
/**
* Convert year, month, day, hour, minute, sec, msec to Unix epoch time
* (in milliseconds).
*
* This is a replacement for {@code AstMktime} class.
*/
public class AstMoment extends AstBuiltin<AstMoment> {
@Override public int nargs() {
return 8;
}
public String[] args() {
return new String[]{"yr", "mo", "dy", "hr", "mi", "se", "ms"};
}
@Override public String str() {
return "moment";
}
@Override
protected ValFrame exec(Val[] args) {
// Parse the input arguments, verifying their validity.
boolean naResult = false;
long numRows = -1;
int[] timeparts = new int[7];
ArrayList<Integer> chunksmap = new ArrayList<>(7);
ArrayList<Vec> timevecs = new ArrayList<>(7);
for (int i = 0; i < 7; i++) {
Val vi = args[i + 1];
if (vi.isFrame()) {
Frame fr = vi.getFrame();
if (fr.numCols() != 1)
throw new IllegalArgumentException("Argument " + i + " is a frame with " + fr.numCols() + " columns");
if (!fr.vec(0).isNumeric())
throw new IllegalArgumentException("Argument " + i + " is not a numeric column");
if (fr.numRows() == 0)
throw new IllegalArgumentException("Column " + i + " has 0 rows");
if (fr.numRows() == 1) {
double d = fr.vec(0).at(0);
if (Double.isNaN(d))
naResult = true;
else
timeparts[i] = (int) d;
} else {
if (numRows == -1)
numRows = fr.numRows();
if (fr.numRows() != numRows)
throw new IllegalArgumentException("Incompatible vec " + i + " having " + fr.numRows() + " rows, whereas " +
"other vecs have " + numRows + " rows.");
timevecs.add(fr.vec(0));
chunksmap.add(i);
}
} else if (vi.isNum()){
double d = vi.getNum();
if (Double.isNaN(d))
naResult = true;
else
timeparts[i] = (int) d;
} else {
throw new IllegalArgumentException("Argument " + i + " is neither a number nor a frame");
}
}
// If all arguments are scalars, return a 1x1 frame
if (timevecs.isEmpty()) {
double val = Double.NaN;
if (!naResult) {
try {
val = ISOChronology.getInstanceUTC().getDateTimeMillis(timeparts[0], timeparts[1], timeparts[2],
timeparts[3], timeparts[4], timeparts[5], timeparts[6]);
} catch (IllegalFieldValueException ignored) {}
}
return make1x1Frame(val);
}
// If the result is all-NAs, make a constant NA vec
if (naResult) {
long n = timevecs.get(0).length();
Vec v = Vec.makeCon(Double.NaN, n, Vec.T_TIME);
Frame fr = new Frame(Key.<Frame>make(), new String[]{"time"}, new Vec[]{v});
return new ValFrame(fr);
}
// Some arguments are vecs -- create a frame of the same size
Vec[] vecs = timevecs.toArray(new Vec[timevecs.size()]);
int[] cm = ArrayUtils.toPrimitive(chunksmap);
Frame fr = new SetTimeTask(timeparts, cm)
.doAll(Vec.T_TIME, vecs)
.outputFrame(new String[]{"time"}, null);
return new ValFrame(fr);
}
private ValFrame make1x1Frame(double val) {
Vec v = Vec.makeTimeVec(new double[]{val}, null);
Frame f = new Frame(new String[]{"time"}, new Vec[]{v});
return new ValFrame(f);
}
private static class SetTimeTask extends MRTask<SetTimeTask> {
private int[] tp;
private int[] cm;
/**
* @param timeparts is the array of [year, month, day, hrs, mins, secs, ms]
* for all constant parts of the date;
* @param chunksmap is a mapping between chunks indices and the timeparts
* array. For example, if {@code chunksmap = [1, 2]},
* then the first chunk describes the "month" part of the
* date, and the second chunk the "day" part.
*/
public SetTimeTask(int[] timeparts, int[] chunksmap) {
tp = timeparts;
cm = chunksmap;
}
@Override public void map(Chunk[] chks, NewChunk nc) {
int nVecs = cm.length;
assert chks.length == nVecs;
Chronology chronology = ISOChronology.getInstanceUTC();
int nChunkRows = chks[0]._len;
int[] tpl = new int[tp.length];
System.arraycopy(tp, 0, tpl, 0, tp.length);
BYROW:
for (int i = 0; i < nChunkRows; i++) {
for (int j = 0; j < nVecs; j++) {
double d = chks[j].atd(i);
if (Double.isNaN(d)) {
nc.addNum(Double.NaN);
continue BYROW;
}
tpl[cm[j]] = (int) d;
}
try {
double millis = chronology.getDateTimeMillis(tpl[0], tpl[1], tpl[2], tpl[3], tpl[4], tpl[5], tpl[6]);
nc.addNum(millis);
} catch (IllegalFieldValueException e) {
nc.addNum(Double.NaN);
}
}
}
}
}