


Yahoo! Research presents Time Explorer
No complaints about the venue either. Met so many cool and interesting researchers in the field of IR (which is basically "search").
Geoff Hendrey's blog about programming and travel.



format1=yyyy-MM-dd HH:mm:ss.SSS z
format2=yyyy-MM-dd HH:mm:ss.SSS
format3=yyyy-MM-dd HH:mm:ss
format4=yyyy-MM-dd HH:mm
format5=E, MMM dd, yyyy
format6=MMM dd yyyy
format7=MMM dd yyyy HH:mm
format8=MMM dd, yyyy
format9=MMM. dd, yyyy
format10=MM/dd/yy
format11=MM/dd/yyyy
format12=M/d/yy
format13=yyyy-MM-dd
format14=yyyy-MM-dd, E
format15=yyyy MMM. dd, HHh 1mmm ss's'
format16=yyyy/M/dd/HH:mm:ss
format17=yyyy-M-dd'T'HH:mm:ss
private static final Pattern ISOLATED_DIGIT = Pattern.compile("(^|\\D|\\s)(\\d)(\\D|$|\\s)"); //digit can be surrounded by non-digit but also begining or end of line or whitespace
public static Object parseDate(String dateString, String id, String paramName) {
if(null == dateString){
return dateString;
}
if(dateString.equalsIgnoreCase("NOW")){
//default time stamp is Zulu time (time at prime meridian, Greenwich)
long currentTime = System.currentTimeMillis();
Timestamp ts = new java.sql.Timestamp(currentTime-TimeZone.getDefault().getOffset(currentTime));
return ts;
}else{
dateString = zeroPadIsolatedDigits(dateString);
if(log.isDebugEnabled())log.debug("input zero-padded to " + dateString);
for(Map.Entry entry:DATE_FORMATS.entrySet()){
String key = entry.getKey().toString();
DateFormat dateFormat = new SimpleDateFormat(entry.getValue().toString());
dateFormat.setLenient(false);
try{
java.util.Date date = dateFormat.parse(dateString);
//I have found bugs in the parse method, in which strings that *don't* actually match the format template
//get parsed without Exception, thus truncating trailing parts of the date (like the timezone). For this reason
//I am forced to regurgitate the date string, and compare it to the input, and if they don't match, skip forward in the loop
String regurgitated = dateFormat.format(date);//...and no, we can't just compare the regurgitated string b/c PST and get adjusted to PDT
if(regurgitated.length() != dateString.length()){ //compare their lengths to make sure nothing got truncated
if(log.isDebugEnabled())log.debug(dateString + " was parsed with format " + entry.getValue() + " but was apparently parsed incorrectly as " + regurgitated + " length difference was "+(regurgitated.length()-dateString.length()));
continue;
}else{
//the length of the regurgitated string matches the length of the input.
//We still need to eat the regurgitated string, and compare the resulting ms since epoch to the Date we originally parsed to make sure we didn't
//encounter a SimpleDateFormat parsing bug.
//Example: 2010-9-23 12:22:23.000 PST gets regurgintated as 2010-09-23 13:22:23.000 PDT, which is different text, but same ms since epoch
//So the above illustrates why we cannot just compare the regurgitated text to the input dateString, because Java may decide on an equivalent but
//textually different representation from the input (PST is the same as PDT in the half of the year when daylight savings time isn't in effect).
java.util.Date reparsed = dateFormat.parse(regurgitated);
if(reparsed.getTime() != date.getTime()){
if(log.isDebugEnabled())log.debug(dateString+" produces different ms since epoch than " +regurgitated );
continue;
}
}
if(log.isDebugEnabled())log.debug("handled date" + dateString +" using format template: " + entry.getValue().toString() + " which regurgitated " + dateString);
TimeZone timeZone = dateFormat.getTimeZone();
//if(log.isDebugEnabled())log.debug(timeZone);
//convert to GMT time and account for timezone
return new java.sql.Timestamp(date.getTime()-timeZone.getOffset(date.getTime()));
}catch(ParseException pe){
if(log.isDebugEnabled()){
log.debug(pe.getMessage());
log.debug(dateString + "couldn't be parsed with format " + entry.getValue());
}
}
}
throw Util.newApplicationException(id, "date "+ dateString+" could not be parsed.","DataFormatException", 130, paramName);
}
}
private static String zeroPadIsolatedDigits(String s) {
//Java date parsing will prepend leading zeros to islolated digits. For this reason, I prepend a zero to the isolated digits before parsing the string, so that the regurgitation step
//produces a string identical to the input
boolean done = false;
//this is an obscure case in which the matching regions overlap, therefore m.find() only finds one of the overlapping instance.
//For instance, 100-1-2. Find() won't find "-2 " because it overalps "-1-". Consequently we have to repeatedly call find on a new matcher after each replacement has been made
while (!done) {
Matcher m = ISOLATED_DIGIT.matcher(s);
StringBuffer buf = new StringBuffer();
if (m.find()) {
if(log.isDebugEnabled())log.debug("found isolated digit:" + m.group(2));
m.appendReplacement(buf, m.group(1) + "0" + m.group(2) + m.group(3)); //need to stuff back in the stuff before the isolated digit, then 0 (zero-pad), then the digit, then stuff after)
}else {
done = true;
}
m.appendTail(buf); //OK, al isolated digits have been replaced by leading-zero padded digits
s = buf.toString(); //replace input with zero-padded input
}
return s;
}
int entry_at(int row, int column) {
// ...
} 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
1 0 3 2 5 4 7 6 9 8 11 10 13 12 15 14 17 16 19 18 21 20 23 22 25 24 27 26 29 28 31 30
2 3 0 1 6 7 4 5 10 11 8 9 14 15 12 13 18 19 16 17 22 23 20 21 26 27 24 25 30 31 28 29
3 2 1 0 7 6 5 4 11 10 9 8 15 14 13 12 19 18 17 16 23 22 21 20 27 26 25 24 31 30 29 28
4 5 6 7 0 1 2 3 12 13 14 15 8 9 10 11 20 21 22 23 16 17 18 19 28 29 30 31 24 25 26 27
5 4 7 6 1 0 3 2 13 12 15 14 9 8 11 10 21 20 23 22 17 16 19 18 29 28 31 30 25 24 27 26
6 7 4 5 2 3 0 1 14 15 12 13 10 11 8 9 22 23 20 21 18 19 16 17 30 31 28 29 26 27 24 25
7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8 23 22 21 20 19 18 17 16 31 30 29 28 27 26 25 24
8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7 24 25 26 27 28 29 30 31 16 17 18 19 20 21 22 23
9 8 11 10 13 12 15 14 1 0 3 2 5 4 7 6 25 24 27 26 29 28 31 30 17 16 19 18 21 20 23 22
10 11 8 9 14 15 12 13 2 3 0 1 6 7 4 5 26 27 24 25 30 31 28 29 18 19 16 17 22 23 20 21
11 10 9 8 15 14 13 12 3 2 1 0 7 6 5 4 27 26 25 24 31 30 29 28 19 18 17 16 23 22 21 20
12 13 14 15 8 9 10 11 4 5 6 7 0 1 2 3 28 29 30 31 24 25 26 27 20 21 22 23 16 17 18 19
13 12 15 14 9 8 11 10 5 4 7 6 1 0 3 2 29 28 31 30 25 24 27 26 21 20 23 22 17 16 19 18
14 15 12 13 10 11 8 9 6 7 4 5 2 3 0 1 30 31 28 29 26 27 24 25 22 23 20 21 18 19 16 17
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
17 16 19 18 21 20 23 22 25 24 27 26 29 28 31 30 1 0 3 2 5 4 7 6 9 8 11 10 13 12 15 14
18 19 16 17 22 23 20 21 26 27 24 25 30 31 28 29 2 3 0 1 6 7 4 5 10 11 8 9 14 15 12 13
19 18 17 16 23 22 21 20 27 26 25 24 31 30 29 28 3 2 1 0 7 6 5 4 11 10 9 8 15 14 13 12
20 21 22 23 16 17 18 19 28 29 30 31 24 25 26 27 4 5 6 7 0 1 2 3 12 13 14 15 8 9 10 11
21 20 23 22 17 16 19 18 29 28 31 30 25 24 27 26 5 4 7 6 1 0 3 2 13 12 15 14 9 8 11 10
22 23 20 21 18 19 16 17 30 31 28 29 26 27 24 25 6 7 4 5 2 3 0 1 14 15 12 13 10 11 8 9
23 22 21 20 19 18 17 16 31 30 29 28 27 26 25 24 7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8
24 25 26 27 28 29 30 31 16 17 18 19 20 21 22 23 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7
25 24 27 26 29 28 31 30 17 16 19 18 21 20 23 22 9 8 11 10 13 12 15 14 1 0 3 2 5 4 7 6
26 27 24 25 30 31 28 29 18 19 16 17 22 23 20 21 10 11 8 9 14 15 12 13 2 3 0 1 6 7 4 5
27 26 25 24 31 30 29 28 19 18 17 16 23 22 21 20 11 10 9 8 15 14 13 12 3 2 1 0 7 6 5 4
28 29 30 31 24 25 26 27 20 21 22 23 16 17 18 19 12 13 14 15 8 9 10 11 4 5 6 7 0 1 2 3
29 28 31 30 25 24 27 26 21 20 23 22 17 16 19 18 13 12 15 14 9 8 11 10 5 4 7 6 1 0 3 2
30 31 28 29 26 27 24 25 22 23 20 21 18 19 16 17 14 15 12 13 10 11 8 9 6 7 4 5 2 3 0 1
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
package random;
import java.util.Arrays;
/**
*
* @author geoffreyhendrey
*/
public class MadMatrix {
int[][] a;
public MadMatrix(int size){
a = new int[size][size];
for(int r=0;r<size;r++){
for(int c=0;c<size;c++){
a[r][c] = valAboveDiag(r,c);
}
}
}
@Override
public String toString(){
StringBuilder buf = new StringBuilder();
for(int[] row:a){
for(int column:row){
buf.append(String.format("%2d ", column));
}
buf.append("\n");
}
return buf.toString();
}
private int valAboveDiag(int r, int c) {
if(r>c){
int tmp = c;
c=r;
r=tmp;
}
int order = getOrder(c);
if(0 == order){
if(r==c) return 0;
return 1;
}
int mid = 1<<order;
if(c >=mid && r<mid){
return valAboveDiag(r, c-mid) + mid;
}else{
return valAboveDiag(r-mid, c-mid);
}
}
private static int getOrder(int c){
int order = 0;
while(c > 1){
c >>>=1;
order++;
}
return order;
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
MadMatrix mm = new MadMatrix(32);
System.out.println(mm);
}
}
private static String encrypt(String encryptMe, byte[] cryptKey, SecretKey secretKey, byte[] iv){
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
byte[] raw = encryptMe.toString().getBytes("ASCII");
if(log.isDebugEnabled())log.debug("unencrypted bytes: " + raw.length);
byte[] cipherText = new byte[cipher.getOutputSize(raw.length)];
int ctLength = cipher.update(raw, 0, raw.length, cipherText, 0);
ctLength += cipher.doFinal(cipherText, ctLength);
if(log.isDebugEnabled())log.debug("ctLength: " + ctLength);
if(log.isDebugEnabled())log.debug("cipherText Length: " + cipherText.length);
//raw = cipher.doFinal(raw, 0, raw.length);
byte[] copy = new byte[ctLength];
System.arraycopy(cipherText, 0, copy, 0, ctLength);
String encrypted = new String(new Base64().encode(copy), "US-ASCII");
return encrypted;
} catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
}

