Bläddra i källkod

Merge pull request #3980 from Kelimion/format-time

Add `time.to_string*` formatters.
Jeroen van Rijn 1 år sedan
förälder
incheckning
4e194d5dbd
2 ändrade filer med 356 tillägg och 30 borttagningar
  1. 319 30
      core/time/time.odin
  2. 37 0
      tests/core/time/test_core_time.odin

+ 319 - 30
core/time/time.odin

@@ -232,27 +232,21 @@ duration_hours :: proc "contextless" (d: Duration) -> f64 {
 }
 
 /*
-Round a duration to a specific unit.
+Round a duration to a specific unit
 
-This procedure rounds the duration to a specific unit.
+This procedure rounds the duration to a specific unit
 
-**Inputs**:
-- `d`: The duration to round.
-- `m`: The unit to round to.
-
-**Returns**:
-- The duration `d`, rounded to the unit specified by `m`.
-
-**Example**:
+**Note**: Any duration can be supplied as a unit.
 
-In order to obtain the rough amount of seconds in a duration, the following call
-can be used:
+Inputs:
+- d: The duration to round
+- m: The unit to round to
 
-```
-time.duration_round(my_duration, time.Second)
-```
+Returns:
+- The duration `d`, rounded to the unit specified by `m`
 
-**Note**: Any duration can be supplied as a unit.
+Example:
+	time.duration_round(my_duration, time.Second)
 */
 duration_round :: proc "contextless" (d, m: Duration) -> Duration {
 	_less_than_half :: #force_inline proc "contextless" (x, y: Duration) -> bool {
@@ -288,23 +282,17 @@ Truncate the duration to the specified unit.
 
 This procedure truncates the duration `d` to the unit specified by `m`.
 
-**Inputs**:
-- `d`: The duration to truncate.
-- `m`: The unit to truncate to.
-
-**Returns**:
-- The duration `d`, truncated to the unit specified by `m`.
-
-**Example**:
+**Note**: Any duration can be supplied as a unit.
 
-In order to obtain the amount of whole seconds in a duration, the following call
-can be used:
+Inputs:
+- d: The duration to truncate.
+- m: The unit to truncate to.
 
-```
-time.duration_round(my_duration, time.Second)
-```
+Returns:
+- The duration `d`, truncated to the unit specified by `m`.
 
-**Note**: Any duration can be supplied as a unit.
+Example:
+	time.duration_round(my_duration, time.Second)
 */
 duration_truncate :: proc "contextless" (d, m: Duration) -> Duration {
 	return d if m <= 0 else d - d%m
@@ -389,6 +377,307 @@ clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) {
 	return
 }
 
+MIN_HMS_LEN       :: 8
+MIN_HMS_12_LEN    :: 11
+MIN_YYYY_DATE_LEN :: 10
+MIN_YY_DATE_LEN   :: 8
+
+/*
+Formats a `Time` as a 24-hour `hh:mm:ss` string.
+
+**Does not allocate**
+
+Inputs:
+- t:   The Time to format.
+- buf: The backing buffer to use.
+
+Returns:
+- res: The formatted string, backed by buf
+
+Example:
+	buf: [MIN_HMS_LEN]u8
+	now := time.now()
+	fmt.println(time.to_string_hms(now, buf[:]))
+*/
+time_to_string_hms :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check {
+	assert(len(buf) >= MIN_HMS_LEN)
+	h, m, s := clock(t)
+
+	buf[7] = '0' + u8(s % 10); s /= 10
+	buf[6] = '0' + u8(s)
+	buf[5] = ':'
+	buf[4] = '0' + u8(m % 10); m /= 10
+	buf[3] = '0' + u8(m)
+	buf[2] = ':'
+	buf[1] = '0' + u8(h % 10); h /= 10
+	buf[0] = '0' + u8(h)
+
+	return string(buf[:MIN_HMS_LEN])
+}
+
+/*
+Formats a `Duration` as a 24-hour `hh:mm:ss` string.
+
+**Does not allocate**
+
+Inputs:
+- d:   The Duration to format.
+- buf: The backing buffer to use.
+
+Returns:
+- res: The formatted string, backed by buf
+
+Example:
+	buf: [MIN_HMS_LEN]u8
+	d   := time.since(earlier)
+	fmt.println(time.to_string_hms(now, buf[:]))
+*/
+duration_to_string_hms :: proc(d: Duration, buf: []u8) -> (res: string) #no_bounds_check {
+	return time_to_string_hms(Time{_nsec=i64(d)}, buf)
+}
+
+to_string_hms :: proc{time_to_string_hms, duration_to_string_hms}
+
+/*
+Formats a `Time` as a 12-hour `hh:mm:ss pm` string
+
+**Does not allocate**
+
+Inputs:
+- t:    The Time to format
+- buf:  The backing buffer to use
+- ampm: An optional pair of am/pm strings to use in place of the default
+
+Returns:
+- res: The formatted string, backed by buf
+
+Example:
+	buf: [64]u8
+	now := time.now()
+	fmt.println(time.to_string_hms_12(now, buf[:]))
+	fmt.println(time.to_string_hms_12(now, buf[:], {"㏂", "㏘"}))
+*/
+to_string_hms_12 :: proc(t: Time, buf: []u8, ampm: [2]string = {" am", " pm"}) -> (res: string) #no_bounds_check {
+	assert(len(buf) >= MIN_HMS_LEN + max(len(ampm[0]), len(ampm[1])))
+	h, m, s := clock(t)
+
+	_h := h % 12
+	buf[7] = '0' + u8(s % 10); s /= 10
+	buf[6] = '0' + u8(s)
+	buf[5] = ':'
+	buf[4] = '0' + u8(m % 10); m /= 10
+	buf[3] = '0' + u8(m)
+	buf[2] = ':'
+	buf[1] = '0' + u8(_h% 10); _h /= 10
+	buf[0] = '0' + u8(_h)
+
+	if h < 13 {
+		copy(buf[8:], ampm[0])
+		return string(buf[:MIN_HMS_LEN+len(ampm[0])])
+	} else {
+		copy(buf[8:], ampm[1])
+		return string(buf[:MIN_HMS_LEN+len(ampm[1])])
+	}
+}
+
+/*
+Formats a Time as a yyyy-mm-dd date string.
+
+Inputs:
+- t:    The Time to format.
+- buf:  The backing buffer to use.
+
+Returns:
+- res: The formatted string, backed by `buf`.
+
+Example:
+	buf: [MIN_YYYY_DATE_LEN]u8
+	now := time.now()
+	fmt.println(time.to_string_yyyy_mm_dd(now, buf[:]))
+*/
+to_string_yyyy_mm_dd :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check {
+	assert(len(buf) >= MIN_YYYY_DATE_LEN)
+	y, _m, d := date(t)
+	m := u8(_m)
+
+	buf[9] = '0' + u8(d % 10); d /= 10
+	buf[8] = '0' + u8(d % 10)
+	buf[7] = '-'
+	buf[6] = '0' + u8(m % 10); m /= 10
+	buf[5] = '0' + u8(m % 10)
+	buf[4] = '-'
+	buf[3] = '0' + u8(y % 10); y /= 10
+	buf[2] = '0' + u8(y % 10); y /= 10
+	buf[1] = '0' + u8(y % 10); y /= 10
+	buf[0] = '0' + u8(y)
+
+	return string(buf[:MIN_YYYY_DATE_LEN])
+}
+
+/*
+Formats a Time as a yy-mm-dd date string.
+
+Inputs:
+- t:    The Time to format.
+- buf:  The backing buffer to use.
+
+Returns:
+- res: The formatted string, backed by `buf`.
+
+Example:
+	buf: [MIN_YY_DATE_LEN]u8
+	now := time.now()
+	fmt.println(time.to_string_yy_mm_dd(now, buf[:]))
+*/
+to_string_yy_mm_dd :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check {
+	assert(len(buf) >= MIN_YY_DATE_LEN)
+	y, _m, d := date(t)
+	y %= 100; m := u8(_m)
+
+	buf[7] = '0' + u8(d % 10); d /= 10
+	buf[6] = '0' + u8(d % 10)
+	buf[5] = '-'
+	buf[4] = '0' + u8(m % 10); m /= 10
+	buf[3] = '0' + u8(m % 10)
+	buf[2] = '-'
+	buf[1] = '0' + u8(y % 10); y /= 10
+	buf[0] = '0' + u8(y)
+
+	return string(buf[:MIN_YY_DATE_LEN])
+}
+
+/*
+Formats a Time as a dd-mm-yyyy date string.
+
+Inputs:
+- t:    The Time to format.
+- buf:  The backing buffer to use.
+
+Returns:
+- res: The formatted string, backed by `buf`.
+
+Example:
+	buf: [MIN_YYYY_DATE_LEN]u8
+	now := time.now()
+	fmt.println(time.to_string_dd_mm_yyyy(now, buf[:]))
+*/
+to_string_dd_mm_yyyy :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check {
+	assert(len(buf) >= MIN_YYYY_DATE_LEN)
+	y, _m, d := date(t)
+	m := u8(_m)
+
+	buf[9] = '0' + u8(y % 10); y /= 10
+	buf[8] = '0' + u8(y % 10); y /= 10
+	buf[7] = '0' + u8(y % 10); y /= 10
+	buf[6] = '0' + u8(y)
+	buf[5] = '-'
+	buf[4] = '0' + u8(m % 10); m /= 10
+	buf[3] = '0' + u8(m % 10)
+	buf[2] = '-'
+	buf[1] = '0' + u8(d % 10); d /= 10
+	buf[0] = '0' + u8(d % 10)
+
+	return string(buf[:MIN_YYYY_DATE_LEN])
+}
+
+/*
+Formats a Time as a dd-mm-yy date string.
+
+Inputs:
+- t:    The Time to format.
+- buf:  The backing buffer to use.
+
+Returns:
+- res: The formatted string, backed by `buf`.
+
+Example:
+	buf: [MIN_YY_DATE_LEN]u8
+	now := time.now()
+	fmt.println(time.to_string_dd_mm_yy(now, buf[:]))
+*/
+to_string_dd_mm_yy :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check {
+	assert(len(buf) >= MIN_YY_DATE_LEN)
+	y, _m, d := date(t)
+	y %= 100; m := u8(_m)
+
+	buf[7] = '0' + u8(y % 10); y /= 10
+	buf[6] = '0' + u8(y)
+	buf[5] = '-'
+	buf[4] = '0' + u8(m % 10); m /= 10
+	buf[3] = '0' + u8(m % 10)
+	buf[2] = '-'
+	buf[1] = '0' + u8(d % 10); d /= 10
+	buf[0] = '0' + u8(d % 10)
+
+	return string(buf[:MIN_YY_DATE_LEN])
+}
+
+/*
+Formats a Time as a mm-dd-yyyy date string.
+
+Inputs:
+- t:    The Time to format.
+- buf:  The backing buffer to use.
+
+Returns:
+- res: The formatted string, backed by `buf`.
+
+Example:
+	buf: [MIN_YYYY_DATE_LEN]u8
+	now := time.now()
+	fmt.println(time.to_string_mm_dd_yyyy(now, buf[:]))
+*/
+to_string_mm_dd_yyyy :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check {
+	assert(len(buf) >= MIN_YYYY_DATE_LEN)
+	y, _m, d := date(t)
+	m := u8(_m)
+
+	buf[9] = '0' + u8(y % 10); y /= 10
+	buf[8] = '0' + u8(y % 10); y /= 10
+	buf[7] = '0' + u8(y % 10); y /= 10
+	buf[6] = '0' + u8(y)
+	buf[5] = '-'
+	buf[4] = '0' + u8(d % 10); d /= 10
+	buf[3] = '0' + u8(d % 10)
+	buf[2] = '-'
+	buf[1] = '0' + u8(m % 10); m /= 10
+	buf[0] = '0' + u8(m % 10)
+
+	return string(buf[:MIN_YYYY_DATE_LEN])
+}
+
+/*
+Formats a Time as a mm-dd-yy date string.
+
+Inputs:
+- t:    The Time to format.
+- buf:  The backing buffer to use.
+
+Returns:
+- res: The formatted string, backed by `buf`.
+
+Example:
+	buf: [MIN_YY_DATE_LEN]u8
+	now := time.now()
+	fmt.println(time.to_string_mm_dd_yy(now, buf[:]))
+*/
+to_string_mm_dd_yy :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check {
+	assert(len(buf) >= MIN_YY_DATE_LEN)
+	y, _m, d := date(t)
+	y %= 100; m := u8(_m)
+
+	buf[7] = '0' + u8(y % 10); y /= 10
+	buf[6] = '0' + u8(y)
+	buf[5] = '-'
+	buf[4] = '0' + u8(d % 10); d /= 10
+	buf[3] = '0' + u8(d % 10)
+	buf[2] = '-'
+	buf[1] = '0' + u8(m % 10); m /= 10
+	buf[0] = '0' + u8(m % 10)
+
+	return string(buf[:MIN_YY_DATE_LEN])
+}
+
 /*
 Read the timestamp counter of the CPU.
 */

