Vuo 2.4.2
Loading...
Searching...
No Matches
VuoTime.c
Go to the documentation of this file.
1
10#include <sys/time.h>
11#include <xlocale.h>
12#include <langinfo.h>
13
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
59char *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
77bool VuoTime_areEqual(const VuoTime valueA, const VuoTime valueB)
78{
79 return valueA == valueB;
80}
81
85bool 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
164bool 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
177const 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
211VuoTime VuoTime_make(VuoInteger year, VuoInteger month, VuoInteger dayOfMonth, VuoInteger hour, VuoInteger minute, VuoReal second)
212{
213 struct tm tm;
214 tm.tm_year = year - 1900;
215 tm.tm_mon = month - 1;
216 tm.tm_mday = dayOfMonth;
217 tm.tm_hour = hour;
218 tm.tm_min = minute;
219 tm.tm_sec = 0;
220 tm.tm_isdst = -1; // autodetect
221 time_t timeInt = mktime(&tm);
222 if (timeInt == -1)
223 {
224 // mktime() failed, so let's try to make it from scratch.
225
226 // Find the number of leaps between the specified year and 1970 (UNIX epoch).
227 int leaps = 0;
228 for (int y = year; y < 1970; (year<1970 ? ++y : --y))
229 if (VuoTime_isLeapYear(y))
230 ++leaps;
231
232 // For non-leap years.
233 char daysPerMonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
234
235 int wrappedMonth = VuoInteger_wrap(month, 1, 12);
236 int dayOfYear = 0;
237 for (int m = 0; m < wrappedMonth - 1; ++m)
238 dayOfYear += daysPerMonth[m];
239 dayOfYear += dayOfMonth;
240
241 if (VuoTime_isLeapYear(year)
242 && dayOfYear > 31 + 28)
243 --leaps;
244
245 timeInt = (
246 (year - 1970)*365
247 + dayOfYear - 1
248 - leaps
249 ) * 60 * 60 * 24
250 + hour * 60 * 60
251 + minute * 60;
252
253 time_t t = time(NULL);
254 struct tm lt = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL};
255 localtime_r(&t, &lt);
256 timeInt -= lt.tm_gmtoff;
257 }
258
259 return (timeInt - VuoUnixTimeOffset) + second;
260}
261
265static char * VuoTime_stringForFormat(VuoTimeFormat format, locale_t locale)
266{
267 const char *dateFormatString = nl_langinfo_l(D_FMT, locale);
268 const char *time12FormatString = nl_langinfo_l(T_FMT_AMPM, locale);
269 const char *time24FormatString = nl_langinfo_l(T_FMT, locale);
270
271 if (format == VuoTimeFormat_DateTimeShort12)
272 return VuoText_format("%s %s", dateFormatString, time12FormatString);
273 else if (format == VuoTimeFormat_DateTimeShort24)
274 return VuoText_format("%s %s", dateFormatString, time24FormatString);
275 else if (format == VuoTimeFormat_DateTimeMedium12)
276 return VuoText_format("%%b %%e, %%Y %s", time12FormatString);
277 else if (format == VuoTimeFormat_DateTimeMedium24)
278 return VuoText_format("%%b %%e, %%Y %s", time24FormatString);
279 else if (format == VuoTimeFormat_DateTimeLong12)
280 return VuoText_format("%%A, %%B %%e, %%Y %s", time12FormatString);
281 else if (format == VuoTimeFormat_DateTimeLong24)
282 return VuoText_format("%%A, %%B %%e, %%Y %s", time24FormatString);
283 else if (format == VuoTimeFormat_DateTimeSortable)
284 return strdup("%Y-%m-%d %H:%M:%SZ");
285 else if (format == VuoTimeFormat_DateShort)
286 return strdup(dateFormatString);
287 else if (format == VuoTimeFormat_DateMedium)
288 return strdup("%b %e, %Y");
289 else if (format == VuoTimeFormat_DateLong)
290 return strdup("%A, %B %e, %Y");
291 else if (format == VuoTimeFormat_Time12)
292 return strdup(time12FormatString);
293 else if (format == VuoTimeFormat_Time24)
294 return strdup(time24FormatString);
295 else
296 return NULL;
297}
298
306static VuoTime VuoTime_makeFromFormats(const char *str, const char **formats, int numFormats)
307{
308 if (VuoText_isEmpty(str))
309 return NAN;
310
311 for (int i = 0; i < numFormats; ++i)
312 {
313 struct tm tm;
314 // Initialize tm, since strptime adds to (rather than replacing) tm's initial values (!!).
315 // "If time relative to today is desired, initialize the tm structure with today's date before passing it to strptime()."
316 bzero(&tm, sizeof(struct tm));
317
318 char *ret = strptime(str, formats[i], &tm);
319 if (ret)
320 {
321 for ( ; *ret != 0 && isspace(*ret); ++ret) {}
322 if (*ret != 0)
323 continue;
324
325 // If the format has a time and no date, set a year so `mktime` can calculate seconds from the Epoch.
326 if (tm.tm_year == 0)
327 tm.tm_year = 100;
328
329 time_t timeInt = mktime(&tm);
330 return timeInt - VuoUnixTimeOffset;
331 }
332 }
333
334 return NAN;
335}
336
343{
344 const int numFormats = 2;
345 const char *formats[numFormats] = {
346 "%a, %d %b %Y %T %z",
347 "%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)
348 };
349
350 return VuoTime_makeFromFormats(rfc822, formats, numFormats);
351}
352
360{
361 const int numFormats = 4;
362 const char *formats[numFormats] = {
363 "%Y-%m-%dT%H:%M:%SZ",
364 "%Y-%m-%d %H:%M:%SZ",
365 "%Y-%m-%dT%H:%M:%S+00:00",
366 "%Y-%m-%d %H:%M:%S+00:00"
367 };
368
369 return VuoTime_makeFromFormats(iso8601, formats, numFormats);
370}
371
375static char *VuoTime_changeTo2DigitYear(char *format)
376{
377 for (int i = 0; format[i] != 0; ++i)
378 if (format[i] == 'Y')
379 format[i] = 'y';
380 return format;
381}
382
389{
390 CFLocaleRef localeCF = CFLocaleCopyCurrent();
391 VuoText localeID = VuoText_makeFromCFString(CFLocaleGetIdentifier(localeCF));
392 VuoRetain(localeID);
393 CFRelease(localeCF);
394 locale_t locale = newlocale(LC_ALL_MASK, localeID, NULL);
395
396 const int numFormats = 15;
397 char *formats[numFormats] = {
398 VuoTime_stringForFormat(VuoTimeFormat_DateTimeSortable, locale),
399 VuoTime_changeTo2DigitYear(VuoTime_stringForFormat(VuoTimeFormat_DateTimeShort12, locale)),
400 VuoTime_stringForFormat(VuoTimeFormat_DateTimeShort12, locale),
401 VuoTime_changeTo2DigitYear(VuoTime_stringForFormat(VuoTimeFormat_DateTimeShort24, locale)),
402 VuoTime_stringForFormat(VuoTimeFormat_DateTimeShort24, locale),
403 VuoTime_stringForFormat(VuoTimeFormat_DateTimeMedium12, locale),
404 VuoTime_stringForFormat(VuoTimeFormat_DateTimeMedium24, locale),
405 VuoTime_stringForFormat(VuoTimeFormat_DateTimeLong12, locale),
406 VuoTime_stringForFormat(VuoTimeFormat_DateTimeLong24, locale),
407 VuoTime_changeTo2DigitYear(VuoTime_stringForFormat(VuoTimeFormat_DateShort, locale)),
408 VuoTime_stringForFormat(VuoTimeFormat_DateShort, locale),
409 VuoTime_stringForFormat(VuoTimeFormat_DateMedium, locale),
410 VuoTime_stringForFormat(VuoTimeFormat_DateLong, locale),
411 VuoTime_stringForFormat(VuoTimeFormat_Time12, locale),
412 VuoTime_stringForFormat(VuoTimeFormat_Time24, locale)
413 };
414
415 VuoTime time = VuoTime_makeFromFormats(str, (const char **)formats, numFormats);
416
417 VuoRelease(localeID);
418 freelocale(locale);
419 for (int i = 0; i < numFormats; ++i)
420 free(formats[i]);
421
422 return time;
423}
424
442bool VuoTime_getComponents(VuoTime time, VuoInteger *year, VuoInteger *dayOfYear, VuoInteger *month, VuoInteger *dayOfMonth, VuoInteger *week, VuoWeekday *dayOfWeek, VuoInteger *hour, VuoInteger *minute, VuoReal *second)
443{
444 if (isnan(time))
445 return false;
446 time_t unixtime = time + VuoUnixTimeOffset;
447 struct tm *tm = localtime(&unixtime);
448 if (!tm)
449 return false;
450 if (year)
451 *year = tm->tm_year + 1900;
452 if (dayOfYear)
453 *dayOfYear = tm->tm_yday + 1;
454 if (month)
455 *month = tm->tm_mon + 1;
456 if (dayOfMonth)
457 *dayOfMonth = tm->tm_mday;
458 if (week)
459 {
460 char s[3];
461 strftime(s, 3, "%V", tm);
462 *week = atoi(s);
463 }
464 if (dayOfWeek)
465 *dayOfWeek = tm->tm_wday;
466 if (hour)
467 *hour = tm->tm_hour;
468 if (minute)
469 *minute = tm->tm_min;
470 if (second)
471 {
472 if (time >= 0)
473 *second = fmod(time, 60);
474 else
475 *second = fmod(60 + fmod(time, 60), 60);
476 }
477
478 return true;
479}
480
484VuoTime VuoTime_round(const VuoTime value, const VuoTimeUnit unit, const int roundingMethod)
485{
486 VuoInteger year;
487 VuoInteger month;
488 VuoInteger dayOfMonth;
489 VuoWeekday dayOfWeek;
490 VuoInteger hour;
491 VuoInteger minute;
492 VuoReal second;
493 bool ret = VuoTime_getComponents(value, &year, NULL, &month, &dayOfMonth, NULL, &dayOfWeek, &hour, &minute, &second);
494 if (!ret)
495 return nan(NULL);
496
497 if (unit == VuoTimeUnit_Millennium)
498 {
499 if (roundingMethod == VuoRoundingMethod_Nearest)
500 return VuoTime_make(1000*(year/1000) + (year%1000 > 500 ? 1000 : 0), 1, 1, 0, 0, 0);
501 else if (roundingMethod == VuoRoundingMethod_Down)
502 return VuoTime_make(1000*(year/1000), 1, 1, 0, 0, 0);
503 else if (roundingMethod == VuoRoundingMethod_Up)
504 return VuoTime_make(1000*(year/1000) + (year%1000 + month + dayOfMonth + hour + minute + second > 0 ? 1000 : 0), 1, 1, 0, 0, 0);
505 }
506 else if (unit == VuoTimeUnit_Century)
507 {
508 if (roundingMethod == VuoRoundingMethod_Nearest)
509 return VuoTime_make(100*(year/100) + (year%100 > 50 ? 100 : 0), 1, 1, 0, 0, 0);
510 else if (roundingMethod == VuoRoundingMethod_Down)
511 return VuoTime_make(100*(year/100), 1, 1, 0, 0, 0);
512 else if (roundingMethod == VuoRoundingMethod_Up)
513 return VuoTime_make(100*(year/100) + (year%100 + month + dayOfMonth + hour + minute + second > 0 ? 100 : 0), 1, 1, 0, 0, 0);
514 }
515 else if (unit == VuoTimeUnit_Decade)
516 {
517 if (roundingMethod == VuoRoundingMethod_Nearest)
518 return VuoTime_make(10*(year/10) + (year%10 > 5 ? 10 : 0), 1, 1, 0, 0, 0);
519 else if (roundingMethod == VuoRoundingMethod_Down)
520 return VuoTime_make(10*(year/10), 1, 1, 0, 0, 0);
521 else if (roundingMethod == VuoRoundingMethod_Up)
522 return VuoTime_make(10*(year/10) + (year%10 + month + dayOfMonth + hour + minute + second > 0 ? 10 : 0), 1, 1, 0, 0, 0);
523 }
524 else if (unit == VuoTimeUnit_Year)
525 {
526 if (roundingMethod == VuoRoundingMethod_Nearest)
527 return VuoTime_make(year + (month > 6 ? 1 : 0), 1, 1, 0, 0, 0);
528 else if (roundingMethod == VuoRoundingMethod_Down)
529 return VuoTime_make(year, 1, 1, 0, 0, 0);
530 else if (roundingMethod == VuoRoundingMethod_Up)
531 return VuoTime_make(year + (month + dayOfMonth + hour + minute + second > 0 ? 1 : 0), 1, 1, 0, 0, 0);
532 }
533 else if (unit == VuoTimeUnit_Quarter)
534 {
535 if (roundingMethod == VuoRoundingMethod_Nearest)
536 return VuoTime_make(year, 3*((month-1)/3) + 1 + ((month-1)%3 > 1 ? 3 : 0), 1, 0, 0, 0);
537 else if (roundingMethod == VuoRoundingMethod_Down)
538 return VuoTime_make(year, 3*((month-1)/3) + 1, 1, 0, 0, 0);
539 else if (roundingMethod == VuoRoundingMethod_Up)
540 return VuoTime_make(year, 3*((month-1)/3) + 1 + ((month-1)%3 + dayOfMonth + hour + minute + second > 0 ? 3 : 0), 1, 0, 0, 0);
541 }
542 else if (unit == VuoTimeUnit_Month)
543 {
544 if (roundingMethod == VuoRoundingMethod_Nearest)
545 return VuoTime_make(year, month + (dayOfMonth > 15 ? 1 : 0), 1, 0, 0, 0);
546 else if (roundingMethod == VuoRoundingMethod_Down)
547 return VuoTime_make(year, month, 1, 0, 0, 0);
548 else if (roundingMethod == VuoRoundingMethod_Up)
549 return VuoTime_make(year, month + (dayOfMonth + hour + minute + second > 0 ? 1 : 0), 1, 0, 0, 0);
550 }
551 else if (unit == VuoTimeUnit_WeekSunday)
552 {
553 if (roundingMethod == VuoRoundingMethod_Nearest)
554 return VuoTime_make(year, month, dayOfMonth + (dayOfWeek > VuoWeekday_Wednesday ? 7-dayOfWeek : -dayOfWeek), 0, 0, 0);
555 else if (roundingMethod == VuoRoundingMethod_Down)
556 return VuoTime_make(year, month, dayOfMonth - dayOfWeek, 0, 0, 0);
557 else if (roundingMethod == VuoRoundingMethod_Up)
558 return VuoTime_make(year, month, dayOfMonth + (dayOfWeek + hour + minute + second > 0 ? 7-dayOfWeek : 0), 0, 0, 0);
559 }
560 else if (unit == VuoTimeUnit_WeekMonday)
561 {
562 if (dayOfWeek == VuoWeekday_Sunday)
563 dayOfWeek += 7;
564
565 if (roundingMethod == VuoRoundingMethod_Nearest)
566 return VuoTime_make(year, month, dayOfMonth + (dayOfWeek > VuoWeekday_Thursday ? 8-dayOfWeek : 1-dayOfWeek), 0, 0, 0);
567 else if (roundingMethod == VuoRoundingMethod_Down)
568 return VuoTime_make(year, month, dayOfMonth - dayOfWeek + 1, 0, 0, 0);
569 else if (roundingMethod == VuoRoundingMethod_Up)
570 return VuoTime_make(year, month, dayOfMonth + (dayOfWeek-1 + hour + minute + second > 0 ? 8-dayOfWeek : 0), 0, 0, 0);
571 }
572 else if (unit == VuoTimeUnit_Day)
573 {
574 if (roundingMethod == VuoRoundingMethod_Nearest)
575 return VuoTime_make(year, month, dayOfMonth + (hour > 12 ? 1 : 0), 0, 0, 0);
576 else if (roundingMethod == VuoRoundingMethod_Down)
577 return VuoTime_make(year, month, dayOfMonth, 0, 0, 0);
578 else if (roundingMethod == VuoRoundingMethod_Up)
579 return VuoTime_make(year, month, dayOfMonth + (hour + minute + second > 0 ? 1 : 0), 0, 0, 0);
580 }
581 else if (unit == VuoTimeUnit_Hour)
582 {
583 if (roundingMethod == VuoRoundingMethod_Nearest)
584 return VuoTime_make(year, month, dayOfMonth, hour + (minute > 30 ? 1 : 0), 0, 0);
585 else if (roundingMethod == VuoRoundingMethod_Down)
586 return VuoTime_make(year, month, dayOfMonth, hour, 0, 0);
587 else if (roundingMethod == VuoRoundingMethod_Up)
588 return VuoTime_make(year, month, dayOfMonth, hour + (minute + second > 0 ? 1 : 0), 0, 0);
589 }
590 else if (unit == VuoTimeUnit_HalfHour)
591 {
592 if (roundingMethod == VuoRoundingMethod_Nearest)
593 return VuoTime_make(year, month, dayOfMonth, hour, 30*(minute/30) + (minute%30 > 15 ? 30 : 0), 0);
594 else if (roundingMethod == VuoRoundingMethod_Down)
595 return VuoTime_make(year, month, dayOfMonth, hour, 30*(minute/30), 0);
596 else if (roundingMethod == VuoRoundingMethod_Up)
597 return VuoTime_make(year, month, dayOfMonth, hour, 30*(minute/30) + (minute%30 + second > 0 ? 30 : 0), 0);
598 }
599 else if (unit == VuoTimeUnit_QuarterHour)
600 {
601 if (roundingMethod == VuoRoundingMethod_Nearest)
602 return VuoTime_make(year, month, dayOfMonth, hour, 15*(minute/15) + (minute%15 > 7 ? 15 : 0), 0);
603 else if (roundingMethod == VuoRoundingMethod_Down)
604 return VuoTime_make(year, month, dayOfMonth, hour, 15*(minute/15), 0);
605 else if (roundingMethod == VuoRoundingMethod_Up)
606 return VuoTime_make(year, month, dayOfMonth, hour, 15*(minute/15) + (minute%15 + second > 0 ? 15 : 0), 0);
607 }
608 else if (unit == VuoTimeUnit_Minute)
609 {
610 if (roundingMethod == VuoRoundingMethod_Nearest)
611 return VuoTime_make(year, month, dayOfMonth, hour, minute + (second > 30 ? 1 : 0), 0);
612 else if (roundingMethod == VuoRoundingMethod_Down)
613 return VuoTime_make(year, month, dayOfMonth, hour, minute, 0);
614 else if (roundingMethod == VuoRoundingMethod_Up)
615 return VuoTime_make(year, month, dayOfMonth, hour, minute + (second > 0 ? 1 : 0), 0);
616 }
617 else if (unit == VuoTimeUnit_Second)
618 {
619 if (roundingMethod == VuoRoundingMethod_Nearest)
620 return lround(value);
621 else if (roundingMethod == VuoRoundingMethod_Down)
622 return floor(value);
623 else if (roundingMethod == VuoRoundingMethod_Up)
624 return ceil(value);
625 }
626
627 return value;
628}
629
638{
639 CFLocaleRef localeCF = CFLocaleCopyCurrent();
640 VuoText localeID = VuoText_makeFromCFString(CFLocaleGetIdentifier(localeCF));
641 VuoRetain(localeID);
642 CFRelease(localeCF);
643
644 VuoText formattedTime = VuoTime_formatWithLocale(time, format, localeID);
645
646 VuoRelease(localeID);
647
648 return formattedTime;
649}
650
658VuoText VuoTime_formatWithLocale(const VuoTime time, const VuoTimeFormat format, const VuoText localeIdentifier)
659{
660 VuoText output = NULL;
661
662 locale_t locale = newlocale(LC_ALL_MASK, localeIdentifier, NULL);
663 static dispatch_once_t loggedLocale = 0;
664 dispatch_once(&loggedLocale, ^{
665 VUserLog("macOS Locale: %s", localeIdentifier);
666 VUserLog(" C Locale: %s", querylocale(LC_ALL_MASK, locale));
667 });
668
669 char *formatString = VuoTime_stringForFormat(format, locale);
670 if (! formatString)
671 goto done;
672
673 time_t t = time + VuoUnixTimeOffset;
674 struct tm *tm;
675 if (format == VuoTimeFormat_DateTimeSortable)
676 tm = gmtime(&t);
677 else
678 tm = localtime(&t);
679 if (!tm)
680 goto done;
681
682 char formattedTime[1024];
683 strftime_l(formattedTime, 1024, formatString, tm, locale);
684
685 output = VuoText_make(formattedTime);
686
687done:
688 free(formatString);
689 freelocale(locale);
690
691 return output;
692}