1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

time: add custom formatter (#14202)

This commit is contained in:
David 'Epper' Marshall
2022-04-29 08:57:08 -04:00
committed by GitHub
parent ec865cfb37
commit 881d0c04f1
4 changed files with 395 additions and 138 deletions

View File

@@ -0,0 +1,11 @@
import time
fn test_custom_format() {
date := time.now()
assert date.custom_format('YYYY-MM-DD HH:mm') == date.format()
assert date.custom_format('MMM') == date.smonth()
test_str := 'M MM MMM MMMM\nD DD DDD DDDD\nd dd ddd dddd\nYY YYYY a A\nH HH h hh k kk e\nm mm s ss Z ZZ ZZZ\nDo DDDo Q Qo QQ\nN NN w wo ww\nM/D/YYYY N-HH:mm:ss Qo?a'
println(date.custom_format(test_str))
}

View File

@@ -3,6 +3,9 @@
// that can be found in the LICENSE file.
module time
import strings
import math
// format returns a date string in "YYYY-MM-DD HH:MM" format (24h).
pub fn (t Time) format() string {
return t.get_fmt_str(.hyphen, .hhmm24, .yyyymmdd)
@@ -53,6 +56,264 @@ pub fn (t Time) md() string {
return t.get_fmt_date_str(.space, .mmmd)
}
// appends ordinal suffix to a number
fn ordinal_suffix(n int) string {
if n > 3 && n < 21 {
return '${n}th'
}
match n % 10 {
1 {
return '${n}st'
}
2 {
return '${n}nd'
}
3 {
return '${n}rd'
}
else {
return '${n}th'
}
}
}
const tokens_2 = ['MM', 'DD', 'Do', 'YY', 'ss', 'kk', 'NN', 'mm', 'hh', 'HH', 'ZZ', 'dd', 'Qo',
'QQ', 'wo', 'ww']
const tokens_3 = ['MMM', 'DDD', 'ZZZ', 'ddd']
const tokens_4 = ['MMMM', 'DDDD', 'DDDo', 'dddd', 'YYYY']
// custom_format returns a date with custom format
// | | Token | Output |
// | :----------- | -------: | :--------- |
// | 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 |
// | | QQ | 01 02 03 04 |
// | | Qo | 1st 2nd 3rd 4th |
// | 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 |
// | | DDDo | 1st 2nd ... 364th 365th |
// | | DDDD | 001 002 ... 364 365 |
// | 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 |
// | | wo | 1st 2nd ... 52nd 53rd |
// | | ww | 01 02 ... 52 53 |
// | Year | YY | 70 71 ... 29 30 |
// | | YYYY | 1970 1971 ... 2029 2030 |
// | Era | N | BC AD |
// | | NN | Before Christ, Anno Domini |
// | AM/PM | A | AM PM |
// | | a | am pm |
// | 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 |
// | | mm | 00 01 ... 58 59 |
// | Second | s | 0 1 ... 58 59 |
// | | ss | 00 01 ... 58 59 |
// | Offset | Z | -7 -6 ... +5 +6 |
// | | ZZ | -0700 -0600 ... +0500 +0600 |
// | | ZZZ | -07:00 -06:00 ... +05:00 +06:00 |
pub fn (t Time) custom_format(s string) string {
mut tokens := []string{}
for i := 0; i < s.len; {
for j := 4; j > 0; j-- {
if i > s.len - j {
continue
}
if j == 1 || (j == 2 && s[i..i + j] in time.tokens_2)
|| (j == 3 && s[i..i + j] in time.tokens_3)
|| (j == 4 && s[i..i + j] in time.tokens_4) {
tokens << s[i..i + j]
i += (j - 1)
break
}
}
i++
}
mut sb := strings.new_builder(128)
for token in tokens {
match token {
'M' {
sb.write_string(t.month.str())
}
'MM' {
sb.write_string('${t.month:02}')
}
'Mo' {
sb.write_string(ordinal_suffix(t.month))
}
'MMM' {
sb.write_string(long_months[t.month - 1][0..3])
}
'MMMM' {
sb.write_string(long_months[t.month - 1])
}
'D' {
sb.write_string(t.day.str())
}
'DD' {
sb.write_string('${t.day:02}')
}
'Do' {
sb.write_string(ordinal_suffix(t.day))
}
'DDD' {
sb.write_string((t.day + days_before[t.month - 1] + int(is_leap_year(t.year))).str())
}
'DDDD' {
sb.write_string('${t.day + days_before[t.month - 1] + int(is_leap_year(t.year)):03}')
}
'DDDo' {
sb.write_string(ordinal_suffix(t.day + days_before[t.month - 1] +
int(is_leap_year(t.year))))
}
'd' {
sb.write_string(t.day_of_week().str())
}
'dd' {
sb.write_string(long_days[t.day_of_week() - 1][0..2])
}
'ddd' {
sb.write_string(long_days[t.day_of_week() - 1][0..3])
}
'dddd' {
sb.write_string(long_days[t.day_of_week() - 1])
}
'YY' {
sb.write_string(t.year.str()[2..4])
}
'YYYY' {
sb.write_string(t.year.str())
}
'H' {
sb.write_string(t.hour.str())
}
'HH' {
sb.write_string('${t.hour:02}')
}
'h' {
sb.write_string((t.hour % 12).str())
}
'hh' {
sb.write_string('${(t.hour % 12):02}')
}
'm' {
sb.write_string(t.minute.str())
}
'mm' {
sb.write_string('${t.minute:02}')
}
's' {
sb.write_string(t.second.str())
}
'ss' {
sb.write_string('${t.second:02}')
}
'k' {
sb.write_string((t.hour + 1).str())
}
'kk' {
sb.write_string('${(t.hour + 1):02}')
}
'w' {
sb.write_string('${math.ceil((t.day + days_before[t.month - 1] +
int(is_leap_year(t.year))) / 7):.0}')
}
'ww' {
sb.write_string('${math.ceil((t.day + days_before[t.month - 1] +
int(is_leap_year(t.year))) / 7):02.0}')
}
'wo' {
sb.write_string(ordinal_suffix(int(math.ceil((t.day + days_before[t.month - 1] +
int(is_leap_year(t.year))) / 7))))
}
'Q' {
sb.write_string('${(t.month % 4) + 1}')
}
'QQ' {
sb.write_string('${(t.month % 4) + 1:02}')
}
'Qo' {
sb.write_string(ordinal_suffix((t.month % 4) + 1))
}
'c' {
sb.write_string('${t.day_of_week() + 1}')
}
'N' {
// TODO integrate BC
sb.write_string('AD')
}
'NN' {
// TODO integrate Before Christ
sb.write_string('Anno Domini')
}
'Z' {
mut hours := offset() / seconds_per_hour
if hours >= 0 {
sb.write_string('+$hours')
} else {
hours = -hours
sb.write_string('-$hours')
}
}
'ZZ' {
// TODO update if minute differs?
mut hours := offset() / seconds_per_hour
if hours >= 0 {
sb.write_string('+${hours:02}00')
} else {
hours = -hours
sb.write_string('-${hours:02}00')
}
}
'ZZZ' {
// TODO update if minute differs?
mut hours := offset() / seconds_per_hour
if hours >= 0 {
sb.write_string('+${hours:02}:00')
} else {
hours = -hours
sb.write_string('-${hours:02}:00')
}
}
'a' {
if t.hour > 12 {
sb.write_string('pm')
} else {
sb.write_string('am')
}
}
'A' {
if t.hour > 12 {
sb.write_string('PM')
} else {
sb.write_string('AM')
}
}
else {
sb.write_string(token)
}
}
}
return sb.str()
}
// clean returns a date string in a following format:
// - a date string in "HH:MM" format (24h) for current day
// - a date string in "MMM D HH:MM" format (24h) for date of current year

View File

@@ -2,8 +2,12 @@ module time
pub const (
days_string = 'MonTueWedThuFriSatSun'
long_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
'Sunday']
month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
months_string = 'JanFebMarAprMayJunJulAugSepOctNovDec'
long_months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
'September', 'October', 'November', 'December']
// The unsigned zero year for internal calculations.
// Must be 1 mod 400, and times before it will not compute correctly,
// but otherwise can be changed at will.
@@ -30,8 +34,6 @@ pub const (
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
]
long_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
'Sunday']
)
// Time contains various time units for a point in time.
@@ -83,7 +85,7 @@ pub enum FormatDelimiter {
no_delimiter
}
// smonth returns month name.
// smonth returns month name abbreviation.
pub fn (t Time) smonth() string {
if t.month <= 0 || t.month > 12 {
return '---'
@@ -224,10 +226,10 @@ pub fn (t Time) day_of_week() int {
return day_of_week(t.year, t.month, t.day)
}
// weekday_str returns the current day as a string.
// weekday_str returns the current day as a string abbreviation.
pub fn (t Time) weekday_str() string {
i := t.day_of_week() - 1
return time.days_string[i * 3..(i + 1) * 3]
return time.long_days[i][0..3]
}
// weekday_str returns the current day as a string.