Changes in uspace/lib/c/generic/time.c [d4d74dc:d3e3a71] in mainline
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
uspace/lib/c/generic/time.c
rd4d74dc rd3e3a71 1 1 /* 2 2 * Copyright (c) 2006 Ondrej Palkovsky 3 * Copyright (c) 2011 Petr Koupy 4 * Copyright (c) 2011 Jiri Zarevucky 3 5 * All rights reserved. 4 6 * … … 43 45 #include <ddi.h> 44 46 #include <libc.h> 45 #include <unistd.h> 47 #include <stdint.h> 48 #include <stdio.h> 49 #include <ctype.h> 50 51 #define ASCTIME_BUF_LEN 26 46 52 47 53 /** Pointer to kernel shared variables with time */ … … 51 57 volatile sysarg_t seconds2; 52 58 } *ktime = NULL; 59 60 /* Helper functions ***********************************************************/ 61 62 #define HOURS_PER_DAY (24) 63 #define MINS_PER_HOUR (60) 64 #define SECS_PER_MIN (60) 65 #define MINS_PER_DAY (MINS_PER_HOUR * HOURS_PER_DAY) 66 #define SECS_PER_HOUR (SECS_PER_MIN * MINS_PER_HOUR) 67 #define SECS_PER_DAY (SECS_PER_HOUR * HOURS_PER_DAY) 68 69 /** 70 * Checks whether the year is a leap year. 71 * 72 * @param year Year since 1900 (e.g. for 1970, the value is 70). 73 * @return true if year is a leap year, false otherwise 74 */ 75 static bool _is_leap_year(time_t year) 76 { 77 year += 1900; 78 79 if (year % 400 == 0) 80 return true; 81 if (year % 100 == 0) 82 return false; 83 if (year % 4 == 0) 84 return true; 85 return false; 86 } 87 88 /** 89 * Returns how many days there are in the given month of the given year. 90 * Note that year is only taken into account if month is February. 91 * 92 * @param year Year since 1900 (can be negative). 93 * @param mon Month of the year. 0 for January, 11 for December. 94 * @return Number of days in the specified month. 95 */ 96 static int _days_in_month(time_t year, time_t mon) 97 { 98 assert(mon >= 0 && mon <= 11); 99 100 static int month_days[] = 101 { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 102 103 if (mon == 1) { 104 year += 1900; 105 /* february */ 106 return _is_leap_year(year) ? 29 : 28; 107 } else { 108 return month_days[mon]; 109 } 110 } 111 112 /** 113 * For specified year, month and day of month, returns which day of that year 114 * it is. 115 * 116 * For example, given date 2011-01-03, the corresponding expression is: 117 * _day_of_year(111, 0, 3) == 2 118 * 119 * @param year Year (year 1900 = 0, can be negative). 120 * @param mon Month (January = 0). 121 * @param mday Day of month (First day is 1). 122 * @return Day of year (First day is 0). 123 */ 124 static int _day_of_year(time_t year, time_t mon, time_t mday) 125 { 126 static int mdays[] = 127 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; 128 static int leap_mdays[] = 129 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }; 130 131 return (_is_leap_year(year) ? leap_mdays[mon] : mdays[mon]) + mday - 1; 132 } 133 134 /** 135 * Integer division that rounds to negative infinity. 136 * Used by some functions in this file. 137 * 138 * @param op1 Dividend. 139 * @param op2 Divisor. 140 * @return Rounded quotient. 141 */ 142 static time_t _floor_div(time_t op1, time_t op2) 143 { 144 if (op1 >= 0 || op1 % op2 == 0) { 145 return op1 / op2; 146 } else { 147 return op1 / op2 - 1; 148 } 149 } 150 151 /** 152 * Modulo that rounds to negative infinity. 153 * Used by some functions in this file. 154 * 155 * @param op1 Dividend. 156 * @param op2 Divisor. 157 * @return Remainder. 158 */ 159 static time_t _floor_mod(time_t op1, time_t op2) 160 { 161 int div = _floor_div(op1, op2); 162 163 /* (a / b) * b + a % b == a */ 164 /* thus, a % b == a - (a / b) * b */ 165 166 int result = op1 - div * op2; 167 168 /* Some paranoid checking to ensure I didn't make a mistake here. */ 169 assert(result >= 0); 170 assert(result < op2); 171 assert(div * op2 + result == op1); 172 173 return result; 174 } 175 176 /** 177 * Number of days since the Epoch. 178 * Epoch is 1970-01-01, which is also equal to day 0. 179 * 180 * @param year Year (year 1900 = 0, may be negative). 181 * @param mon Month (January = 0). 182 * @param mday Day of month (first day = 1). 183 * @return Number of days since the Epoch. 184 */ 185 static time_t _days_since_epoch(time_t year, time_t mon, time_t mday) 186 { 187 return (year - 70) * 365 + _floor_div(year - 69, 4) - 188 _floor_div(year - 1, 100) + _floor_div(year + 299, 400) + 189 _day_of_year(year, mon, mday); 190 } 191 192 /** 193 * Seconds since the Epoch. see also _days_since_epoch(). 194 * 195 * @param tm Normalized broken-down time. 196 * @return Number of seconds since the epoch, not counting leap seconds. 197 */ 198 static time_t _secs_since_epoch(const struct tm *tm) 199 { 200 return _days_since_epoch(tm->tm_year, tm->tm_mon, tm->tm_mday) * 201 SECS_PER_DAY + tm->tm_hour * SECS_PER_HOUR + 202 tm->tm_min * SECS_PER_MIN + tm->tm_sec; 203 } 204 205 /** 206 * Which day of week the specified date is. 207 * 208 * @param year Year (year 1900 = 0). 209 * @param mon Month (January = 0). 210 * @param mday Day of month (first = 1). 211 * @return Day of week (Sunday = 0). 212 */ 213 static int _day_of_week(time_t year, time_t mon, time_t mday) 214 { 215 /* 1970-01-01 is Thursday */ 216 return _floor_mod((_days_since_epoch(year, mon, mday) + 4), 7); 217 } 218 219 /** 220 * Normalizes the broken-down time and optionally adds specified amount of 221 * seconds. 222 * 223 * @param tm Broken-down time to normalize. 224 * @param sec_add Seconds to add. 225 * @return 0 on success, -1 on overflow 226 */ 227 static int _normalize_time(struct tm *tm, time_t sec_add) 228 { 229 // TODO: DST correction 230 231 /* Set initial values. */ 232 time_t sec = tm->tm_sec + sec_add; 233 time_t min = tm->tm_min; 234 time_t hour = tm->tm_hour; 235 time_t day = tm->tm_mday - 1; 236 time_t mon = tm->tm_mon; 237 time_t year = tm->tm_year; 238 239 /* Adjust time. */ 240 min += _floor_div(sec, SECS_PER_MIN); 241 sec = _floor_mod(sec, SECS_PER_MIN); 242 hour += _floor_div(min, MINS_PER_HOUR); 243 min = _floor_mod(min, MINS_PER_HOUR); 244 day += _floor_div(hour, HOURS_PER_DAY); 245 hour = _floor_mod(hour, HOURS_PER_DAY); 246 247 /* Adjust month. */ 248 year += _floor_div(mon, 12); 249 mon = _floor_mod(mon, 12); 250 251 /* Now the difficult part - days of month. */ 252 253 /* First, deal with whole cycles of 400 years = 146097 days. */ 254 year += _floor_div(day, 146097) * 400; 255 day = _floor_mod(day, 146097); 256 257 /* Then, go in one year steps. */ 258 if (mon <= 1) { 259 /* January and February. */ 260 while (day > 365) { 261 day -= _is_leap_year(year) ? 366 : 365; 262 year++; 263 } 264 } else { 265 /* Rest of the year. */ 266 while (day > 365) { 267 day -= _is_leap_year(year + 1) ? 366 : 365; 268 year++; 269 } 270 } 271 272 /* Finally, finish it off month per month. */ 273 while (day >= _days_in_month(year, mon)) { 274 day -= _days_in_month(year, mon); 275 mon++; 276 if (mon >= 12) { 277 mon -= 12; 278 year++; 279 } 280 } 281 282 /* Calculate the remaining two fields. */ 283 tm->tm_yday = _day_of_year(year, mon, day + 1); 284 tm->tm_wday = _day_of_week(year, mon, day + 1); 285 286 /* And put the values back to the struct. */ 287 tm->tm_sec = (int) sec; 288 tm->tm_min = (int) min; 289 tm->tm_hour = (int) hour; 290 tm->tm_mday = (int) day + 1; 291 tm->tm_mon = (int) mon; 292 293 /* Casts to work around libc brain-damage. */ 294 if (year > ((int)INT_MAX) || year < ((int)INT_MIN)) { 295 tm->tm_year = (year < 0) ? ((int)INT_MIN) : ((int)INT_MAX); 296 return -1; 297 } 298 299 tm->tm_year = (int) year; 300 return 0; 301 } 302 303 /** 304 * Which day the week-based year starts on, relative to the first calendar day. 305 * E.g. if the year starts on December 31st, the return value is -1. 306 * 307 * @param Year since 1900. 308 * @return Offset of week-based year relative to calendar year. 309 */ 310 static int _wbyear_offset(int year) 311 { 312 int start_wday = _day_of_week(year, 0, 1); 313 return _floor_mod(4 - start_wday, 7) - 3; 314 } 315 316 /** 317 * Returns week-based year of the specified time. 318 * 319 * @param tm Normalized broken-down time. 320 * @return Week-based year. 321 */ 322 static int _wbyear(const struct tm *tm) 323 { 324 int day = tm->tm_yday - _wbyear_offset(tm->tm_year); 325 if (day < 0) { 326 /* Last week of previous year. */ 327 return tm->tm_year - 1; 328 } 329 if (day > 364 + _is_leap_year(tm->tm_year)) { 330 /* First week of next year. */ 331 return tm->tm_year + 1; 332 } 333 /* All the other days are in the calendar year. */ 334 return tm->tm_year; 335 } 336 337 /** 338 * Week number of the year, assuming weeks start on sunday. 339 * The first Sunday of January is the first day of week 1; 340 * days in the new year before this are in week 0. 341 * 342 * @param tm Normalized broken-down time. 343 * @return The week number (0 - 53). 344 */ 345 static int _sun_week_number(const struct tm *tm) 346 { 347 int first_day = (7 - _day_of_week(tm->tm_year, 0, 1)) % 7; 348 return (tm->tm_yday - first_day + 7) / 7; 349 } 350 351 /** 352 * Week number of the year, assuming weeks start on monday. 353 * If the week containing January 1st has four or more days in the new year, 354 * then it is considered week 1. Otherwise, it is the last week of the previous 355 * year, and the next week is week 1. Both January 4th and the first Thursday 356 * of January are always in week 1. 357 * 358 * @param tm Normalized broken-down time. 359 * @return The week number (1 - 53). 360 */ 361 static int _iso_week_number(const struct tm *tm) 362 { 363 int day = tm->tm_yday - _wbyear_offset(tm->tm_year); 364 if (day < 0) { 365 /* Last week of previous year. */ 366 return 53; 367 } 368 if (day > 364 + _is_leap_year(tm->tm_year)) { 369 /* First week of next year. */ 370 return 1; 371 } 372 /* All the other days give correct answer. */ 373 return (day / 7 + 1); 374 } 375 376 /** 377 * Week number of the year, assuming weeks start on monday. 378 * The first Monday of January is the first day of week 1; 379 * days in the new year before this are in week 0. 380 * 381 * @param tm Normalized broken-down time. 382 * @return The week number (0 - 53). 383 */ 384 static int _mon_week_number(const struct tm *tm) 385 { 386 int first_day = (1 - _day_of_week(tm->tm_year, 0, 1)) % 7; 387 return (tm->tm_yday - first_day + 7) / 7; 388 } 389 390 /******************************************************************************/ 391 53 392 54 393 /** Add microseconds to given timeval. … … 229 568 } 230 569 570 /** 571 * This function first normalizes the provided broken-down time 572 * (moves all values to their proper bounds) and then tries to 573 * calculate the appropriate time_t representation. 574 * 575 * @param tm Broken-down time. 576 * @return time_t representation of the time, undefined value on overflow. 577 */ 578 time_t mktime(struct tm *tm) 579 { 580 // TODO: take DST flag into account 581 // TODO: detect overflow 582 583 _normalize_time(tm, 0); 584 return _secs_since_epoch(tm); 585 } 586 587 /** 588 * Convert time and date to a string, based on a specified format and 589 * current locale. 590 * 591 * @param s Buffer to write string to. 592 * @param maxsize Size of the buffer. 593 * @param format Format of the output. 594 * @param tm Broken-down time to format. 595 * @return Number of bytes written. 596 */ 597 size_t strftime(char *restrict s, size_t maxsize, 598 const char *restrict format, const struct tm *restrict tm) 599 { 600 assert(s != NULL); 601 assert(format != NULL); 602 assert(tm != NULL); 603 604 // TODO: use locale 605 static const char *wday_abbr[] = { 606 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" 607 }; 608 static const char *wday[] = { 609 "Sunday", "Monday", "Tuesday", "Wednesday", 610 "Thursday", "Friday", "Saturday" 611 }; 612 static const char *mon_abbr[] = { 613 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 614 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 615 }; 616 static const char *mon[] = { 617 "January", "February", "March", "April", "May", "June", "July", 618 "August", "September", "October", "November", "December" 619 }; 620 621 if (maxsize < 1) { 622 return 0; 623 } 624 625 char *ptr = s; 626 size_t consumed; 627 size_t remaining = maxsize; 628 629 #define append(...) { \ 630 /* FIXME: this requires POSIX-correct snprintf */ \ 631 /* otherwise it won't work with non-ascii chars */ \ 632 consumed = snprintf(ptr, remaining, __VA_ARGS__); \ 633 if (consumed >= remaining) { \ 634 return 0; \ 635 } \ 636 ptr += consumed; \ 637 remaining -= consumed; \ 638 } 639 640 #define recurse(fmt) { \ 641 consumed = strftime(ptr, remaining, fmt, tm); \ 642 if (consumed == 0) { \ 643 return 0; \ 644 } \ 645 ptr += consumed; \ 646 remaining -= consumed; \ 647 } 648 649 #define TO_12H(hour) (((hour) > 12) ? ((hour) - 12) : \ 650 (((hour) == 0) ? 12 : (hour))) 651 652 while (*format != '\0') { 653 if (*format != '%') { 654 append("%c", *format); 655 format++; 656 continue; 657 } 658 659 format++; 660 if (*format == '0' || *format == '+') { 661 // TODO: padding 662 format++; 663 } 664 while (isdigit(*format)) { 665 // TODO: padding 666 format++; 667 } 668 if (*format == 'O' || *format == 'E') { 669 // TODO: locale's alternative format 670 format++; 671 } 672 673 switch (*format) { 674 case 'a': 675 append("%s", wday_abbr[tm->tm_wday]); break; 676 case 'A': 677 append("%s", wday[tm->tm_wday]); break; 678 case 'b': 679 append("%s", mon_abbr[tm->tm_mon]); break; 680 case 'B': 681 append("%s", mon[tm->tm_mon]); break; 682 case 'c': 683 // TODO: locale-specific datetime format 684 recurse("%Y-%m-%d %H:%M:%S"); break; 685 case 'C': 686 append("%02d", (1900 + tm->tm_year) / 100); break; 687 case 'd': 688 append("%02d", tm->tm_mday); break; 689 case 'D': 690 recurse("%m/%d/%y"); break; 691 case 'e': 692 append("%2d", tm->tm_mday); break; 693 case 'F': 694 recurse("%+4Y-%m-%d"); break; 695 case 'g': 696 append("%02d", _wbyear(tm) % 100); break; 697 case 'G': 698 append("%d", _wbyear(tm)); break; 699 case 'h': 700 recurse("%b"); break; 701 case 'H': 702 append("%02d", tm->tm_hour); break; 703 case 'I': 704 append("%02d", TO_12H(tm->tm_hour)); break; 705 case 'j': 706 append("%03d", tm->tm_yday); break; 707 case 'k': 708 append("%2d", tm->tm_hour); break; 709 case 'l': 710 append("%2d", TO_12H(tm->tm_hour)); break; 711 case 'm': 712 append("%02d", tm->tm_mon); break; 713 case 'M': 714 append("%02d", tm->tm_min); break; 715 case 'n': 716 append("\n"); break; 717 case 'p': 718 append("%s", tm->tm_hour < 12 ? "AM" : "PM"); break; 719 case 'P': 720 append("%s", tm->tm_hour < 12 ? "am" : "PM"); break; 721 case 'r': 722 recurse("%I:%M:%S %p"); break; 723 case 'R': 724 recurse("%H:%M"); break; 725 case 's': 726 append("%ld", _secs_since_epoch(tm)); break; 727 case 'S': 728 append("%02d", tm->tm_sec); break; 729 case 't': 730 append("\t"); break; 731 case 'T': 732 recurse("%H:%M:%S"); break; 733 case 'u': 734 append("%d", (tm->tm_wday == 0) ? 7 : tm->tm_wday); 735 break; 736 case 'U': 737 append("%02d", _sun_week_number(tm)); break; 738 case 'V': 739 append("%02d", _iso_week_number(tm)); break; 740 case 'w': 741 append("%d", tm->tm_wday); break; 742 case 'W': 743 append("%02d", _mon_week_number(tm)); break; 744 case 'x': 745 // TODO: locale-specific date format 746 recurse("%Y-%m-%d"); break; 747 case 'X': 748 // TODO: locale-specific time format 749 recurse("%H:%M:%S"); break; 750 case 'y': 751 append("%02d", tm->tm_year % 100); break; 752 case 'Y': 753 append("%d", 1900 + tm->tm_year); break; 754 case 'z': 755 // TODO: timezone 756 break; 757 case 'Z': 758 // TODO: timezone 759 break; 760 case '%': 761 append("%%"); 762 break; 763 default: 764 /* Invalid specifier, print verbatim. */ 765 while (*format != '%') { 766 format--; 767 } 768 append("%%"); 769 break; 770 } 771 format++; 772 } 773 774 #undef append 775 #undef recurse 776 777 return maxsize - remaining; 778 } 779 780 struct tm *gmtime(const time_t *timer) 781 { 782 assert(timer != NULL); 783 784 static struct tm result; 785 786 /* Set result to epoch. */ 787 result.tm_sec = 0; 788 result.tm_min = 0; 789 result.tm_hour = 0; 790 result.tm_mday = 1; 791 result.tm_mon = 0; 792 result.tm_year = 70; /* 1970 */ 793 794 if (_normalize_time(&result, *timer) == -1) { 795 errno = EOVERFLOW; 796 return NULL; 797 } 798 799 return &result; 800 } 801 802 /** 803 * Converts broken-down time to a string in format 804 * "Sun Jan 1 00:00:00 1970\n". (Obsolete) 805 * 806 * @param timeptr Broken-down time structure. 807 * @return Pointer to a statically allocated string. 808 */ 809 char *asctime(const struct tm *timeptr) 810 { 811 static char buf[ASCTIME_BUF_LEN]; 812 813 assert(timeptr != NULL); 814 815 static const char *wday[] = { 816 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" 817 }; 818 static const char *mon[] = { 819 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 820 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 821 }; 822 823 snprintf(buf, ASCTIME_BUF_LEN, "%s %s %2d %02d:%02d:%02d %d\n", 824 wday[timeptr->tm_wday], 825 mon[timeptr->tm_mon], 826 timeptr->tm_mday, timeptr->tm_hour, 827 timeptr->tm_min, timeptr->tm_sec, 828 1900 + timeptr->tm_year); 829 830 return buf; 831 832 } 833 834 /** 835 * Converts a time value to a broken-down local time. 836 * 837 * @param timer Time to convert. 838 * @return Normalized broken-down time in local timezone, NULL on overflow. 839 */ 840 struct tm *localtime(const time_t *timer) 841 { 842 // TODO: deal with timezone 843 // currently assumes system and all times are in GMT 844 845 static struct tm result; 846 847 /* Set result to epoch. */ 848 result.tm_sec = 0; 849 result.tm_min = 0; 850 result.tm_hour = 0; 851 result.tm_mday = 1; 852 result.tm_mon = 0; 853 result.tm_year = 70; /* 1970 */ 854 855 if (_normalize_time(&result, *timer) == -1) { 856 errno = EOVERFLOW; 857 return NULL; 858 } 859 860 return &result; 861 } 862 863 /** 864 * Equivalent to asctime(localtime(clock)). 865 * 866 * @param timer Time to convert. 867 * @return Pointer to a statically allocated string holding the date. 868 */ 869 char *ctime(const time_t *timer) 870 { 871 struct tm *loctime = localtime(timer); 872 if (loctime == NULL) { 873 return NULL; 874 } 875 return asctime(loctime); 876 } 877 878 /** 879 * Calculate the difference between two times, in seconds. 880 * 881 * @param time1 First time. 882 * @param time0 Second time. 883 * @return Time in seconds. 884 */ 885 double difftime(time_t time1, time_t time0) 886 { 887 return (double) (time1 - time0); 888 } 889 231 890 /** @} 232 891 */
Note:
See TracChangeset
for help on using the changeset viewer.