support for relationships in REST URLs (calling them 'links')
...account/db/link/FRIENDSHIP/from/uname/john/to/uname/tom
The path parameters "from" and "to" describe the tail and the head of
the relationship, respectively. In other words, we can draw a
relationship as an arrow from one table to another table. This makes
'from' and 'to' the natural path parameters.
The REST URL above is equivalent to:
NAME=FIND_FRIEND;
ROW f1 FROM FRIEND;
ROW f2 FROM FRIEND;
f1 RELATED f2 VIA FRIENDSHIP;
WHERE(){
f1.uname='john' AND f2.uname='tom'
..account/db/link/FRIENDSHIP/from/uname/john
is equivalent to:
NAME=FIND_FRIEND;
ROW f1 FROM FRIEND;
ROW f2 FROM FRIEND;
f1 RELATED f2 VIA FRIENDSHIP;
WHERE(TEXT name1){
f1.uname=${name1}
.../account/db/query/FIND_FRIEND/name1/john
So it all will play nicely with existing queries you have already
defined, and new ones you may define in the future.
I also intend to support cookies. So let's say you wanted to log a
user in via https, and then securely transmit his password with each
subsequent REST URL using regular HTTP (not https). First the user
logs in via a secure (httpS) rest access to a query you named LOGIN.
So the password below would not be in the clear, since the form on the
HTML page would GET to an HTTPS url. The URL is constructed
dynamically by the form on submission:
https://nextdb.net/...db/query/LOGIN/uname/geoff/pwd/xxxxxxx;set-cookie
the 'set-cookie' matrix parameter tells nextdb to return a set-cookie
HTTP header with a value equal to cypher('xxxxxxxx'). I.e., nextdb
will encrypt the value that was sent in your request's REST URL,
wherever it finds ;set-cookie, and tell the browser to pass a cookie
named 'pwd' (in this example) with a value that is the encrypted
version of whatever is in the REST URL for the password in the pwd/
{password} path parameter. The set-cookie matrix parameter can occur
at any path segment in the URL, and would be applied to whatever path
parameter is described by the path segment.
from that point on you can use regular rest URLs and nextdb will
substitute into your URL the encrypted cookie value. So you can use
URL's like this in your application, for later actions that require a
user's password:
.../db/link/ACCOUNT_DETAILS/from/pwd/;get-cookie
The get-cookie matrix parameter int the URL allows an HTML page to be
designed with static URLs, into which nextdb will dynamically
substitute the URL path parameters with values sent by the browser as
cookies. It's a bit of a mind bender but this allows a site be
designed with static URLs. The only dynamic URL ever generated by the
app is the single login query that sets the pwd/{password} path
parameter.
so when nextdb receives the static URL above, it will first see that
the URL incudes a get-cookie matric paramter. NextDB will then look
for a cookie named 'pwd' sent by the browser (or other http client),
decrypt the cookie value, and place the value into the URL where it
finds the get-cookie matrix parameter:
.../db/link/ACCOUNT_DETAILS/from/pwd/xxxxxxxx/
The URL above is then equivalent to this NextQuery expression:
NAME=ACCOUNT_DETAILS;
ROW r1 FROM USER;
ROW r2 FROM ACCOUNT;
r1 RELATED r2 VIA MY_ACCOUNT;
WHERE(){
r1.pwd='xxxxxxxx'