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