Changes in uspace/lib/c/generic/time.c [3e6a98c5:fbcdeb8] in mainline
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
uspace/lib/c/generic/time.c
r3e6a98c5 rfbcdeb8 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 * … … 37 35 #include <sys/time.h> 38 36 #include <time.h> 39 #include < stdbool.h>37 #include <bool.h> 40 38 #include <libarch/barrier.h> 41 39 #include <macros.h> … … 45 43 #include <ddi.h> 46 44 #include <libc.h> 47 #include <stdint.h>48 #include <stdio.h>49 #include <ctype.h>50 #include <assert.h>51 #include <unistd.h>52 #include <loc.h>53 #include <device/clock_dev.h>54 #include <malloc.h>55 56 #define ASCTIME_BUF_LEN 2657 45 58 46 /** Pointer to kernel shared variables with time */ … … 63 51 } *ktime = NULL; 64 52 65 /* Helper functions ***********************************************************/66 67 #define HOURS_PER_DAY (24)68 #define MINS_PER_HOUR (60)69 #define SECS_PER_MIN (60)70 #define MINS_PER_DAY (MINS_PER_HOUR * HOURS_PER_DAY)71 #define SECS_PER_HOUR (SECS_PER_MIN * MINS_PER_HOUR)72 #define SECS_PER_DAY (SECS_PER_HOUR * HOURS_PER_DAY)73 74 /**75 * Checks whether the year is a leap year.76 *77 * @param year Year since 1900 (e.g. for 1970, the value is 70).78 * @return true if year is a leap year, false otherwise79 */80 static bool _is_leap_year(time_t year)81 {82 year += 1900;83 84 if (year % 400 == 0)85 return true;86 if (year % 100 == 0)87 return false;88 if (year % 4 == 0)89 return true;90 return false;91 }92 93 /**94 * Returns how many days there are in the given month of the given year.95 * Note that year is only taken into account if month is February.96 *97 * @param year Year since 1900 (can be negative).98 * @param mon Month of the year. 0 for January, 11 for December.99 * @return Number of days in the specified month.100 */101 static int _days_in_month(time_t year, time_t mon)102 {103 assert(mon >= 0 && mon <= 11);104 105 static int month_days[] =106 { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };107 108 if (mon == 1) {109 year += 1900;110 /* february */111 return _is_leap_year(year) ? 29 : 28;112 } else {113 return month_days[mon];114 }115 }116 117 /**118 * For specified year, month and day of month, returns which day of that year119 * it is.120 *121 * For example, given date 2011-01-03, the corresponding expression is:122 * _day_of_year(111, 0, 3) == 2123 *124 * @param year Year (year 1900 = 0, can be negative).125 * @param mon Month (January = 0).126 * @param mday Day of month (First day is 1).127 * @return Day of year (First day is 0).128 */129 static int _day_of_year(time_t year, time_t mon, time_t mday)130 {131 static int mdays[] =132 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };133 static int leap_mdays[] =134 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 };135 136 return (_is_leap_year(year) ? leap_mdays[mon] : mdays[mon]) + mday - 1;137 }138 139 /**140 * Integer division that rounds to negative infinity.141 * Used by some functions in this file.142 *143 * @param op1 Dividend.144 * @param op2 Divisor.145 * @return Rounded quotient.146 */147 static time_t _floor_div(time_t op1, time_t op2)148 {149 if (op1 >= 0 || op1 % op2 == 0) {150 return op1 / op2;151 } else {152 return op1 / op2 - 1;153 }154 }155 156 /**157 * Modulo that rounds to negative infinity.158 * Used by some functions in this file.159 *160 * @param op1 Dividend.161 * @param op2 Divisor.162 * @return Remainder.163 */164 static time_t _floor_mod(time_t op1, time_t op2)165 {166 int div = _floor_div(op1, op2);167 168 /* (a / b) * b + a % b == a */169 /* thus, a % b == a - (a / b) * b */170 171 int result = op1 - div * op2;172 173 /* Some paranoid checking to ensure I didn't make a mistake here. */174 assert(result >= 0);175 assert(result < op2);176 assert(div * op2 + result == op1);177 178 return result;179 }180 181 /**182 * Number of days since the Epoch.183 * Epoch is 1970-01-01, which is also equal to day 0.184 *185 * @param year Year (year 1900 = 0, may be negative).186 * @param mon Month (January = 0).187 * @param mday Day of month (first day = 1).188 * @return Number of days since the Epoch.189 */190 static time_t _days_since_epoch(time_t year, time_t mon, time_t mday)191 {192 return (year - 70) * 365 + _floor_div(year - 69, 4) -193 _floor_div(year - 1, 100) + _floor_div(year + 299, 400) +194 _day_of_year(year, mon, mday);195 }196 197 /**198 * Seconds since the Epoch. see also _days_since_epoch().199 *200 * @param tm Normalized broken-down time.201 * @return Number of seconds since the epoch, not counting leap seconds.202 */203 static time_t _secs_since_epoch(const struct tm *tm)204 {205 return _days_since_epoch(tm->tm_year, tm->tm_mon, tm->tm_mday) *206 SECS_PER_DAY + tm->tm_hour * SECS_PER_HOUR +207 tm->tm_min * SECS_PER_MIN + tm->tm_sec;208 }209 210 /**211 * Which day of week the specified date is.212 *213 * @param year Year (year 1900 = 0).214 * @param mon Month (January = 0).215 * @param mday Day of month (first = 1).216 * @return Day of week (Sunday = 0).217 */218 static int _day_of_week(time_t year, time_t mon, time_t mday)219 {220 /* 1970-01-01 is Thursday */221 return _floor_mod((_days_since_epoch(year, mon, mday) + 4), 7);222 }223 224 /**225 * Normalizes the broken-down time and optionally adds specified amount of226 * seconds.227 *228 * @param tm Broken-down time to normalize.229 * @param sec_add Seconds to add.230 * @return 0 on success, -1 on overflow231 */232 static int _normalize_time(struct tm *tm, time_t sec_add)233 {234 // TODO: DST correction235 236 /* Set initial values. */237 time_t sec = tm->tm_sec + sec_add;238 time_t min = tm->tm_min;239 time_t hour = tm->tm_hour;240 time_t day = tm->tm_mday - 1;241 time_t mon = tm->tm_mon;242 time_t year = tm->tm_year;243 244 /* Adjust time. */245 min += _floor_div(sec, SECS_PER_MIN);246 sec = _floor_mod(sec, SECS_PER_MIN);247 hour += _floor_div(min, MINS_PER_HOUR);248 min = _floor_mod(min, MINS_PER_HOUR);249 day += _floor_div(hour, HOURS_PER_DAY);250 hour = _floor_mod(hour, HOURS_PER_DAY);251 252 /* Adjust month. */253 year += _floor_div(mon, 12);254 mon = _floor_mod(mon, 12);255 256 /* Now the difficult part - days of month. */257 258 /* First, deal with whole cycles of 400 years = 146097 days. */259 year += _floor_div(day, 146097) * 400;260 day = _floor_mod(day, 146097);261 262 /* Then, go in one year steps. */263 if (mon <= 1) {264 /* January and February. */265 while (day > 365) {266 day -= _is_leap_year(year) ? 366 : 365;267 year++;268 }269 } else {270 /* Rest of the year. */271 while (day > 365) {272 day -= _is_leap_year(year + 1) ? 366 : 365;273 year++;274 }275 }276 277 /* Finally, finish it off month per month. */278 while (day >= _days_in_month(year, mon)) {279 day -= _days_in_month(year, mon);280 mon++;281 if (mon >= 12) {282 mon -= 12;283 year++;284 }285 }286 287 /* Calculate the remaining two fields. */288 tm->tm_yday = _day_of_year(year, mon, day + 1);289 tm->tm_wday = _day_of_week(year, mon, day + 1);290 291 /* And put the values back to the struct. */292 tm->tm_sec = (int) sec;293 tm->tm_min = (int) min;294 tm->tm_hour = (int) hour;295 tm->tm_mday = (int) day + 1;296 tm->tm_mon = (int) mon;297 298 /* Casts to work around libc brain-damage. */299 if (year > ((int)INT_MAX) || year < ((int)INT_MIN)) {300 tm->tm_year = (year < 0) ? ((int)INT_MIN) : ((int)INT_MAX);301 return -1;302 }303 304 tm->tm_year = (int) year;305 return 0;306 }307 308 /**309 * Which day the week-based year starts on, relative to the first calendar day.310 * E.g. if the year starts on December 31st, the return value is -1.311 *312 * @param Year since 1900.313 * @return Offset of week-based year relative to calendar year.314 */315 static int _wbyear_offset(int year)316 {317 int start_wday = _day_of_week(year, 0, 1);318 return _floor_mod(4 - start_wday, 7) - 3;319 }320 321 /**322 * Returns week-based year of the specified time.323 *324 * @param tm Normalized broken-down time.325 * @return Week-based year.326 */327 static int _wbyear(const struct tm *tm)328 {329 int day = tm->tm_yday - _wbyear_offset(tm->tm_year);330 if (day < 0) {331 /* Last week of previous year. */332 return tm->tm_year - 1;333 }334 if (day > 364 + _is_leap_year(tm->tm_year)) {335 /* First week of next year. */336 return tm->tm_year + 1;337 }338 /* All the other days are in the calendar year. */339 return tm->tm_year;340 }341 342 /**343 * Week number of the year, assuming weeks start on sunday.344 * The first Sunday of January is the first day of week 1;345 * days in the new year before this are in week 0.346 *347 * @param tm Normalized broken-down time.348 * @return The week number (0 - 53).349 */350 static int _sun_week_number(const struct tm *tm)351 {352 int first_day = (7 - _day_of_week(tm->tm_year, 0, 1)) % 7;353 return (tm->tm_yday - first_day + 7) / 7;354 }355 356 /**357 * Week number of the year, assuming weeks start on monday.358 * If the week containing January 1st has four or more days in the new year,359 * then it is considered week 1. Otherwise, it is the last week of the previous360 * year, and the next week is week 1. Both January 4th and the first Thursday361 * of January are always in week 1.362 *363 * @param tm Normalized broken-down time.364 * @return The week number (1 - 53).365 */366 static int _iso_week_number(const struct tm *tm)367 {368 int day = tm->tm_yday - _wbyear_offset(tm->tm_year);369 if (day < 0) {370 /* Last week of previous year. */371 return 53;372 }373 if (day > 364 + _is_leap_year(tm->tm_year)) {374 /* First week of next year. */375 return 1;376 }377 /* All the other days give correct answer. */378 return (day / 7 + 1);379 }380 381 /**382 * Week number of the year, assuming weeks start on monday.383 * The first Monday of January is the first day of week 1;384 * days in the new year before this are in week 0.385 *386 * @param tm Normalized broken-down time.387 * @return The week number (0 - 53).388 */389 static int _mon_week_number(const struct tm *tm)390 {391 int first_day = (1 - _day_of_week(tm->tm_year, 0, 1)) % 7;392 return (tm->tm_yday - first_day + 7) / 7;393 }394 395 /******************************************************************************/396 397 398 53 /** Add microseconds to given timeval. 399 54 * … … 483 138 */ 484 139 int gettimeofday(struct timeval *tv, struct timezone *tz) 485 {486 int rc;487 struct tm t;488 category_id_t cat_id;489 size_t svc_cnt;490 service_id_t *svc_ids = NULL;491 service_id_t svc_id;492 char *svc_name = NULL;493 494 static async_sess_t *clock_conn = NULL;495 496 if (tz) {497 tz->tz_minuteswest = 0;498 tz->tz_dsttime = DST_NONE;499 }500 501 if (clock_conn == NULL) {502 rc = loc_category_get_id("clock", &cat_id, IPC_FLAG_BLOCKING);503 if (rc != EOK)504 goto ret_uptime;505 506 rc = loc_category_get_svcs(cat_id, &svc_ids, &svc_cnt);507 if (rc != EOK)508 goto ret_uptime;509 510 if (svc_cnt == 0)511 goto ret_uptime;512 513 rc = loc_service_get_name(svc_ids[0], &svc_name);514 if (rc != EOK)515 goto ret_uptime;516 517 rc = loc_service_get_id(svc_name, &svc_id, 0);518 if (rc != EOK)519 goto ret_uptime;520 521 clock_conn = loc_service_connect(EXCHANGE_SERIALIZE,522 svc_id, IPC_FLAG_BLOCKING);523 if (!clock_conn)524 goto ret_uptime;525 }526 527 rc = clock_dev_time_get(clock_conn, &t);528 if (rc != EOK)529 goto ret_uptime;530 531 tv->tv_usec = 0;532 tv->tv_sec = mktime(&t);533 534 free(svc_name);535 free(svc_ids);536 537 return EOK;538 539 ret_uptime:540 541 free(svc_name);542 free(svc_ids);543 544 return getuptime(tv);545 }546 547 int getuptime(struct timeval *tv)548 140 { 549 141 if (ktime == NULL) { … … 567 159 } 568 160 161 if (tz) { 162 tz->tz_minuteswest = 0; 163 tz->tz_dsttime = DST_NONE; 164 } 165 569 166 sysarg_t s2 = ktime->seconds2; 570 167 … … 580 177 } else 581 178 tv->tv_sec = s1; 582 179 583 180 return 0; 584 181 } … … 631 228 } 632 229 633 /**634 * This function first normalizes the provided broken-down time635 * (moves all values to their proper bounds) and then tries to636 * calculate the appropriate time_t representation.637 *638 * @param tm Broken-down time.639 * @return time_t representation of the time, undefined value on overflow.640 */641 time_t mktime(struct tm *tm)642 {643 // TODO: take DST flag into account644 // TODO: detect overflow645 646 _normalize_time(tm, 0);647 return _secs_since_epoch(tm);648 }649 650 /**651 * Convert time and date to a string, based on a specified format and652 * current locale.653 *654 * @param s Buffer to write string to.655 * @param maxsize Size of the buffer.656 * @param format Format of the output.657 * @param tm Broken-down time to format.658 * @return Number of bytes written.659 */660 size_t strftime(char *restrict s, size_t maxsize,661 const char *restrict format, const struct tm *restrict tm)662 {663 assert(s != NULL);664 assert(format != NULL);665 assert(tm != NULL);666 667 // TODO: use locale668 static const char *wday_abbr[] = {669 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"670 };671 static const char *wday[] = {672 "Sunday", "Monday", "Tuesday", "Wednesday",673 "Thursday", "Friday", "Saturday"674 };675 static const char *mon_abbr[] = {676 "Jan", "Feb", "Mar", "Apr", "May", "Jun",677 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"678 };679 static const char *mon[] = {680 "January", "February", "March", "April", "May", "June", "July",681 "August", "September", "October", "November", "December"682 };683 684 if (maxsize < 1) {685 return 0;686 }687 688 char *ptr = s;689 size_t consumed;690 size_t remaining = maxsize;691 692 #define append(...) { \693 /* FIXME: this requires POSIX-correct snprintf */ \694 /* otherwise it won't work with non-ascii chars */ \695 consumed = snprintf(ptr, remaining, __VA_ARGS__); \696 if (consumed >= remaining) { \697 return 0; \698 } \699 ptr += consumed; \700 remaining -= consumed; \701 }702 703 #define recurse(fmt) { \704 consumed = strftime(ptr, remaining, fmt, tm); \705 if (consumed == 0) { \706 return 0; \707 } \708 ptr += consumed; \709 remaining -= consumed; \710 }711 712 #define TO_12H(hour) (((hour) > 12) ? ((hour) - 12) : \713 (((hour) == 0) ? 12 : (hour)))714 715 while (*format != '\0') {716 if (*format != '%') {717 append("%c", *format);718 format++;719 continue;720 }721 722 format++;723 if (*format == '0' || *format == '+') {724 // TODO: padding725 format++;726 }727 while (isdigit(*format)) {728 // TODO: padding729 format++;730 }731 if (*format == 'O' || *format == 'E') {732 // TODO: locale's alternative format733 format++;734 }735 736 switch (*format) {737 case 'a':738 append("%s", wday_abbr[tm->tm_wday]); break;739 case 'A':740 append("%s", wday[tm->tm_wday]); break;741 case 'b':742 append("%s", mon_abbr[tm->tm_mon]); break;743 case 'B':744 append("%s", mon[tm->tm_mon]); break;745 case 'c':746 // TODO: locale-specific datetime format747 recurse("%Y-%m-%d %H:%M:%S"); break;748 case 'C':749 append("%02d", (1900 + tm->tm_year) / 100); break;750 case 'd':751 append("%02d", tm->tm_mday); break;752 case 'D':753 recurse("%m/%d/%y"); break;754 case 'e':755 append("%2d", tm->tm_mday); break;756 case 'F':757 recurse("%+4Y-%m-%d"); break;758 case 'g':759 append("%02d", _wbyear(tm) % 100); break;760 case 'G':761 append("%d", _wbyear(tm)); break;762 case 'h':763 recurse("%b"); break;764 case 'H':765 append("%02d", tm->tm_hour); break;766 case 'I':767 append("%02d", TO_12H(tm->tm_hour)); break;768 case 'j':769 append("%03d", tm->tm_yday); break;770 case 'k':771 append("%2d", tm->tm_hour); break;772 case 'l':773 append("%2d", TO_12H(tm->tm_hour)); break;774 case 'm':775 append("%02d", tm->tm_mon); break;776 case 'M':777 append("%02d", tm->tm_min); break;778 case 'n':779 append("\n"); break;780 case 'p':781 append("%s", tm->tm_hour < 12 ? "AM" : "PM"); break;782 case 'P':783 append("%s", tm->tm_hour < 12 ? "am" : "PM"); break;784 case 'r':785 recurse("%I:%M:%S %p"); break;786 case 'R':787 recurse("%H:%M"); break;788 case 's':789 append("%ld", _secs_since_epoch(tm)); break;790 case 'S':791 append("%02d", tm->tm_sec); break;792 case 't':793 append("\t"); break;794 case 'T':795 recurse("%H:%M:%S"); break;796 case 'u':797 append("%d", (tm->tm_wday == 0) ? 7 : tm->tm_wday);798 break;799 case 'U':800 append("%02d", _sun_week_number(tm)); break;801 case 'V':802 append("%02d", _iso_week_number(tm)); break;803 case 'w':804 append("%d", tm->tm_wday); break;805 case 'W':806 append("%02d", _mon_week_number(tm)); break;807 case 'x':808 // TODO: locale-specific date format809 recurse("%Y-%m-%d"); break;810 case 'X':811 // TODO: locale-specific time format812 recurse("%H:%M:%S"); break;813 case 'y':814 append("%02d", tm->tm_year % 100); break;815 case 'Y':816 append("%d", 1900 + tm->tm_year); break;817 case 'z':818 // TODO: timezone819 break;820 case 'Z':821 // TODO: timezone822 break;823 case '%':824 append("%%");825 break;826 default:827 /* Invalid specifier, print verbatim. */828 while (*format != '%') {829 format--;830 }831 append("%%");832 break;833 }834 format++;835 }836 837 #undef append838 #undef recurse839 840 return maxsize - remaining;841 }842 843 844 /** Converts a time value to a broken-down UTC time845 *846 * @param time Time to convert847 * @param result Structure to store the result to848 *849 * @return EOK or a negative error code850 */851 int time_utc2tm(const time_t time, struct tm *restrict result)852 {853 assert(result != NULL);854 855 /* Set result to epoch. */856 result->tm_sec = 0;857 result->tm_min = 0;858 result->tm_hour = 0;859 result->tm_mday = 1;860 result->tm_mon = 0;861 result->tm_year = 70; /* 1970 */862 863 if (_normalize_time(result, time) == -1)864 return EOVERFLOW;865 866 return EOK;867 }868 869 /** Converts a time value to a null terminated string of the form870 * "Wed Jun 30 21:49:08 1993\n" expressed in UTC.871 *872 * @param time Time to convert.873 * @param buf Buffer to store the string to, must be at least874 * ASCTIME_BUF_LEN bytes long.875 *876 * @return EOK or a negative error code.877 */878 int time_utc2str(const time_t time, char *restrict buf)879 {880 struct tm t;881 int r;882 883 if ((r = time_utc2tm(time, &t)) != EOK)884 return r;885 886 time_tm2str(&t, buf);887 return EOK;888 }889 890 891 /**892 * Converts broken-down time to a string in format893 * "Sun Jan 1 00:00:00 1970\n". (Obsolete)894 *895 * @param timeptr Broken-down time structure.896 * @param buf Buffer to store string to, must be at least ASCTIME_BUF_LEN897 * bytes long.898 */899 void time_tm2str(const struct tm *restrict timeptr, char *restrict buf)900 {901 assert(timeptr != NULL);902 assert(buf != NULL);903 904 static const char *wday[] = {905 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"906 };907 static const char *mon[] = {908 "Jan", "Feb", "Mar", "Apr", "May", "Jun",909 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"910 };911 912 snprintf(buf, ASCTIME_BUF_LEN, "%s %s %2d %02d:%02d:%02d %d\n",913 wday[timeptr->tm_wday],914 mon[timeptr->tm_mon],915 timeptr->tm_mday, timeptr->tm_hour,916 timeptr->tm_min, timeptr->tm_sec,917 1900 + timeptr->tm_year);918 }919 920 /**921 * Converts a time value to a broken-down local time, expressed relative922 * to the user's specified timezone.923 *924 * @param timer Time to convert.925 * @param result Structure to store the result to.926 *927 * @return EOK on success or a negative error code.928 */929 int time_local2tm(const time_t time, struct tm *restrict result)930 {931 // TODO: deal with timezone932 // currently assumes system and all times are in GMT933 934 /* Set result to epoch. */935 result->tm_sec = 0;936 result->tm_min = 0;937 result->tm_hour = 0;938 result->tm_mday = 1;939 result->tm_mon = 0;940 result->tm_year = 70; /* 1970 */941 942 if (_normalize_time(result, time) == -1)943 return EOVERFLOW;944 945 return EOK;946 }947 948 /**949 * Converts the calendar time to a null terminated string950 * of the form "Wed Jun 30 21:49:08 1993\n" expressed relative to the951 * user's specified timezone.952 *953 * @param timer Time to convert.954 * @param buf Buffer to store the string to. Must be at least955 * ASCTIME_BUF_LEN bytes long.956 *957 * @return EOK on success or a negative error code.958 */959 int time_local2str(const time_t time, char *buf)960 {961 struct tm loctime;962 int r;963 964 if ((r = time_local2tm(time, &loctime)) != EOK)965 return r;966 967 time_tm2str(&loctime, buf);968 969 return EOK;970 }971 972 /**973 * Calculate the difference between two times, in seconds.974 *975 * @param time1 First time.976 * @param time0 Second time.977 * @return Time in seconds.978 */979 double difftime(time_t time1, time_t time0)980 {981 return (double) (time1 - time0);982 }983 984 230 /** @} 985 231 */
Note:
See TracChangeset
for help on using the changeset viewer.