+#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);
+}