+ 37 - 0
tests/core/time/test_core_time.odin

@@ -6,6 +6,43 @@ import dt "core:time/datetime"
 
 is_leap_year :: time.is_leap_year
 
+@test
+test_time_and_date_formatting :: proc(t: ^testing.T) {
+	buf: [64]u8
+	{
+		now := time.Time{_nsec=min(i64)} // 1677-09-21 00:12:44.145224192 +0000 UTC
+		d := time.Duration(now._nsec)
+
+		testing.expect_value(t, time.to_string_hms       (now, buf[:]),               "00:12:44")
+		testing.expect_value(t, time.to_string_hms_12    (now, buf[:]),               "00:12:44 am")
+		testing.expect_value(t, time.to_string_hms_12    (now, buf[:],  {"㏂", "㏘"}), "00:12:44㏂")
+		testing.expect_value(t, time.to_string_hms       (d,   buf[:]),               "00:12:44")
+
+		testing.expect_value(t, time.to_string_yyyy_mm_dd(now, buf[:]),               "1677-09-21")
+		testing.expect_value(t, time.to_string_yy_mm_dd  (now, buf[:]),               "77-09-21")
+		testing.expect_value(t, time.to_string_dd_mm_yyyy(now, buf[:]),               "21-09-1677")
+		testing.expect_value(t, time.to_string_dd_mm_yy  (now, buf[:]),               "21-09-77")
+		testing.expect_value(t, time.to_string_mm_dd_yyyy(now, buf[:]),               "09-21-1677")
+		testing.expect_value(t, time.to_string_mm_dd_yy  (now, buf[:]),               "09-21-77")
+	}
+	{
+		now := time.Time{_nsec=max(i64)} // 2262-04-11 23:47:16.854775807 +0000 UTC
+		d := time.Duration(now._nsec)
+
+		testing.expect_value(t, time.to_string_hms       (now, buf[:]),               "23:47:16")
+		testing.expect_value(t, time.to_string_hms_12    (now, buf[:]),               "11:47:16 pm")
+		testing.expect_value(t, time.to_string_hms_12    (now, buf[:],  {"㏂", "㏘"}), "11:47:16㏘")
+		testing.expect_value(t, time.to_string_hms       (d,   buf[:]),               "23:47:16")
+
+		testing.expect_value(t, time.to_string_yyyy_mm_dd(now, buf[:]),               "2262-04-11")
+		testing.expect_value(t, time.to_string_yy_mm_dd  (now, buf[:]),               "62-04-11")
+		testing.expect_value(t, time.to_string_dd_mm_yyyy(now, buf[:]),               "11-04-2262")
+		testing.expect_value(t, time.to_string_dd_mm_yy  (now, buf[:]),               "11-04-62")
+		testing.expect_value(t, time.to_string_mm_dd_yyyy(now, buf[:]),               "04-11-2262")
+		testing.expect_value(t, time.to_string_mm_dd_yy  (now, buf[:]),               "04-11-62")
+	}
+}
+
 @test
 test_ordinal_date_roundtrip :: proc(t: ^testing.T) {
 	testing.expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MIN_DATE)) == dt.MIN_DATE, "Roundtripping MIN_DATE failed.")