Vuo  2.3.2
VuoTime.c
Go to the documentation of this file.
1 
10 #include <sys/time.h>
11 #include <xlocale.h>
12 #include <langinfo.h>
13 
14 #include "VuoMacOSSDKWorkaround.h"
15 #include <CoreFoundation/CoreFoundation.h>
16 
17 #include "type.h"
18 #include "VuoRoundingMethod.h"
19 #include "VuoTime.h"
20 #include "VuoList_VuoTime.h"
21 
23 #ifdef VUO_COMPILER
25  "title" : "Date-Time",
26  "description" : "A date and time.",
27  "keywords" : [ ],
28  "version" : "1.0.0",
29  "dependencies" : [
30  "VuoText",
31  "VuoTimeUnit",
32  "VuoList_VuoTime"
33  ]
34  });
35 #endif
37 
44 {
45  return json_object_get_double(js);
46 }
47 
52 {
53  return json_object_new_double(value);
54 }
55 
59 char *VuoTime_getSummary(const VuoTime value)
60 {
61  VuoInteger year;
62  VuoInteger month;
63  VuoInteger dayOfMonth;
64  VuoInteger hour;
65  VuoInteger minute;
66  VuoReal second;
67  bool ret = VuoTime_getComponents(value, &year, NULL, &month, &dayOfMonth, NULL, NULL, &hour, &minute, &second);
68  if (ret)
69  return VuoText_format("%04lld-%02lld-%02lld %02lld:%02lld:%05.02f", year, month, dayOfMonth, hour, minute, second);
70  else
71  return strdup("Unknown");
72 }
73 
77 bool VuoTime_areEqual(const VuoTime valueA, const VuoTime valueB)
78 {
79  return valueA == valueB;
80 }
81 
85 bool VuoTime_isLessThan(const VuoTime valueA, const VuoTime valueB)
86 {
87  return valueA < valueB;
88 }
89 
94 {
95  VuoTime minimumTime = INFINITY;
96  int timeCount = VuoListGetCount_VuoTime(times);
97  for (int i = 1; i <= timeCount; ++i)
98  {
99  VuoTime t = VuoListGetValue_VuoTime(times, i);
100  if (isnan(t))
101  return false;
102  if (t < minimumTime)
103  minimumTime = t;
104  }
105 
106  VuoTime toleranceInSeconds = tolerance * VuoTimeUnit_getSeconds(toleranceUnit);
107  for (int i = 1; i <= timeCount; ++i)
108  {
109  VuoTime t = VuoListGetValue_VuoTime(times, i);
110  if (fabs(t - minimumTime) > toleranceInSeconds)
111  return false;
112  }
113 
114  return true;
115 }
116 
121 {
122  return fmod(time, 86400);
123 }
124 
134 {
135  VuoTime minimumTime = INFINITY;
136  int timeCount = VuoListGetCount_VuoTime(times);
137  for (int i = 1; i <= timeCount; ++i)
138  {
140  if (isnan(t))
141  return false;
142  if (t < minimumTime)
143  minimumTime = t;
144  }
145 
146  VuoTime toleranceInSeconds = tolerance * VuoTimeUnit_getSeconds(toleranceUnit);
147  for (int i = 1; i <= timeCount; ++i)
148  {
150  if (fabs(t - minimumTime) > toleranceInSeconds
151  && fabs(t - 86400 - minimumTime) > toleranceInSeconds)
152  return false;
153  }
154 
155  return true;
156 }
157 
164 bool VuoTime_isTimeOfDayLessThan(const VuoTime valueA, const VuoTime valueB, const VuoTime startOfDay)
165 {
166  VuoTime timeOnlyA = VuoTime_removeDate(valueA);
167  VuoTime timeOnlyB = VuoTime_removeDate(valueB);
168  VuoTime timeOnlyStart = VuoTime_removeDate(startOfDay);
169 
170  timeOnlyA = fmod(86400 + timeOnlyA - timeOnlyStart, 86400);
171  timeOnlyB = fmod(86400 + timeOnlyB - timeOnlyStart, 86400);
172 
173  return timeOnlyA < timeOnlyB;
174 }
175 
177 const int VuoUnixTimeOffset = (31 /* 2001-1970 */ * 365 + 8 /* leap days between 1970 and 2001 */) * 24 * 60 * 60;
178 
185 {
186  struct timeval now;
187  gettimeofday(&now, NULL);
188  return (now.tv_sec - VuoUnixTimeOffset) + now.tv_usec / 1000000.;
189 }
190 
195 {
196  return (year % 4 == 0)
197  && !((year % 100 == 0)
198  && (year % 400 != 0));
199 }
200 
206 VuoTime VuoTime_make(VuoInteger year, VuoInteger month, VuoInteger dayOfMonth, VuoInteger hour, VuoInteger minute, VuoReal second)
207 {
208  struct tm tm;
209  tm.tm_year = year - 1900;
210  tm.tm_mon = month - 1;
211  tm.tm_mday = dayOfMonth;
212  tm.tm_hour = hour;
213  tm.tm_min = minute;
214  tm.tm_sec = 0;
215  tm.tm_isdst = -1; // autodetect
216  time_t timeInt = mktime(&tm);
217  if (timeInt == -1)
218  {
219  // mktime() failed, so let's try to make it from scratch.
220 
221  // Find the number of leaps between the specified year and 1970 (UNIX epoch).
222  int leaps = 0;
223  for (int y = year; y < 1970; (year<1970 ? ++y : --y))
224  if (VuoTime_isLeapYear(y))
225  ++leaps;
226 
227  // For non-leap years.
228  char daysPerMonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
229 
230  int dayOfYear = 0;
231  for (int m = 0; m < month-1; ++m)
232  dayOfYear += daysPerMonth[m];
233  dayOfYear += dayOfMonth;
234 
235  if (VuoTime_isLeapYear(year)
236  && dayOfYear > 31 + 28)
237  --leaps;
238 
239  timeInt = (
240  (year - 1970)*365
241  + dayOfYear - 1
242  - leaps
243  ) * 60 * 60 * 24
244  + hour * 60 * 60
245  + minute * 60;
246 
247  time_t t = time(NULL);
248  struct tm lt = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL};
249  localtime_r(&t, &lt);
250  timeInt -= lt.tm_gmtoff;
251  }
252 
253  return (timeInt - VuoUnixTimeOffset) + second;
254 }
255 
259 static char * VuoTime_stringForFormat(VuoTimeFormat format, locale_t locale)
260 {
261  const char *dateFormatString = nl_langinfo_l(D_FMT, locale);
262  const char *time12FormatString = nl_langinfo_l(T_FMT_AMPM, locale);
263  const char *time24FormatString = nl_langinfo_l(T_FMT, locale);
264 
265  if (format == VuoTimeFormat_DateTimeShort12)
266  return VuoText_format("%s %s", dateFormatString, time12FormatString);
267  else if (format == VuoTimeFormat_DateTimeShort24)
268  return VuoText_format("%s %s", dateFormatString, time24FormatString);
269  else if (format == VuoTimeFormat_DateTimeMedium12)
270  return VuoText_format("%%b %%e, %%Y %s", time12FormatString);
271  else if (format == VuoTimeFormat_DateTimeMedium24)
272  return VuoText_format("%%b %%e, %%Y %s", time24FormatString);
273  else if (format == VuoTimeFormat_DateTimeLong12)
274  return VuoText_format("%%A, %%B %%e, %%Y %s", time12FormatString);
275  else if (format == VuoTimeFormat_DateTimeLong24)
276  return VuoText_format("%%A, %%B %%e, %%Y %s", time24FormatString);
277  else if (format == VuoTimeFormat_DateTimeSortable)
278  return strdup("%Y-%m-%d %H:%M:%SZ");
279  else if (format == VuoTimeFormat_DateShort)
280  return strdup(dateFormatString);
281  else if (format == VuoTimeFormat_DateMedium)
282  return strdup("%b %e, %Y");
283  else if (format == VuoTimeFormat_DateLong)
284  return strdup("%A, %B %e, %Y");
285  else if (format == VuoTimeFormat_Time12)
286  return strdup(time12FormatString);
287  else if (format == VuoTimeFormat_Time24)
288  return strdup(time24FormatString);
289  else
290  return NULL;
291 }
292 
300 static VuoTime VuoTime_makeFromFormats(const char *str, const char **formats, int numFormats)
301 {
302  if (VuoText_isEmpty(str))
303  return NAN;
304 
305  for (int i = 0; i < numFormats; ++i)
306  {
307  struct tm tm;
308  // Initialize tm, since strptime adds to (rather than replacing) tm's initial values (!!).
309  // "If time relative to today is desired, initialize the tm structure with today's date before passing it to strptime()."
310  bzero(&tm, sizeof(struct tm));
311 
312  char *ret = strptime(str, formats[i], &tm);
313  if (ret)
314  {
315  for ( ; *ret != 0 && isspace(*ret); ++ret) {}
316  if (*ret != 0)
317  continue;
318 
319  // If the format has a time and no date, set a year so `mktime` can calculate seconds from the Epoch.
320  if (tm.tm_year == 0)
321  tm.tm_year = 100;
322 
323  time_t timeInt = mktime(&tm);
324  return timeInt - VuoUnixTimeOffset;
325  }
326  }
327 
328  return NAN;
329 }
330 
336 VuoTime VuoTime_makeFromRFC822(const char *rfc822)
337 {
338  const int numFormats = 2;
339  const char *formats[numFormats] = {
340  "%a, %d %b %Y %T %z",
341  "%a, %d %b %Y %R %Z" // `Tue, 12 Jan 2016 11:33 EST` (backup format, seen in https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss)
342  };
343 
344  return VuoTime_makeFromFormats(rfc822, formats, numFormats);
345 }
346 
353 VuoTime VuoTime_makeFromISO8601(const char *iso8601)
354 {
355  const int numFormats = 4;
356  const char *formats[numFormats] = {
357  "%Y-%m-%dT%H:%M:%SZ",
358  "%Y-%m-%d %H:%M:%SZ",
359  "%Y-%m-%dT%H:%M:%S+00:00",
360  "%Y-%m-%d %H:%M:%S+00:00"
361  };
362 
363  return VuoTime_makeFromFormats(iso8601, formats, numFormats);
364 }
365 
369 static char *VuoTime_changeTo2DigitYear(char *format)
370 {
371  for (int i = 0; format[i] != 0; ++i)
372  if (format[i] == 'Y')
373  format[i] = 'y';
374  return format;
375 }
376 
383 {
384  CFLocaleRef localeCF = CFLocaleCopyCurrent();
385  VuoText localeID = VuoText_makeFromCFString(CFLocaleGetIdentifier(localeCF));
386  VuoRetain(localeID);
387  CFRelease(localeCF);
388  locale_t locale = newlocale(LC_ALL_MASK, localeID, NULL);
389 
390  const int numFormats = 15;
391  char *formats[numFormats] = {
392  VuoTime_stringForFormat(VuoTimeFormat_DateTimeSortable, locale),
393  VuoTime_changeTo2DigitYear(VuoTime_stringForFormat(VuoTimeFormat_DateTimeShort12, locale)),
394  VuoTime_stringForFormat(VuoTimeFormat_DateTimeShort12, locale),
395  VuoTime_changeTo2DigitYear(VuoTime_stringForFormat(VuoTimeFormat_DateTimeShort24, locale)),
396  VuoTime_stringForFormat(VuoTimeFormat_DateTimeShort24, locale),
397  VuoTime_stringForFormat(VuoTimeFormat_DateTimeMedium12, locale),
398  VuoTime_stringForFormat(VuoTimeFormat_DateTimeMedium24, locale),
399  VuoTime_stringForFormat(VuoTimeFormat_DateTimeLong12, locale),
400  VuoTime_stringForFormat(VuoTimeFormat_DateTimeLong24, locale),
401  VuoTime_changeTo2DigitYear(VuoTime_stringForFormat(VuoTimeFormat_DateShort, locale)),
402  VuoTime_stringForFormat(VuoTimeFormat_DateShort, locale),
403  VuoTime_stringForFormat(VuoTimeFormat_DateMedium, locale),
404  VuoTime_stringForFormat(VuoTimeFormat_DateLong, locale),
405  VuoTime_stringForFormat(VuoTimeFormat_Time12, locale),
406  VuoTime_stringForFormat(VuoTimeFormat_Time24, locale)
407  };
408 
409  VuoTime time = VuoTime_makeFromFormats(str, (const char **)formats, numFormats);
410 
411  VuoRelease(localeID);
412  freelocale(locale);
413  for (int i = 0; i < numFormats; ++i)
414  free(formats[i]);
415 
416  return time;
417 }
418 
436 bool VuoTime_getComponents(VuoTime time, VuoInteger *year, VuoInteger *dayOfYear, VuoInteger *month, VuoInteger *dayOfMonth, VuoInteger *week, VuoWeekday *dayOfWeek, VuoInteger *hour, VuoInteger *minute, VuoReal *second)
437 {
438  time_t unixtime = time + VuoUnixTimeOffset;
439  struct tm *tm = localtime(&unixtime);
440  if (!tm)
441  return false;
442  if (year)
443  *year = tm->tm_year + 1900;
444  if (dayOfYear)
445  *dayOfYear = tm->tm_yday + 1;
446  if (month)
447  *month = tm->tm_mon + 1;
448  if (dayOfMonth)
449  *dayOfMonth = tm->tm_mday;
450  if (week)
451  {
452  char s[3];
453  strftime(s, 3, "%V", tm);
454  *week = atoi(s);
455  }
456  if (dayOfWeek)
457  *dayOfWeek = tm->tm_wday;
458  if (hour)
459  *hour = tm->tm_hour;
460  if (minute)
461  *minute = tm->tm_min;
462  if (second)
463  {
464  if (time >= 0)
465  *second = fmod(time, 60);
466  else
467  *second = fmod(60 + fmod(time, 60), 60);
468  }
469 
470  return true;
471 }
472 
476 VuoTime VuoTime_round(const VuoTime value, const VuoTimeUnit unit, const int roundingMethod)
477 {
478  VuoInteger year;
479  VuoInteger month;
480  VuoInteger dayOfMonth;
481  VuoWeekday dayOfWeek;
482  VuoInteger hour;
483  VuoInteger minute;
484  VuoReal second;
485  bool ret = VuoTime_getComponents(value, &year, NULL, &month, &dayOfMonth, NULL, &dayOfWeek, &hour, &minute, &second);
486  if (!ret)
487  return nan(NULL);
488 
489  if (unit == VuoTimeUnit_Millennium)
490  {
491  if (roundingMethod == VuoRoundingMethod_Nearest)
492  return VuoTime_make(1000*(year/1000) + (year%1000 > 500 ? 1000 : 0), 1, 1, 0, 0, 0);
493  else if (roundingMethod == VuoRoundingMethod_Down)
494  return VuoTime_make(1000*(year/1000), 1, 1, 0, 0, 0);
495  else if (roundingMethod == VuoRoundingMethod_Up)
496  return VuoTime_make(1000*(year/1000) + (year%1000 + month + dayOfMonth + hour + minute + second > 0 ? 1000 : 0), 1, 1, 0, 0, 0);
497  }
498  else if (unit == VuoTimeUnit_Century)
499  {
500  if (roundingMethod == VuoRoundingMethod_Nearest)
501  return VuoTime_make(100*(year/100) + (year%100 > 50 ? 100 : 0), 1, 1, 0, 0, 0);
502  else if (roundingMethod == VuoRoundingMethod_Down)
503  return VuoTime_make(100*(year/100), 1, 1, 0, 0, 0);
504  else if (roundingMethod == VuoRoundingMethod_Up)
505  return VuoTime_make(100*(year/100) + (year%100 + month + dayOfMonth + hour + minute + second > 0 ? 100 : 0), 1, 1, 0, 0, 0);
506  }
507  else if (unit == VuoTimeUnit_Decade)
508  {
509  if (roundingMethod == VuoRoundingMethod_Nearest)
510  return VuoTime_make(10*(year/10) + (year%10 > 5 ? 10 : 0), 1, 1, 0, 0, 0);
511  else if (roundingMethod == VuoRoundingMethod_Down)
512  return VuoTime_make(10*(year/10), 1, 1, 0, 0, 0);
513  else if (roundingMethod == VuoRoundingMethod_Up)
514  return VuoTime_make(10*(year/10) + (year%10 + month + dayOfMonth + hour + minute + second > 0 ? 10 : 0), 1, 1, 0, 0, 0);
515  }
516  else if (unit == VuoTimeUnit_Year)
517  {
518  if (roundingMethod == VuoRoundingMethod_Nearest)
519  return VuoTime_make(year + (month > 6 ? 1 : 0), 1, 1, 0, 0, 0);
520  else if (roundingMethod == VuoRoundingMethod_Down)
521  return VuoTime_make(year, 1, 1, 0, 0, 0);
522  else if (roundingMethod == VuoRoundingMethod_Up)
523  return VuoTime_make(year + (month + dayOfMonth + hour + minute + second > 0 ? 1 : 0), 1, 1, 0, 0, 0);
524  }
525  else if (unit == VuoTimeUnit_Quarter)
526  {
527  if (roundingMethod == VuoRoundingMethod_Nearest)
528  return VuoTime_make(year, 3*((month-1)/3) + 1 + ((month-1)%3 > 1 ? 3 : 0), 1, 0, 0, 0);
529  else if (roundingMethod == VuoRoundingMethod_Down)
530  return VuoTime_make(year, 3*((month-1)/3) + 1, 1, 0, 0, 0);
531  else if (roundingMethod == VuoRoundingMethod_Up)
532  return VuoTime_make(year, 3*((month-1)/3) + 1 + ((month-1)%3 + dayOfMonth + hour + minute + second > 0 ? 3 : 0), 1, 0, 0, 0);
533  }
534  else if (unit == VuoTimeUnit_Month)
535  {
536  if (roundingMethod == VuoRoundingMethod_Nearest)
537  return VuoTime_make(year, month + (dayOfMonth > 15 ? 1 : 0), 1, 0, 0, 0);
538  else if (roundingMethod == VuoRoundingMethod_Down)
539  return VuoTime_make(year, month, 1, 0, 0, 0);
540  else if (roundingMethod == VuoRoundingMethod_Up)
541  return VuoTime_make(year, month + (dayOfMonth + hour + minute + second > 0 ? 1 : 0), 1, 0, 0, 0);
542  }
543  else if (unit == VuoTimeUnit_WeekSunday)
544  {
545  if (roundingMethod == VuoRoundingMethod_Nearest)
546  return VuoTime_make(year, month, dayOfMonth + (dayOfWeek > VuoWeekday_Wednesday ? 7-dayOfWeek : -dayOfWeek), 0, 0, 0);
547  else if (roundingMethod == VuoRoundingMethod_Down)
548  return VuoTime_make(year, month, dayOfMonth - dayOfWeek, 0, 0, 0);
549  else if (roundingMethod == VuoRoundingMethod_Up)
550  return VuoTime_make(year, month, dayOfMonth + (dayOfWeek + hour + minute + second > 0 ? 7-dayOfWeek : 0), 0, 0, 0);
551  }
552  else if (unit == VuoTimeUnit_WeekMonday)
553  {
554  if (dayOfWeek == VuoWeekday_Sunday)
555  dayOfWeek += 7;
556 
557  if (roundingMethod == VuoRoundingMethod_Nearest)
558  return VuoTime_make(year, month, dayOfMonth + (dayOfWeek > VuoWeekday_Thursday ? 8-dayOfWeek : 1-dayOfWeek), 0, 0, 0);
559  else if (roundingMethod == VuoRoundingMethod_Down)
560  return VuoTime_make(year, month, dayOfMonth - dayOfWeek + 1, 0, 0, 0);
561  else if (roundingMethod == VuoRoundingMethod_Up)
562  return VuoTime_make(year, month, dayOfMonth + (dayOfWeek-1 + hour + minute + second > 0 ? 8-dayOfWeek : 0), 0, 0, 0);
563  }
564  else if (unit == VuoTimeUnit_Day)
565  {
566  if (roundingMethod == VuoRoundingMethod_Nearest)
567  return VuoTime_make(year, month, dayOfMonth + (hour > 12 ? 1 : 0), 0, 0, 0);
568  else if (roundingMethod == VuoRoundingMethod_Down)
569  return VuoTime_make(year, month, dayOfMonth, 0, 0, 0);
570  else if (roundingMethod == VuoRoundingMethod_Up)
571  return VuoTime_make(year, month, dayOfMonth + (hour + minute + second > 0 ? 1 : 0), 0, 0, 0);
572  }
573  else if (unit == VuoTimeUnit_Hour)
574  {
575  if (roundingMethod == VuoRoundingMethod_Nearest)
576  return VuoTime_make(year, month, dayOfMonth, hour + (minute > 30 ? 1 : 0), 0, 0);
577  else if (roundingMethod == VuoRoundingMethod_Down)
578  return VuoTime_make(year, month, dayOfMonth, hour, 0, 0);
579  else if (roundingMethod == VuoRoundingMethod_Up)
580  return VuoTime_make(year, month, dayOfMonth, hour + (minute + second > 0 ? 1 : 0), 0, 0);
581  }
582  else if (unit == VuoTimeUnit_HalfHour)
583  {
584  if (roundingMethod == VuoRoundingMethod_Nearest)
585  return VuoTime_make(year, month, dayOfMonth, hour, 30*(minute/30) + (minute%30 > 15 ? 30 : 0), 0);
586  else if (roundingMethod == VuoRoundingMethod_Down)
587  return VuoTime_make(year, month, dayOfMonth, hour, 30*(minute/30), 0);
588  else if (roundingMethod == VuoRoundingMethod_Up)
589  return VuoTime_make(year, month, dayOfMonth, hour, 30*(minute/30) + (minute%30 + second > 0 ? 30 : 0), 0);
590  }
591  else if (unit == VuoTimeUnit_QuarterHour)
592  {
593  if (roundingMethod == VuoRoundingMethod_Nearest)
594  return VuoTime_make(year, month, dayOfMonth, hour, 15*(minute/15) + (minute%15 > 7 ? 15 : 0), 0);
595  else if (roundingMethod == VuoRoundingMethod_Down)
596  return VuoTime_make(year, month, dayOfMonth, hour, 15*(minute/15), 0);
597  else if (roundingMethod == VuoRoundingMethod_Up)
598  return VuoTime_make(year, month, dayOfMonth, hour, 15*(minute/15) + (minute%15 + second > 0 ? 15 : 0), 0);
599  }
600  else if (unit == VuoTimeUnit_Minute)
601  {
602  if (roundingMethod == VuoRoundingMethod_Nearest)
603  return VuoTime_make(year, month, dayOfMonth, hour, minute + (second > 30 ? 1 : 0), 0);
604  else if (roundingMethod == VuoRoundingMethod_Down)
605  return VuoTime_make(year, month, dayOfMonth, hour, minute, 0);
606  else if (roundingMethod == VuoRoundingMethod_Up)
607  return VuoTime_make(year, month, dayOfMonth, hour, minute + (second > 0 ? 1 : 0), 0);
608  }
609  else if (unit == VuoTimeUnit_Second)
610  {
611  if (roundingMethod == VuoRoundingMethod_Nearest)
612  return lround(value);
613  else if (roundingMethod == VuoRoundingMethod_Down)
614  return floor(value);
615  else if (roundingMethod == VuoRoundingMethod_Up)
616  return ceil(value);
617  }
618 
619  return value;
620 }
621 
629 VuoText VuoTime_format(const VuoTime time, const VuoTimeFormat format)
630 {
631  CFLocaleRef localeCF = CFLocaleCopyCurrent();
632  VuoText localeID = VuoText_makeFromCFString(CFLocaleGetIdentifier(localeCF));
633  VuoRetain(localeID);
634  CFRelease(localeCF);
635 
636  VuoText formattedTime = VuoTime_formatWithLocale(time, format, localeID);
637 
638  VuoRelease(localeID);
639 
640  return formattedTime;
641 }
642 
650 VuoText VuoTime_formatWithLocale(const VuoTime time, const VuoTimeFormat format, const VuoText localeIdentifier)
651 {
652  VuoText output = NULL;
653 
654  locale_t locale = newlocale(LC_ALL_MASK, localeIdentifier, NULL);
655  static dispatch_once_t loggedLocale = 0;
656  dispatch_once(&loggedLocale, ^{
657  VUserLog("macOS Locale: %s", localeIdentifier);
658  VUserLog(" C Locale: %s", querylocale(LC_ALL_MASK, locale));
659  });
660 
661  char *formatString = VuoTime_stringForFormat(format, locale);
662  if (! formatString)
663  goto done;
664 
665  time_t t = time + VuoUnixTimeOffset;
666  struct tm *tm;
667  if (format == VuoTimeFormat_DateTimeSortable)
668  tm = gmtime(&t);
669  else
670  tm = localtime(&t);
671  if (!tm)
672  goto done;
673 
674  char formattedTime[1024];
675  strftime_l(formattedTime, 1024, formatString, tm, locale);
676 
677  output = VuoText_make(formattedTime);
678 
679 done:
680  free(formatString);
681  freelocale(locale);
682 
683  return output;
684 }