From bf954cc9bc328cbd9b23eb1377d15bbb3433188e Mon Sep 17 00:00:00 2001 From: David 'Epper' Marshall Date: Sun, 1 May 2022 03:47:05 -0400 Subject: [PATCH] time: relative update (#14240) --- vlib/time/format.v | 33 ++++++----- vlib/time/relative_test.v | 58 +++++++++++++++++++ vlib/time/time.v | 118 +++++++++++++++++++++++++------------- 3 files changed, 155 insertions(+), 54 deletions(-) create mode 100644 vlib/time/relative_test.v diff --git a/vlib/time/format.v b/vlib/time/format.v index baa141a688..93d3fc2e6c 100644 --- a/vlib/time/format.v +++ b/vlib/time/format.v @@ -87,48 +87,53 @@ const tokens_4 = ['MMMM', 'DDDD', 'DDDo', 'dddd', 'YYYY'] // custom_format returns a date with custom format // // | | Token | Output | -// | :----------- | -------: | :--------- | -// | Month | M | 1 2 ... 11 12 | +// | ----------: | :------ | :--------- | +// | **Month** | M | 1 2 ... 11 12 | // | | Mo | 1st 2nd ... 11th 12th | // | | MM | 01 02 ... 11 12 | // | | MMM | Jan Feb ... Nov Dec | // | | MMMM | January February ... November December | -// | Quarter | Q | 1 2 3 4 | +// | **Quarter** | Q | 1 2 3 4 | // | | QQ | 01 02 03 04 | // | | Qo | 1st 2nd 3rd 4th | -// | Day of Month | D | 1 2 ... 30 31 | +// | **Day of Month** | D | 1 2 ... 30 31 | // | | Do | 1st 2nd ... 30th 31st | // | | DD | 01 02 ... 30 31 | -// | Day of Year | DDD | 1 2 ... 364 365 | +// | **Day of Year** | DDD | 1 2 ... 364 365 | // | | DDDo | 1st 2nd ... 364th 365th | // | | DDDD | 001 002 ... 364 365 | -// | Day of Week | d | 0 1 ... 5 6 (Sun-Sat) | +// | **Day of Week** | d | 0 1 ... 5 6 (Sun-Sat) | // | | c | 1 2 ... 6 7 (Mon-Sun) | // | | dd | Su Mo ... Fr Sa | // | | ddd | Sun Mon ... Fri Sat | // | | dddd | Sunday Monday ... Friday Saturday | -// | Week of Year | w | 1 2 ... 52 53 | +// | **Week of Year** | w | 1 2 ... 52 53 | // | | wo | 1st 2nd ... 52nd 53rd | // | | ww | 01 02 ... 52 53 | -// | Year | YY | 70 71 ... 29 30 | +// | **Year** | YY | 70 71 ... 29 30 | // | | YYYY | 1970 1971 ... 2029 2030 | -// | Era | N | BC AD | +// | **Era** | N | BC AD | // | | NN | Before Christ, Anno Domini | -// | AM/PM | A | AM PM | +// | **AM/PM** | A | AM PM | // | | a | am pm | -// | Hour | H | 0 1 ... 22 23 | +// | **Hour** | H | 0 1 ... 22 23 | // | | HH | 00 01 ... 22 23 | // | | h | 1 2 ... 11 12 | // | | hh | 01 02 ... 11 12 | // | | k | 1 2 ... 23 24 | // | | kk | 01 02 ... 23 24 | -// | Minute | m | 0 1 ... 58 59 | +// | **Minute** | m | 0 1 ... 58 59 | // | | mm | 00 01 ... 58 59 | -// | Second | s | 0 1 ... 58 59 | +// | **Second** | s | 0 1 ... 58 59 | // | | ss | 00 01 ... 58 59 | -// | Offset | Z | -7 -6 ... +5 +6 | +// | **Offset** | Z | -7 -6 ... +5 +6 | // | | ZZ | -0700 -0600 ... +0500 +0600 | // | | ZZZ | -07:00 -06:00 ... +05:00 +06:00 | +// +// Usage: +// ```v +// println(time.now().custom_format('MMMM Mo YY N kk:mm:ss A')) // output like: January 1st 22 AD 13:45:33 PM +// ``` pub fn (t Time) custom_format(s string) string { mut tokens := []string{} for i := 0; i < s.len; { diff --git a/vlib/time/relative_test.v b/vlib/time/relative_test.v new file mode 100644 index 0000000000..8e330619e4 --- /dev/null +++ b/vlib/time/relative_test.v @@ -0,0 +1,58 @@ +import time + +fn test_relative() { + // past + mut date := time.now() + assert date.relative() == 'now' + date = date.add_seconds(-61) + assert date.relative() == '1 minute ago' + assert date.relative_short() == '1m ago' + date = date.add_seconds(-120) + assert date.relative() == '3 minutes ago' + assert date.relative_short() == '3m ago' + date = date.add_seconds(-1 * time.seconds_per_hour) + assert date.relative() == '1 hour ago' + assert date.relative_short() == '1h ago' + date = date.add_seconds(-5 * time.seconds_per_hour) + assert date.relative() == '6 hours ago' + assert date.relative_short() == '6h ago' + date = date.add_seconds(-1 * time.seconds_per_day) + assert date.relative() == '1 day ago' + assert date.relative_short() == '1d ago' + date = date.add_seconds(-4 * time.seconds_per_day) + assert date.relative() == '5 days ago' + assert date.relative_short() == '5d ago' + date = time.now().add_seconds(-75 * time.seconds_per_day) + assert date.relative() == 'last ${date.custom_format('MMM')} ${date.custom_format('D')}' + assert date.relative_short() == '75d ago' + date = time.now().add_seconds(-400 * time.seconds_per_day) + assert date.relative() == '1 year ago' + assert date.relative_short() == '1y ago' + + // future + date = time.now() + date = date.add_seconds(61) + assert date.relative() == 'in 1 minute' + assert date.relative_short() == 'in 1m' + date = date.add_seconds(120) + assert date.relative() == 'in 3 minutes' + assert date.relative_short() == 'in 3m' + date = date.add_seconds(1 * time.seconds_per_hour) + assert date.relative() == 'in 1 hour' + assert date.relative_short() == 'in 1h' + date = date.add_seconds(5 * time.seconds_per_hour) + assert date.relative() == 'in 6 hours' + assert date.relative_short() == 'in 6h' + date = date.add_seconds(time.seconds_per_day) + assert date.relative() == 'in 1 day' + assert date.relative_short() == 'in 1d' + date = date.add_seconds(4 * time.seconds_per_day) + assert date.relative() == 'in 5 days' + assert date.relative_short() == 'in 5d' + date = time.now().add_seconds(75 * time.seconds_per_day) + assert date.relative() == 'on ${date.custom_format('MMM')} ${date.custom_format('D')}' + assert date.relative_short() == 'in 75d' + date = time.now().add_seconds(400 * time.seconds_per_day) + assert date.relative() == 'in 1 year' + assert date.relative_short() == 'in 1y' +} diff --git a/vlib/time/time.v b/vlib/time/time.v index 072c866b35..020cbaaa65 100644 --- a/vlib/time/time.v +++ b/vlib/time/time.v @@ -131,48 +131,70 @@ pub fn since(t Time) Duration { // relative returns a string representation of the difference between t // and the current time. +// +// Sample outputs: +// ``` +// // future +// now +// in 5 minutes +// in 1 day +// on Feb 17 +// // past +// 2 hours ago +// last Jan 15 +// 5 years ago +// ``` pub fn (t Time) relative() string { znow := now() - secs := znow.unix - t.unix - if secs <= 30 { - // right now or in the future - // TODO handle time in the future + mut secs := znow.unix - t.unix + mut prefix := '' + mut suffix := '' + if secs < 0 { + secs *= -1 + prefix = 'in ' + } else { + suffix = ' ago' + } + if secs < time.seconds_per_minute / 2 { return 'now' } - if secs < 60 { - return '1m' - } - if secs < 3600 { - m := secs / 60 + if secs < time.seconds_per_hour { + m := secs / time.seconds_per_minute if m == 1 { - return '1 minute ago' + return '${prefix}1 minute$suffix' } - return '$m minutes ago' + return '$prefix$m minutes$suffix' } - if secs < 3600 * 24 { - h := secs / 3600 + if secs < time.seconds_per_hour * 24 { + h := secs / time.seconds_per_hour if h == 1 { - return '1 hour ago' + return '${prefix}1 hour$suffix' } - return '$h hours ago' + return '$prefix$h hours$suffix' } - if secs < 3600 * 24 * 5 { - d := secs / 3600 / 24 + if secs < time.seconds_per_hour * 24 * 7 { + d := secs / time.seconds_per_hour / 24 if d == 1 { - return '1 day ago' + return '${prefix}1 day$suffix' } - return '$d days ago' + return '$prefix$d days$suffix' } - if secs > 3600 * 24 * 10000 { - return '' + if secs < time.seconds_per_hour * 24 * 365 { + if prefix == 'in ' { + return 'on $t.md()' + } + return 'last $t.md()' } - return t.md() + y := secs / time.seconds_per_hour / 24 / 365 + if y == 1 { + return '${prefix}1 year$suffix' + } + return '$prefix$y years$suffix' } // relative_short returns a string saying how long ago a time occured as follows: // 0-30 seconds: `"now"`; 30-60 seconds: `"1m"`; anything else is rounded to the -// nearest minute, hour or day; anything higher than 10000 days (about 27 years) -// years returns an empty string. +// nearest minute, hour, day, or year // Some Examples: // `0s -> 'now'`; // `20s -> 'now'`; @@ -184,28 +206,44 @@ pub fn (t Time) relative() string { // `15842354871s -> ''` pub fn (t Time) relative_short() string { znow := now() - secs := znow.unix - t.unix - if secs <= 30 { - // right now or in the future - // TODO handle time in the future + mut secs := znow.unix - t.unix + mut prefix := '' + mut suffix := '' + if secs < 0 { + secs *= -1 + prefix = 'in ' + } else { + suffix = ' ago' + } + if secs < time.seconds_per_minute / 2 { return 'now' } - if secs < 60 { - return '1m' + if secs < time.seconds_per_hour { + m := secs / time.seconds_per_minute + if m == 1 { + return '${prefix}1m$suffix' + } + return '$prefix${m}m$suffix' } - if secs < 3600 { - return '${secs / 60}m' + if secs < time.seconds_per_hour * 24 { + h := secs / time.seconds_per_hour + if h == 1 { + return '${prefix}1h$suffix' + } + return '$prefix${h}h$suffix' } - if secs < 3600 * 24 { - return '${secs / 3600}h' + if secs < time.seconds_per_hour * 24 * 365 { + d := secs / time.seconds_per_hour / 24 + if d == 1 { + return '${prefix}1d$suffix' + } + return '$prefix${d}d$suffix' } - if secs < 3600 * 24 * 5 { - return '${secs / 3600 / 24}d' + y := secs / time.seconds_per_hour / 24 / 365 + if y == 1 { + return '${prefix}1y$suffix' } - if secs > 3600 * 24 * 10000 { - return '' - } - return t.md() + return '$prefix${y}y$suffix' } // day_of_week returns the current day of a given year, month, and day,