Index: /trunk/hiptop/pester/net/sabi/pester/Alarm.java
===================================================================
--- /trunk/hiptop/pester/net/sabi/pester/Alarm.java	(revision 248)
+++ /trunk/hiptop/pester/net/sabi/pester/Alarm.java	(revision 249)
@@ -21,5 +21,5 @@
     private String mMessage;
     private int mType;
-    private int mPeriod; // XXX rename to 'interval'
+    private int mInterval; // XXX rename to 'interval'
     private Date mDate;
     private RingToneObject mAlert;
@@ -38,5 +38,5 @@
     }
     public int getPeriod() {
-	return mPeriod;
+	return mInterval;
     }
     public boolean getUsesPeriod() {
@@ -58,5 +58,5 @@
     public void setPeriod(int period, boolean repeating) {
 	mType = repeating ? TYPE_PERIODIC_REPEATING : TYPE_PERIODIC;
-	mPeriod = period;
+	mInterval = period;
     }
     public void setDate(Date date) {
@@ -75,5 +75,5 @@
 	    dataStream.writeByte(VERSION_1);
 	    dataStream.writeUTF(mMessage);
-	    dataStream.writeInt(mPeriod);
+	    dataStream.writeInt(mInterval);
 	    dataStream.writeInt(mDate.getUnixTimeGMT());
 	    dataStream.writeInt(mType);
@@ -96,5 +96,5 @@
 	    }
 	    mMessage = dataStream.readUTF();
-	    mPeriod = dataStream.readInt();
+	    mInterval = dataStream.readInt();
 	    mType = dataStream.readInt();
 	} catch (Exception e) {
@@ -108,10 +108,18 @@
     }
 
+    public static int secondsFromNow(Date date) {
+	return date.getDangerTimeGMT() - new Date().getDangerTimeGMT();
+    }
+
+    protected int remainingInterval() {
+	return secondsFromNow(mDate);
+    }
+
     void resume() {
 	mState = STATE_SCHEDULED;
-	int period = mDate.getDangerTimeGMT() - new Date().getDangerTimeGMT();
-	if (period < 0) period = 0;
-	mAlarm.resetWake(period);
-	DEBUG.p("resetWake: " + description() + " - " + period + "s left");
+	int interval = remainingInterval();
+	if (interval < 0) interval = 0;
+	mAlarm.resetWake(interval);
+	DEBUG.p("resetWake: " + description() + " - " + interval + "s left");
 	update();
     }
@@ -120,5 +128,5 @@
 	if (getUsesPeriod()) {
 	    mDate = new Date();
-	    mDate.addSeconds(mPeriod);
+	    mDate.addSeconds(mInterval);
 	}
 	resume();
@@ -146,7 +154,7 @@
 	switch (mType) {
 	case TYPE_PERIODIC:
-	    sb.append("periodic (").append(mPeriod).append("s) "); break;
+	    sb.append("periodic (").append(mInterval).append("s) "); break;
 	case TYPE_PERIODIC_REPEATING:
-	    sb.append("repeating periodic (").append(mPeriod).append("s) ");
+	    sb.append("repeating periodic (").append(mInterval).append("s) ");
 	    break;
 	case TYPE_DATE:
@@ -158,5 +166,5 @@
     }
 
-    protected static String dateTimeString(Date date) {
+    public static String dateTimeString(Date date, boolean relative) {
 	if (date == null)
 	    return null;
@@ -164,15 +172,65 @@
 	String timeFormat = LocaleUtils.getMediumTimeFormat();
 	String dateFormat = LocaleUtils.getMediumDateFormat();
-	return StringFormat.withFormat(layout,
-				       DateFormat.withFormat(timeFormat, date),
-				       DateFormat.withFormat(dateFormat, date));
+	String timeString = DateFormat.withFormat(timeFormat, date);
+	String dateString = DateFormat.withFormat(dateFormat, date);
+	if (relative) {
+	    int daysFromToday = date.getDaysBetween(new Date());
+	    if (daysFromToday == 0)
+		dateString = "Today"; // XXX localize
+	    else if (daysFromToday == 1)
+		dateString = "Tomorrow"; // XXX localize
+	    else if (daysFromToday < 7)
+		dateString = date.getDayString();
+	}
+	return StringFormat.withFormat(layout, timeString, dateString);
+    }
+
+    protected static void addUnit(StringBuffer sb, int n, String unit) {
+	if (n == 0) return;
+	if (sb.length() > 0) sb.append(", ");
+	if (n == 1) sb.append("one ").append(unit);
+	else sb.append(n).append(' ').append(unit).append("s");
+    }
+
+    public static String intervalString(int i) { // XXX localize
+	if (i < 0)
+	    return "expired";
+	StringBuffer sb = new StringBuffer();
+	if (i < 60) {
+	    addUnit(sb, i, "second");
+	} else {
+	    i /= 60; // minutes
+	    if (i < 24 * 60) {
+		addUnit(sb, i / 60, "hour");
+		addUnit(sb, i % 60, "minute");
+	    } else {
+		i /= 60; // hours
+		if (i < 365) {
+		    addUnit(sb, i / 24, "day");
+		    addUnit(sb, i % 24, "hour");
+		} else {
+		    i /= 24; // days
+		    addUnit(sb, i / 365, "year"); // XXX actually 365.242199
+		    addUnit(sb, i % 365, "day");
+		}
+	    }
+	}
+	return sb.toString();
+    }
+
+    public String getDateTimeString(boolean relative) {
+	return dateTimeString(mDate, relative);
     }
 
     public String getDateTimeString() {
-	return dateTimeString(mDate);
+	return getDateTimeString(false);
+    }
+
+    public String getIntervalString() {
+	return intervalString(remainingInterval());
     }
    
     public String toString() {
-	return mMessage;
+	return getDateTimeString(true) + " - " + mMessage;
     }
 
Index: /trunk/hiptop/pester/net/sabi/pester/AlarmListView.java
===================================================================
--- /trunk/hiptop/pester/net/sabi/pester/AlarmListView.java	(revision 248)
+++ /trunk/hiptop/pester/net/sabi/pester/AlarmListView.java	(revision 249)
@@ -138,4 +138,5 @@
     // XXX check before dismissing dialog that alarm is valid
     // XXX and display alarm date
+    // XXX default to now somehow?  Would be useful.
     protected void constrainDate() {
 	// XXX schedule every minute (second?) if in AT mode
@@ -235,6 +236,7 @@
 	if (item == null) // this is dumb, why do we get asked for a tooltip?
 	    return null;
-	// XXX need "in XXX minutes" too
-	return ((Alarm)item).getDateTimeString();
+	// XXX need to refresh, localize
+	Alarm alarm = (Alarm)item;
+	return alarm.getDateTimeString() + " - " + alarm.getIntervalString();
     }
 }
