265 lines
11 KiB
Diff
265 lines
11 KiB
Diff
diff --git a/plugins/dclock.c b/plugins/dclock.c
|
|
index 1a7fe187..2b87a287 100644
|
|
--- a/plugins/dclock.c
|
|
+++ b/plugins/dclock.c
|
|
@@ -169,6 +169,202 @@ static void dclock_timer_set(DClockPlugin * dc, struct timeval *current_time)
|
|
dc->timer = g_timeout_add(milliseconds, (GSourceFunc) dclock_update_display, (gpointer) dc);
|
|
}
|
|
|
|
+/* Erisian (Discordian) calendar support, ported from dtime-js
|
|
+ * (https://gitlab.com/zlax/dtime-js).
|
|
+ * A year has five 73-day seasons plus St. Tib's Day in leap years.
|
|
+ * A day has ten decimal hours, each a hundred decimal minutes of a hundred
|
|
+ * decimal seconds (one decimal second equals 864 christian milliseconds).
|
|
+ */
|
|
+typedef struct {
|
|
+ int yold; /* Erisian year (YOLD) */
|
|
+ int season; /* 0-4 */
|
|
+ int day; /* 1-73 within the season */
|
|
+ int hour; /* 0-9 decimal hour */
|
|
+ int minute; /* 0-99 decimal minute */
|
|
+ int second; /* 0-99 decimal second */
|
|
+ int st_tibs; /* 1 on St. Tib's Day */
|
|
+ char season_name[16]; /* e.g. "Bureaucracy" */
|
|
+ char weekday_name[32]; /* one of five */
|
|
+} DClockErisTime;
|
|
+
|
|
+/* Easter Island winter time offset used by dtime-js, in seconds. */
|
|
+#define DCLOCK_ERIS_TZ_OFFSET (-5 * 60 * 60)
|
|
+
|
|
+static const char *dclock_eris_seasons[5] = {
|
|
+ "Chaos", "Discord", "Confusion", "Bureaucracy", "Aftermath"
|
|
+};
|
|
+
|
|
+static const char *dclock_eris_weekdays[5] = {
|
|
+ "Sweetmorn", "Boomtime", "Pungenday", "Prickle-Prickle", "Setting Orange"
|
|
+};
|
|
+
|
|
+/* Cumulative days before each month (January = index 0); index 12 = total
|
|
+ * days in the year. Turns a Gregorian date into a day-of-year by lookup. */
|
|
+static const unsigned short dclock_eris_cum_common[13] = {
|
|
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
|
|
+};
|
|
+static const unsigned short dclock_eris_cum_leap[13] = {
|
|
+ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366
|
|
+};
|
|
+
|
|
+static int dclock_eris_leap(int year)
|
|
+{
|
|
+ return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
|
|
+}
|
|
+
|
|
+/* Day-of-year (1-based) from a Gregorian date, via the cumulative-day table. */
|
|
+static int dclock_eris_day_of_year(int day, int month, int year)
|
|
+{
|
|
+ const unsigned short * cum = dclock_eris_leap(year)
|
|
+ ? dclock_eris_cum_leap
|
|
+ : dclock_eris_cum_common;
|
|
+ return (int)(cum[month - 1] + day);
|
|
+}
|
|
+
|
|
+/* Cached Erisian "day anchor". The Erisian date depends only on the (offset)
|
|
+ * UTC calendar day, so it changes at most once per day. This structure is
|
|
+ * refreshed only when that day rolls over; between refreshes the decimal time
|
|
+ * is derived cheaply (a subtraction from day_start, no modulo). */
|
|
+typedef struct {
|
|
+ int64_t day_start; /* shifted unix sec of 00:00:00 of the current day */
|
|
+ int64_t next; /* shifted unix sec at which the date changes */
|
|
+ int yold, season, day, st_tibs;
|
|
+ char weekday_name[32];
|
|
+ int valid;
|
|
+} DClockErisDay;
|
|
+
|
|
+static DClockErisDay eris_day = { 0 };
|
|
+
|
|
+static void dclock_eris_now(const struct timeval *now, DClockErisTime *e);
|
|
+static char *dclock_expand_codes(const char *fmt, const DClockErisTime *eris,
|
|
+ long long unix_sec);
|
|
+
|
|
+/* Compute the Erisian date (cached; refreshed only when the shifted UTC day
|
|
+ * changes) and the decimal time (every call) for the current moment. */
|
|
+static void dclock_eris_now(const struct timeval *now, DClockErisTime *e)
|
|
+{
|
|
+ time_t shifted_sec = (time_t)now->tv_sec + DCLOCK_ERIS_TZ_OFFSET;
|
|
+
|
|
+ /* Refresh the cached Erisian date only when the shifted UTC day changes —
|
|
+ * at most once per day. The recompute branch is rare, so mark it unlikely
|
|
+ * to keep the common cache-hit path lean. */
|
|
+ if (!eris_day.valid || __builtin_expect(shifted_sec >= eris_day.next, 0)) {
|
|
+ struct tm utc;
|
|
+ int dom, mon, year, doy, st_tibs;
|
|
+ int season, day, yold, sec_since_midnight;
|
|
+
|
|
+ gmtime_r(&shifted_sec, &utc);
|
|
+ dom = utc.tm_mday;
|
|
+ mon = utc.tm_mon + 1;
|
|
+ year = utc.tm_year + 1900;
|
|
+
|
|
+ doy = dclock_eris_day_of_year(dom, mon, year);
|
|
+ st_tibs = dclock_eris_leap(year) && (doy == 60);
|
|
+ if (dclock_eris_leap(year) && doy > 60)
|
|
+ doy--;
|
|
+ season = doy / 73;
|
|
+ day = doy % 73;
|
|
+ if (day == 0) {
|
|
+ season--;
|
|
+ day = 73;
|
|
+ }
|
|
+ yold = year + 1166;
|
|
+
|
|
+ /* Anchor of the current shifted UTC day (00:00:00) and its end. */
|
|
+ sec_since_midnight = utc.tm_hour * 3600 + utc.tm_min * 60 + utc.tm_sec;
|
|
+ eris_day.day_start = (int64_t)shifted_sec - sec_since_midnight;
|
|
+ eris_day.next = eris_day.day_start + 86400;
|
|
+
|
|
+ eris_day.yold = yold;
|
|
+ eris_day.season = season;
|
|
+ eris_day.day = day;
|
|
+ eris_day.st_tibs = st_tibs;
|
|
+ if (st_tibs)
|
|
+ eris_day.weekday_name[0] = '\0';
|
|
+ else
|
|
+ snprintf(eris_day.weekday_name, sizeof(eris_day.weekday_name),
|
|
+ "%s", dclock_eris_weekdays[(doy - 1) % 5]);
|
|
+ eris_day.valid = 1;
|
|
+ }
|
|
+
|
|
+ e->yold = eris_day.yold;
|
|
+ e->season = eris_day.season;
|
|
+ e->day = eris_day.day;
|
|
+ e->st_tibs = eris_day.st_tibs;
|
|
+ snprintf(e->season_name, sizeof(e->season_name), "%s",
|
|
+ dclock_eris_seasons[eris_day.season]);
|
|
+ memcpy(e->weekday_name, eris_day.weekday_name, sizeof(e->weekday_name));
|
|
+
|
|
+ /* Decimal time: seconds (and ms) into the current day, derived from the
|
|
+ * cached day_start by subtraction (no modulo). ds = decimal-second 0-99999. */
|
|
+ int64_t secs_into_day = (int64_t)shifted_sec - eris_day.day_start;
|
|
+ int64_t ms_into_day = secs_into_day * 1000 + (int64_t)(now->tv_usec / 1000);
|
|
+ int64_t ds = ms_into_day / 864;
|
|
+ e->hour = (int)(ds / 10000);
|
|
+ e->minute = (int)((ds % 10000) / 100);
|
|
+ e->second = (int)(ds % 100);
|
|
+}
|
|
+
|
|
+/* Expand the non-strftime codes (%e* and %ut) in a format string, leaving the
|
|
+ * remaining % codes for strftime(). Returns a newly allocated string. */
|
|
+static char *dclock_expand_codes(const char *fmt, const DClockErisTime *eris,
|
|
+ long long unix_sec)
|
|
+{
|
|
+ GString *s = g_string_new(NULL);
|
|
+ const char *p;
|
|
+
|
|
+ if (fmt == NULL)
|
|
+ return g_string_free(s, FALSE);
|
|
+
|
|
+ for (p = fmt; *p; ) {
|
|
+ int advanced = 0;
|
|
+ if (*p == '%') {
|
|
+ if (p[1] == '%') {
|
|
+ g_string_append(s, "%%"); /* literal % — leave for strftime() */
|
|
+ advanced = 2;
|
|
+ }
|
|
+ else if (p[1] == 'e') {
|
|
+ switch (p[2]) {
|
|
+ case 'Y': g_string_append_printf(s, "%d", eris->yold); advanced = 3; break;
|
|
+ case 'y': g_string_append_printf(s, "%d", eris->yold - 3000); advanced = 3; break;
|
|
+ case 'S': g_string_append(s, eris->season_name); advanced = 3; break;
|
|
+ case 'E': g_string_append_printf(s, "%d", eris->season + 1); advanced = 3; break;
|
|
+ case 'd':
|
|
+ if (eris->st_tibs)
|
|
+ g_string_append(s, "tib");
|
|
+ else
|
|
+ g_string_append_printf(s, "%d", eris->day);
|
|
+ advanced = 3;
|
|
+ break;
|
|
+ case 'h': g_string_append_printf(s, "%d", eris->hour); advanced = 3; break;
|
|
+ case 'm': g_string_append_printf(s, "%02d", eris->minute); advanced = 3; break;
|
|
+ case 's': g_string_append_printf(s, "%02d", eris->second); advanced = 3; break;
|
|
+ case 'w':
|
|
+ if (eris->st_tibs)
|
|
+ g_string_append(s, "St. Tib's Day");
|
|
+ else
|
|
+ g_string_append(s, eris->weekday_name);
|
|
+ advanced = 3;
|
|
+ break;
|
|
+ default: break;
|
|
+ }
|
|
+ }
|
|
+ else if (p[1] == 'u' && p[2] == 't') {
|
|
+ g_string_append_printf(s, "%lld", unix_sec);
|
|
+ advanced = 3;
|
|
+ }
|
|
+ }
|
|
+ if (advanced) {
|
|
+ p += advanced;
|
|
+ continue;
|
|
+ }
|
|
+ g_string_append_c(s, *p);
|
|
+ p++;
|
|
+ }
|
|
+
|
|
+ return g_string_free(s, FALSE);
|
|
+}
|
|
+
|
|
/* Periodic timer callback.
|
|
* Also used during initialization and configuration change to do a redraw. */
|
|
static gboolean dclock_update_display(DClockPlugin * dc)
|
|
@@ -183,15 +379,24 @@ static gboolean dclock_update_display(DClockPlugin * dc)
|
|
dclock_timer_set(dc, &now);
|
|
current_time = localtime(&now.tv_sec);
|
|
|
|
- /* Determine the content of the clock label and tooltip. */
|
|
- char clock_value[64];
|
|
- char tooltip_value[64];
|
|
+ /* Determine the content of the clock label and tooltip.
|
|
+ * Compute the Erisian date/time once (cached date, cheap decimal time), then
|
|
+ * expand the Erisian/unix codes in both formats before strftime. */
|
|
+ DClockErisTime eris;
|
|
+ dclock_eris_now(&now, &eris);
|
|
+ char * expanded_clock = dclock_expand_codes(dc->clock_format, &eris, (long long)now.tv_sec);
|
|
+ char * expanded_tooltip = dclock_expand_codes(dc->tooltip_format, &eris, (long long)now.tv_sec);
|
|
+
|
|
+ char clock_value[128];
|
|
+ char tooltip_value[128];
|
|
clock_value[0] = '\0';
|
|
if (dc->clock_format != NULL)
|
|
- strftime(clock_value, sizeof(clock_value), dc->clock_format, current_time);
|
|
+ strftime(clock_value, sizeof(clock_value), expanded_clock, current_time);
|
|
tooltip_value[0] = '\0';
|
|
if (dc->tooltip_format != NULL)
|
|
- strftime(tooltip_value, sizeof(tooltip_value), dc->tooltip_format, current_time);
|
|
+ strftime(tooltip_value, sizeof(tooltip_value), expanded_tooltip, current_time);
|
|
+ g_free(expanded_clock);
|
|
+ g_free(expanded_tooltip);
|
|
|
|
/* When we write the clock value, it causes the panel to do a full relayout.
|
|
* Since this function may be called too often while the timing experiment is underway,
|
|
@@ -435,6 +640,9 @@ static GtkWidget *dclock_configure(LXPanel *panel, GtkWidget *p)
|
|
_("Clock Format"), &dc->clock_format, CONF_TYPE_STR,
|
|
_("Tooltip Format"), &dc->tooltip_format, CONF_TYPE_STR,
|
|
_("Format codes: man 3 strftime; %n for line break"), NULL, CONF_TYPE_TRIM,
|
|
+ _("Erisian/unix: %eY year %ey short-year %eS season %eE season# %ed day "
|
|
+ "%eh:%em:%es hour:min:sec %ew weekday (St Tib's day: %ed=tib, %ew=St. Tib's Day) "
|
|
+ "%ut unix time"), NULL, CONF_TYPE_TRIM,
|
|
_("Action when clicked (default: display calendar)"), &dc->action, CONF_TYPE_STR,
|
|
_("Bold font"), &dc->bold, CONF_TYPE_BOOL,
|
|
_("Tooltip only"), &dc->icon_only, CONF_TYPE_BOOL,
|
|
diff --git a/po/lxpanel.pot b/po/lxpanel.pot
|
|
index dc6b1b07..e6e86067 100644
|
|
--- a/po/lxpanel.pot
|
|
+++ b/po/lxpanel.pot
|
|
@@ -716,6 +716,12 @@ msgstr ""
|
|
msgid "Format codes: man 3 strftime; %n for line break"
|
|
msgstr ""
|
|
|
|
+#: ../plugins/dclock.c
|
|
+msgid "Erisian/unix: %eY year %ey short-year %eS season %eE season# %ed day "
|
|
+"%eh:%em:%es hour:min:sec %ew weekday (St Tib's day: %ed=tib, %ew=St. Tib's Day) "
|
|
+"%ut unix time"
|
|
+msgstr ""
|
|
+
|
|
#: ../plugins/dclock.c:438
|
|
msgid "Action when clicked (default: display calendar)"
|
|
msgstr ""
|