[libc-commits] [libc] [libc] create TimeReader to look at a struct tm (PR #126138)

Nick Desaulniers via libc-commits libc-commits at lists.llvm.org
Tue Feb 11 12:35:14 PST 2025


================
@@ -89,11 +96,254 @@ LIBC_INLINE struct tm *gmtime_internal(const time_t *timer, struct tm *result) {
 
 // TODO: localtime is not yet implemented and a temporary solution is to
 //       use gmtime, https://github.com/llvm/llvm-project/issues/107597
-LIBC_INLINE struct tm *localtime(const time_t *t_ptr) {
-  static struct tm result;
+LIBC_INLINE tm *localtime(const time_t *t_ptr) {
+  static tm result;
   return time_utils::gmtime_internal(t_ptr, &result);
 }
 
+// Returns number of years from (1, year).
+LIBC_INLINE constexpr int64_t get_num_of_leap_years_before(int64_t year) {
+  return (year / 4) - (year / 100) + (year / 400);
+}
+
+// Returns True if year is a leap year.
+LIBC_INLINE constexpr bool is_leap_year(const int64_t year) {
+  return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0));
+}
+
+LIBC_INLINE constexpr int get_days_in_year(const int year) {
+  return is_leap_year(year) ? time_constants::DAYS_PER_LEAP_YEAR
+                            : time_constants::DAYS_PER_NON_LEAP_YEAR;
+}
+
+// This is a helper class that takes a struct tm and lets you inspect its
+// values. Where relevant, results are bounds checked and returned as optionals.
+// This class does not, however, do data normalization except where necessary.
+// It will faithfully return a date of 9999-99-99, even though that makes no
+// sense.
+class TMReader final {
+  const tm *timeptr;
+
+  template <size_t N>
+  LIBC_INLINE constexpr cpp::optional<cpp::string_view>
+  bounds_check(const cpp::array<cpp::string_view, N> &arr, int index) const {
+    if (index >= 0 && index < static_cast<int>(arr.size()))
+      return arr[index];
+    return cpp::nullopt;
+  }
+
+public:
+  LIBC_INLINE constexpr explicit TMReader(const tm *tmptr) : timeptr(tmptr) {
+    ;
+  }
+
+  // Strings
+  LIBC_INLINE constexpr cpp::optional<cpp::string_view>
+  get_weekday_short_name() const {
+    return bounds_check(time_constants::WEEK_DAY_NAMES, timeptr->tm_wday);
+  }
+
+  LIBC_INLINE constexpr cpp::optional<cpp::string_view>
+  get_weekday_full_name() const {
+    return bounds_check(time_constants::WEEK_DAY_FULL_NAMES, timeptr->tm_wday);
+  }
+
+  LIBC_INLINE constexpr cpp::optional<cpp::string_view>
+  get_month_short_name() const {
+    return bounds_check(time_constants::MONTH_NAMES, timeptr->tm_mon);
+  }
+
+  LIBC_INLINE constexpr cpp::optional<cpp::string_view>
+  get_month_full_name() const {
+    return bounds_check(time_constants::MONTH_FULL_NAMES, timeptr->tm_mon);
+  }
+
+  LIBC_INLINE constexpr cpp::string_view get_am_pm() const {
+    if (timeptr->tm_hour < 12)
+      return "AM";
+    return "PM";
+  }
+
+  LIBC_INLINE constexpr cpp::string_view get_timezone_name() const {
+    // TODO: timezone support
+    return "UTC";
+  }
+
+  // Numbers
+  LIBC_INLINE constexpr int get_sec() const { return timeptr->tm_sec; }
+  LIBC_INLINE constexpr int get_min() const { return timeptr->tm_min; }
+  LIBC_INLINE constexpr int get_hour() const { return timeptr->tm_hour; }
+  LIBC_INLINE constexpr int get_mday() const { return timeptr->tm_mday; }
+  LIBC_INLINE constexpr int get_mon() const { return timeptr->tm_mon; }
+  LIBC_INLINE constexpr int get_yday() const { return timeptr->tm_yday; }
+  LIBC_INLINE constexpr int get_wday() const { return timeptr->tm_wday; }
+  LIBC_INLINE constexpr int get_isdst() const { return timeptr->tm_isdst; }
+
+  // returns the year, counting from 1900
+  LIBC_INLINE constexpr int get_year_raw() const { return timeptr->tm_year; }
+  // returns the year, counting from 0
+  LIBC_INLINE constexpr int get_year() const {
+    return timeptr->tm_year + time_constants::TIME_YEAR_BASE;
+  }
+
+  LIBC_INLINE constexpr int is_leap_year() const {
+    return time_utils::is_leap_year(get_year());
+  }
+
+  LIBC_INLINE constexpr int get_iso_wday() const {
+    using time_constants::DAYS_PER_WEEK;
+    using time_constants::MONDAY;
+    // ISO uses a week that starts on Monday, but struct tm starts its week on
+    // Sunday. This function normalizes the weekday so that it always returns a
+    // value 0-6
+    const int NORMALIZED_WDAY = timeptr->tm_wday % DAYS_PER_WEEK;
+    return (NORMALIZED_WDAY + (DAYS_PER_WEEK - MONDAY)) % DAYS_PER_WEEK;
+  }
+
+  // returns the week of the current year, with weeks starting on start_day.
+  LIBC_INLINE constexpr int get_week(time_constants::WeekDay start_day) const {
+    using time_constants::DAYS_PER_WEEK;
+    // The most recent start_day. The rest of the days into the current week
+    // don't count, so ignore them.
+    // Also add 7 to handle start_day > tm_wday
+    const int start_of_cur_week =
+        timeptr->tm_yday -
+        ((timeptr->tm_wday + DAYS_PER_WEEK - start_day) % DAYS_PER_WEEK);
+
+    // The original formula is ceil((start_of_cur_week + 1) / DAYS_PER_WEEK)
+    // That becomes (start_of_cur_week + 1 + DAYS_PER_WEEK - 1) / DAYS_PER_WEEK)
+    // Which simplifies to (start_of_cur_week + DAYS_PER_WEEK) / DAYS_PER_WEEK
+    const int ceil_weeks_since_start =
+        (start_of_cur_week + DAYS_PER_WEEK) / DAYS_PER_WEEK;
+
+    return ceil_weeks_since_start;
+  }
+
+  LIBC_INLINE constexpr int get_iso_week() const {
+    using time_constants::DAYS_PER_WEEK;
+    using time_constants::ISO_FIRST_DAY_OF_YEAR;
+    using time_constants::MONDAY;
+    using time_constants::WeekDay;
+    using time_constants::WEEKS_PER_YEAR;
+
+    constexpr WeekDay START_DAY = MONDAY;
+
+    // The most recent start_day. The rest of the days into the current week
+    // don't count, so ignore them.
+    // Also add 7 to handle start_day > tm_wday
+    const int start_of_cur_week =
+        timeptr->tm_yday -
+        ((timeptr->tm_wday + DAYS_PER_WEEK - START_DAY) % DAYS_PER_WEEK);
+
+    // if the week starts in the previous year, and also if the 4th of this year
+    // is not in this week.
+    if (start_of_cur_week < -3) {
+      const int days_into_prev_year =
+          get_days_in_year(get_year() - 1) + start_of_cur_week;
+      // Each year has at least 52 weeks, but a year's last week will be 53 if
+      // its first week starts in the previous year and its last week ends
+      // in the next year. We know get_year() - 1 must extend into get_year(),
+      // so here we check if it also extended into get_year() - 2 and add 1 week
+      // if it does.
+      return WEEKS_PER_YEAR +
+             ((days_into_prev_year % DAYS_PER_WEEK) > ISO_FIRST_DAY_OF_YEAR);
+    }
+
+    // subtract 1 to account for yday being 0 indexed
+    const int days_until_end_of_year =
+        get_days_in_year(get_year()) - start_of_cur_week - 1;
+
+    // if there are less than 3 days from the start of this week to the end of
+    // the year, then there must be 4 days in this week in the next year, which
+    // means that this week is the first week of that year.
+    if (days_until_end_of_year < 3)
+      return 1;
+
+    // else just calculate the current week like normal.
+    const int ceil_weeks_since_start =
+        (start_of_cur_week + DAYS_PER_WEEK) / DAYS_PER_WEEK;
+
+    // add 1 if this year's first week starts in the previous year.
+    const int WEEK_STARTS_IN_PREV_YEAR =
+        ((start_of_cur_week + time_constants::DAYS_PER_WEEK) %
+         time_constants::DAYS_PER_WEEK) > time_constants::ISO_FIRST_DAY_OF_YEAR;
+    return ceil_weeks_since_start + WEEK_STARTS_IN_PREV_YEAR;
+  }
+
+  LIBC_INLINE constexpr int get_iso_year() const {
+    const int BASE_YEAR = get_year();
+    // The ISO year is the same as a standard year for all dates after the start
+    // of the first week and before the last week. Since the first ISO week of a
+    // year starts on the 4th, anything after that is in this year.
+    if (timeptr->tm_yday >= time_constants::ISO_FIRST_DAY_OF_YEAR &&
+        timeptr->tm_yday < time_constants::DAYS_PER_NON_LEAP_YEAR -
+                               time_constants::DAYS_PER_WEEK)
+      return BASE_YEAR;
+
+    const int ISO_WDAY = get_iso_wday();
+    // The first week of the ISO year is defined as the week containing the
+    // 4th day of January.
+
+    // first week
+    if (timeptr->tm_yday < time_constants::ISO_FIRST_DAY_OF_YEAR) {
+      /*
+      If jan 4 is in this week, then we're in BASE_YEAR, else we're in the
+      previous year. The formula's been rearranged so here's the derivation:
+
+              +--------+-- days until jan 4
+              |        |
+       wday + (4 - yday) < 7
+       |               |
+       +---------------+-- weekday of jan 4
+
+       rearranged to get all the constants on one side:
+
+       wday - yday < 7 - 4
+      */
+      const int IS_CUR_YEAR = (ISO_WDAY - timeptr->tm_yday <
+                               time_constants::DAYS_PER_WEEK -
+                                   time_constants::ISO_FIRST_DAY_OF_YEAR);
+      return BASE_YEAR - !IS_CUR_YEAR;
+    }
+
+    // last week
+    const int DAYS_LEFT_IN_YEAR =
+        get_days_in_year(get_year()) - timeptr->tm_yday;
+    /*
+    Similar to above, we're checking if jan 4 (of next year) is in this week. If
+    it is, this is in the next year. Note that this also handles the case of
+    yday > days in year gracefully.
+
+           +------------------+-- days until jan 4 (of next year)
+           |                  |
+    wday + (4 + remaining days) < 7
+    |                         |
+    +-------------------------+-- weekday of jan 4
+
+    rearranging we get:
+
+    wday + remaining days < 7 - 4
+    */
+    const int IS_NEXT_YEAR =
+        (ISO_WDAY + DAYS_LEFT_IN_YEAR <
+         time_constants::DAYS_PER_WEEK - time_constants::ISO_FIRST_DAY_OF_YEAR);
+    return BASE_YEAR + IS_NEXT_YEAR;
+  }
+
+  LIBC_INLINE constexpr time_t get_epoch() const {
+    return mktime_internal(timeptr);
+  }
+
+  // returns the timezone offset in microwave time:
----------------
nickdesaulniers wrote:

lol

https://github.com/llvm/llvm-project/pull/126138


More information about the libc-commits mailing list