Changes in uspace/lib/c/generic/time.c [d3e3a71:d4d74dc] in mainline
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
uspace/lib/c/generic/time.c
rd3e3a71 rd4d74dc 1 1 /* 2 2 * Copyright (c) 2006 Ondrej Palkovsky 3 * Copyright (c) 2011 Petr Koupy4 * Copyright (c) 2011 Jiri Zarevucky5 3 * All rights reserved. 6 4 * … … 45 43 #include <ddi.h> 46 44 #include <libc.h> 47 #include <stdint.h> 48 #include <stdio.h> 49 #include <ctype.h> 50 51 #define ASCTIME_BUF_LEN 26 45 #include <unistd.h> 52 46 53 47 /** Pointer to kernel shared variables with time */ … … 57 51 volatile sysarg_t seconds2; 58 52 } *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 otherwise74 */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 year114 * it is.115 *116 * For example, given date 2011-01-03, the corresponding expression is:117 * _day_of_year(111, 0, 3) == 2118 *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 of221 * seconds.222 *223 * @param tm Broken-down time to normalize.224 * @param sec_add Seconds to add.225 * @return 0 on success, -1 on overflow226 */227 static int _normalize_time(struct tm *tm, time_t sec_add)228 {229 // TODO: DST correction230 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 previous355 * year, and the next week is week 1. Both January 4th and the first Thursday356 * 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 392 53 393 54 /** Add microseconds to given timeval. … … 568 229 } 569 230 570 /**571 * This function first normalizes the provided broken-down time572 * (moves all values to their proper bounds) and then tries to573 * 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 account581 // TODO: detect overflow582 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 and589 * 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 locale605 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: padding662 format++;663 }664 while (isdigit(*format)) {665 // TODO: padding666 format++;667 }668 if (*format == 'O' || *format == 'E') {669 // TODO: locale's alternative format670 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 format684 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 format746 recurse("%Y-%m-%d"); break;747 case 'X':748 // TODO: locale-specific time format749 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: timezone756 break;757 case 'Z':758 // TODO: timezone759 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 append775 #undef recurse776 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 format804 * "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 timezone843 // currently assumes system and all times are in GMT844 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 890 231 /** @} 891 232 */
Note:
See TracChangeset
for help on using the changeset viewer.