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