Port date/time functions to GDateTime
[projects/chimara/chimara.git] / libchimara / datetime.c
index e01ab105ed3655f66ea5c72306a39b61570d9ac2..9d5c2cf2dec83f5e05ead5e015a09d31d9d00bb9 100644 (file)
@@ -1,5 +1,117 @@
 #include <glib.h>
 #include "glk.h"
+#include "magic.h"
+
+/* Parts adapted from Andrew Plotkin's Cheapglk implementation */
+
+/* Copy a GDateTime to a glkdate. */
+static void
+date_from_gdatetime(glkdate_t *date, GDateTime *dt)
+{
+    date->year = g_date_time_get_year(dt);
+    date->month = g_date_time_get_month(dt);
+    date->day = g_date_time_get_day_of_month(dt);
+    /* GDateTime has 1-7, with 1 = Monday; Glk has 0-6, with 0 = Sunday */
+    date->weekday = g_date_time_get_day_of_week(dt) % G_DATE_SUNDAY;
+    date->hour = g_date_time_get_hour(dt);
+    date->minute = g_date_time_get_minute(dt);
+    date->second = g_date_time_get_second(dt);
+}
+
+/* Copy a glkdate to a GDateTime.
+ This is used in the "glk_date_to_..." functions, which are supposed
+ to normalize the glkdate. We're going to rely on GDateTime to do that.
+ Returns NULL if the date is not supported by GDateTime.
+ Call g_date_time_unref() on the return value when done.
+ */
+static GDateTime *
+date_to_gdatetime(glkdate_t *date, GTimeZone *tz)
+{
+       /* Combining seconds and microseconds into one floating-point number should
+        * take care of normalizing any negative microseconds or microseconds > one
+        * million */
+       double seconds = date->second + (double)date->microsec / G_USEC_PER_SEC;
+       GDateTime *retval = g_date_time_new(tz,
+               date->year,
+               date->month,
+               date->day,
+               date->hour,
+               date->minute,
+               seconds);
+       if( G_UNLIKELY(retval == NULL) ) {
+               if(date->year < 1)
+                       WARNING("Years earlier than 1 C.E. are not currently supported.");
+               else if(date->year > 9999)
+                       WARNING("Years later than 9999 C.E. are not currently supported.");
+               else
+                       WARNING("Date is not supported or not valid.");
+       }
+       return retval;
+}
+
+/* Convert a Unix timestamp (seconds since Jan 1 1970) to a glktimeval,
+ * adding a number of microseconds as well. */
+static void
+unix_time_to_time(gint64 sec, int microsec, glktimeval_t *time)
+{
+       time->high_sec = (sec >> 32) & 0xFFFFFFFF;
+       time->low_sec = sec & 0xFFFFFFFF;
+    time->microsec = microsec;
+}
+
+/* Convert a gint64 microseconds value, as returned by g_get_real_time(),
+ * to a glktimeval. */
+static void
+real_time_to_time(gint64 real_time, glktimeval_t *time)
+{
+       gint64 unix_time = real_time / G_USEC_PER_SEC;
+       int microsec = real_time % G_USEC_PER_SEC;
+       unix_time_to_time(unix_time, microsec, time);
+}
+
+/* Divide a Unix timestamp by a (positive) value. */
+static glsi32
+simplify_time(gint64 timestamp, glui32 factor)
+{
+    /* We want to round towards negative infinity, which takes a little
+        bit of fussing. */
+    if (timestamp >= 0) {
+               return timestamp / (gint64)factor;
+    }
+    else {
+               return -1 - (((gint64)-1 - timestamp) / (gint64)factor);
+    }
+}
+
+/* Convert a glkdate to a glktimeval, in the given time zone. */
+static void
+date_to_time(glkdate_t *date, glktimeval_t *tv, GTimeZone *tz)
+{
+       GDateTime *dt = date_to_gdatetime(date, tz);
+       if(dt == NULL) {
+               tv->high_sec = -1;
+               tv->low_sec = -1;
+               return;
+       }
+       gint64 timestamp = g_date_time_to_unix(dt);
+       int microsec = g_date_time_get_microsecond(dt);
+       g_date_time_unref(dt);
+       unix_time_to_time(timestamp, microsec, tv);
+}
+
+/* Convert a glkdate to a Unix timestamp divided by a value, in the given time
+zone. */
+static glsi32
+date_to_simple_time(glkdate_t *date, glui32 factor, GTimeZone *tz)
+{
+       GDateTime *dt = date_to_gdatetime(date, tz);
+       if(dt == NULL)
+               return -1;
+       gint64 timestamp = g_date_time_to_unix(dt);
+       g_date_time_unref(dt);
+
+       return simplify_time(timestamp, factor);
+}
 
 /**
  * glk_current_time:
@@ -24,6 +136,7 @@ void
 glk_current_time(glktimeval_t *time)
 {
        g_return_if_fail(time != NULL);
+       real_time_to_time(g_get_real_time(), time);
 }
 
 /**
@@ -42,9 +155,10 @@ glk_current_time(glktimeval_t *time)
 glsi32
 glk_current_simple_time(glui32 factor)
 {
-       g_return_val_if_fail(factor != 0, -1);
-       
-       return -1;
+       g_return_val_if_fail(factor != 0, 0);
+
+       gint64 sec = g_get_real_time() / G_USEC_PER_SEC;
+       return simplify_time(sec, factor);
 }
 
 /**
@@ -65,6 +179,16 @@ glk_time_to_date_utc(glktimeval_t *time, glkdate_t *date)
 {
        g_return_if_fail(time != NULL);
        g_return_if_fail(date != NULL);
+
+       time_t timestamp = time->low_sec;
+    if (sizeof(timestamp) > 4) {
+        timestamp += ((gint64)time->high_sec << 32);
+    }
+
+       GDateTime *dt = g_date_time_new_from_unix_utc(timestamp);
+       date_from_gdatetime(date, dt);
+       g_date_time_unref(dt);
+    date->microsec = time->microsec;
 }
 
 /**
@@ -80,6 +204,16 @@ glk_time_to_date_local(glktimeval_t *time, glkdate_t *date)
 {
        g_return_if_fail(time != NULL);
        g_return_if_fail(date != NULL);
+
+       time_t timestamp = time->low_sec;
+    if (sizeof(timestamp) > 4) {
+        timestamp += ((int64_t)time->high_sec << 32);
+    }
+
+       GDateTime *dt = g_date_time_new_from_unix_local(timestamp);
+       date_from_gdatetime(date, dt);
+       g_date_time_unref(dt);
+    date->microsec = time->microsec;
 }
 
 /**
@@ -101,6 +235,13 @@ glk_simple_time_to_date_utc(glsi32 time, glui32 factor, glkdate_t *date)
 {
        g_return_if_fail(factor != 0);
        g_return_if_fail(date != NULL);
+
+       time_t timestamp = (time_t)time * factor;
+
+       GDateTime *dt = g_date_time_new_from_unix_utc(timestamp);
+       date_from_gdatetime(date, dt);
+       g_date_time_unref(dt);
+    date->microsec = 0;
 }
 
 /**
@@ -117,6 +258,13 @@ glk_simple_time_to_date_local(glsi32 time, glui32 factor, glkdate_t *date)
 {
        g_return_if_fail(factor != 0);
        g_return_if_fail(date != NULL);
+
+       time_t timestamp = (time_t)time * factor;
+
+       GDateTime *dt = g_date_time_new_from_unix_local(timestamp);
+       date_from_gdatetime(date, dt);
+       g_date_time_unref(dt);
+    date->microsec = 0;
 }
 
 /**
@@ -131,12 +279,20 @@ glk_simple_time_to_date_local(glsi32 time, glui32 factor, glkdate_t *date)
  * If the time cannot be represented by the platform's time library, this may
  * return -1 for the seconds value. (I.e., the @high_sec and @low_sec fields
  * both $FFFFFFFF. The microseconds field is undefined in this case.)
+ * <note><title>Chimara</title><para>
+ *   Chimara does not currently support years earlier than 1 C.E. or later than
+ *   9999 C.E.
+ * </para></note>
  */
 void
 glk_date_to_time_utc(glkdate_t *date, glktimeval_t *time)
 {
        g_return_if_fail(date != NULL);
        g_return_if_fail(time != NULL);
+
+       GTimeZone *utc = g_time_zone_new_utc();
+       date_to_time(date, time, utc);
+       g_time_zone_unref(utc);
 }
 
 /**
@@ -160,6 +316,10 @@ glk_date_to_time_local(glkdate_t *date, glktimeval_t *time)
 {
        g_return_if_fail(date != NULL);
        g_return_if_fail(time != NULL);
+
+       GTimeZone *local = g_time_zone_new_local();
+       date_to_time(date, time, local);
+       g_time_zone_unref(local);
 }
 
 /**
@@ -173,6 +333,10 @@ glk_date_to_time_local(glkdate_t *date, glktimeval_t *time)
  *
  * If the time cannot be represented by the platform's time library, this may
  * return -1.
+ * <note><title>Chimara</title><para>
+ *   Chimara does not currently support years earlier than 1 C.E. or later than
+ *   9999 C.E.
+ * </para></note>
  *
  * Returns: a timestamp divided by @factor, and truncated to 32 bits, or -1 on
  * error.
@@ -180,10 +344,13 @@ glk_date_to_time_local(glkdate_t *date, glktimeval_t *time)
 glsi32
 glk_date_to_simple_time_utc(glkdate_t *date, glui32 factor)
 {
-       g_return_val_if_fail(date != NULL, -1);
-       g_return_val_if_fail(factor != 0, -1);
-       
-       return -1;
+       g_return_val_if_fail(date != NULL, 0);
+       g_return_val_if_fail(factor != 0, 0);
+
+       GTimeZone *utc = g_time_zone_new_utc();
+       glsi32 retval = date_to_simple_time(date, factor, utc);
+       g_time_zone_unref(utc);
+       return retval;
 }
 
 /**
@@ -200,8 +367,11 @@ glk_date_to_simple_time_utc(glkdate_t *date, glui32 factor)
 glsi32
 glk_date_to_simple_time_local(glkdate_t *date, glui32 factor)
 {
-       g_return_val_if_fail(date != NULL, -1);
-       g_return_val_if_fail(factor != 0, -1);
-       
-       return -1;
+       g_return_val_if_fail(date != NULL, 0);
+       g_return_val_if_fail(factor != 0, 0);
+
+       GTimeZone *local = g_time_zone_new_local();
+       glsi32 retval = date_to_simple_time(date, factor, local);
+       g_time_zone_unref(local);
+       return retval;
 }