From 4cf8328f71381b244706ec9bfca70606dcdc7fbe Mon Sep 17 00:00:00 2001 From: yuyi Date: Fri, 4 Aug 2023 21:54:16 +0800 Subject: [PATCH 01/11] ast, fmt: simplify fmt.fn_decl() (#19054) --- vlib/v/ast/str.v | 46 +++++- vlib/v/fmt/fmt.v | 132 +----------------- ...n_headers_with_inline_comments_expected.vv | 2 +- 3 files changed, 47 insertions(+), 133 deletions(-) diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index a991553d25..c324a1ca46 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -89,6 +89,13 @@ pub fn (t &Table) stringify_fn_decl(node &FnDecl, cur_mod string, m2a map[string f.write_string('pub ') } f.write_string('fn ') + pre_comments := node.comments.filter(it.pos.pos < node.name_pos.pos) + if pre_comments.len > 0 { + write_comments(pre_comments, mut f) + if !f.last_n(1)[0].is_space() { + f.write_string(' ') + } + } if node.is_method { f.write_string('(') mut styp := util.no_cur_mod(t.type_to_code(node.receiver.typ.clear_flag(.shared_f)), @@ -211,13 +218,50 @@ fn (t &Table) stringify_fn_after_name(node &FnDecl, mut f strings.Builder, cur_m } } +fn write_comments(comments []Comment, mut f strings.Builder) { + for i, c in comments { + if !f.last_n(1)[0].is_space() { + f.write_string(' ') + } + write_comment(c, mut f) + if c.is_inline && i < comments.len - 1 && !c.is_multi { + f.write_string(' ') + } else if (!c.is_inline || c.is_multi) && i < comments.len - 1 { + f.writeln('') + } + } +} + +fn write_comment(node Comment, mut f strings.Builder) { + if node.is_inline { + x := node.text.trim_left('\x01').trim_space() + if x.contains('\n') { + f.writeln('/*') + f.writeln(x) + f.write_string('*/') + } else { + f.write_string('/* ${x} */') + } + } else { + mut s := node.text.trim_left('\x01').trim_right(' ') + mut out_s := '//' + if s != '' { + if s[0].is_letter() || s[0].is_digit() { + out_s += ' ' + } + out_s += s + } + f.writeln(out_s) + } +} + struct StringifyModReplacement { mod string alias string weight int } -pub fn shorten_full_name_based_on_aliases(input string, m2a map[string]string) string { +fn shorten_full_name_based_on_aliases(input string, m2a map[string]string) string { if m2a.len == 0 || -1 == input.index_u8(`.`) { // a simple typename, like `string` or `[]bool`; no module aliasings apply, // (or there just are not any mappings) diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 4094550561..6040680b26 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -1019,7 +1019,7 @@ pub fn (mut f Fmt) enum_decl(node ast.EnumDecl) { pub fn (mut f Fmt) fn_decl(node ast.FnDecl) { f.attrs(node.attrs) - f.fn_header(node) + f.write(f.table.stringify_fn_decl(&node, f.cur_mod, f.mod2alias)) // Handle trailing comments after fn header declarations if node.no_body && node.end_comments.len > 0 { first_comment := node.end_comments[0] @@ -1043,136 +1043,6 @@ pub fn (mut f Fmt) fn_decl(node ast.FnDecl) { f.fn_body(node) } -pub fn (mut f Fmt) fn_header(node ast.FnDecl) { - if node.is_pub { - f.write('pub ') - } - f.write('fn ') - pre_comments := node.comments.filter(it.pos.pos < node.name_pos.pos) - if pre_comments.len > 0 { - f.comments(pre_comments) - f.write(' ') - } - if node.is_method { - f.write('(') - mut styp := util.no_cur_mod(f.table.type_to_code(node.receiver.typ.clear_flag(.shared_f)), - f.cur_mod) - if node.rec_mut { - f.write(node.receiver.typ.share().str() + ' ') - styp = styp[1..] // remove & - } - f.write(node.receiver.name + ' ') - styp = util.no_cur_mod(styp, f.cur_mod) - if node.params[0].is_auto_rec { - styp = styp.trim('&') - } - f.write(styp + ') ') - } else if node.is_static_type_method { - mut styp := util.no_cur_mod(f.table.type_to_code(node.receiver.typ.clear_flag(.shared_f)), - f.cur_mod) - f.write(styp + '.') - } - mut name := if !node.is_method && node.language == .v { - node.name.all_after_last('.') - } else { - node.name - } - if node.is_static_type_method { - name = name.after('__static__') - } - f.write(name) - if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '>=', '<='] { - f.write(' ') - } - mut add_para_types := true - if node.generic_names.len > 0 { - if node.is_method { - sym := f.table.sym(node.params[0].typ) - if sym.info is ast.Struct { - generic_names := sym.info.generic_types.map(f.table.sym(it).name) - if generic_names == node.generic_names { - add_para_types = false - } - } - } - if add_para_types { - f.write('[') - for i, gname in node.generic_names { - is_last := i == node.generic_names.len - 1 - f.write(gname) - if !is_last { - f.write(', ') - } - } - f.write(']') - } - } - f.write('(') - for i, arg in node.params { - before_comments := arg.comments.filter(it.pos.pos < arg.pos.pos) - if before_comments.len > 0 { - f.comments(before_comments, level: .indent) - } - // skip receiver - if node.is_method && i == 0 { - continue - } - if arg.is_hidden { - continue - } - is_last_arg := i == node.params.len - 1 - is_type_only := arg.name == '' - should_add_type := true - if arg.is_mut { - f.write(arg.typ.share().str() + ' ') - } - f.write(arg.name) - arg_sym := f.table.sym(arg.typ) - if arg_sym.kind == .struct_ && (arg_sym.info as ast.Struct).is_anon { - f.write(' struct {') - struct_ := arg_sym.info as ast.Struct - for field in struct_.fields { - f.write(' ${field.name} ${f.table.type_to_str(field.typ)}') - if field.has_default_expr { - f.write(' = ${field.default_expr}') - } - } - if struct_.fields.len > 0 { - f.write(' ') - } - f.write('}') - } else { - mut s := f.table.type_to_str(arg.typ.clear_flag(.shared_f)) - if arg.is_mut { - if s.starts_with('&') && ((!arg_sym.is_number() && arg_sym.kind != .bool) - || node.language != .v) { - s = s[1..] - } - } - s = util.no_cur_mod(s, f.cur_mod) - s = ast.shorten_full_name_based_on_aliases(s, f.mod2alias) - if should_add_type { - if !is_type_only { - f.write(' ') - } - if node.is_variadic && is_last_arg { - f.write('...') - } - f.write(s) - } - } - if !is_last_arg { - f.write(', ') - } - } - f.write(')') - if node.return_type != ast.void_type { - sreturn_type := util.no_cur_mod(f.table.type_to_str(node.return_type), f.cur_mod) - short_sreturn_type := ast.shorten_full_name_based_on_aliases(sreturn_type, f.mod2alias) - f.write(' ${short_sreturn_type}') - } -} - pub fn (mut f Fmt) anon_fn(node ast.AnonFn) { f.write(f.table.stringify_anon_decl(&node, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast f.fn_body(node.decl) diff --git a/vlib/v/fmt/tests/fn_headers_with_inline_comments_expected.vv b/vlib/v/fmt/tests/fn_headers_with_inline_comments_expected.vv index 9915bd90a2..25b113fa06 100644 --- a/vlib/v/fmt/tests/fn_headers_with_inline_comments_expected.vv +++ b/vlib/v/fmt/tests/fn_headers_with_inline_comments_expected.vv @@ -3,6 +3,6 @@ fn /* main */ main() { } fn // hi - print_hi() { +print_hi() { println('hi') } From 301320f4b00036cbd4600426f40af2bc6bd8e98f Mon Sep 17 00:00:00 2001 From: shove Date: Sat, 5 Aug 2023 12:11:07 +0800 Subject: [PATCH 02/11] os.notify: implement the kqueue backend for notify.FdNotifier (#19057) --- vlib/os/notify/backend_darwin.c.v | 230 ++++++++++++++++++++++++++++++ vlib/os/notify/kqueue.h | 12 ++ vlib/os/notify/notify_test.v | 39 +++-- 3 files changed, 272 insertions(+), 9 deletions(-) create mode 100644 vlib/os/notify/backend_darwin.c.v create mode 100644 vlib/os/notify/kqueue.h diff --git a/vlib/os/notify/backend_darwin.c.v b/vlib/os/notify/backend_darwin.c.v new file mode 100644 index 0000000000..5f0e213475 --- /dev/null +++ b/vlib/os/notify/backend_darwin.c.v @@ -0,0 +1,230 @@ +module notify + +import time +import os + +#insert "@VEXEROOT/vlib/os/notify/kqueue.h" + +struct C.kevent { +mut: + ident u32 + filter i16 + flags u16 + fflags u32 + data int + udata voidptr +} + +fn C.kqueue() int +fn C.__kevent__(int, voidptr, int, voidptr, int, voidptr) int +fn C.EV_SET(voidptr, u32, i16, u16, u32, int, voidptr) + +// KqueueNotifier provides methods that implement FdNotifier using the +// kqueue I/O event notification facility (macos, freeBSD, xxxBSD...unix only) +[noinit] +struct KqueueNotifier { + kqueue_fd int +} + +// KqueueEvent describes an event that occurred for a file descriptor in +// the watch list +[noinit] +struct KqueueEvent { +pub: + fd int + kind FdEventType +} + +// new creates a new KqueueNotifier +// The FdNotifier interface is returned to allow OS specific +// implementations without exposing the concrete type +pub fn new() !FdNotifier { + fd := C.kqueue() + if fd == -1 { + return error(os.posix_get_error_msg(C.errno)) + } + // Needed to circumvent V limitations + x := &KqueueNotifier{ + kqueue_fd: fd + } + return x +} + +const ( + // filter types + kqueue_read = i16(C.EVFILT_READ) + kqueue_write = i16(C.EVFILT_WRITE) + kqueue_aio = i16(C.EVFILT_AIO) + kqueue_vnode = i16(C.EVFILT_VNODE) + kqueue_proc = i16(C.EVFILT_PROC) + kqueue_signal = i16(C.EVFILT_SIGNAL) + kqueue_timer = i16(C.EVFILT_TIMER) + kqueue_machport = i16(C.EVFILT_MACHPORT) + kqueue_fs = i16(C.EVFILT_FS) + kqueue_user = i16(C.EVFILT_USER) + kqueue_vm = i16(C.EVFILT_VM) + kqueue_exception = i16(C.EVFILT_EXCEPT) + kqueue_syscount = i16(C.EVFILT_SYSCOUNT) + + // actions + kqueue_add = u16(C.EV_ADD) + kqueue_delete = u16(C.EV_DELETE) + kqueue_enable = u16(C.EV_ENABLE) + kqueue_disable = u16(C.EV_DISABLE) + + // flags + kqueue_oneshot = u16(C.EV_ONESHOT) + kqueue_edge_trigger = u16(C.EV_CLEAR) // kqueue_clear + kqueue_receipt = u16(C.EV_RECEIPT) + kqueue_dispatch = u16(C.EV_DISPATCH) + kqueue_udata_specific = u16(C.EV_UDATA_SPECIFIC) + kqueue_dispatch2 = u16(C.EV_DISPATCH | C.EV_UDATA_SPECIFIC) + kqueue_vanished = u16(C.EV_VANISHED) + kqueue_sysflags = u16(C.EV_SYSFLAGS) + kqueue_flag0 = u16(C.EV_FLAG0) + kqueue_flag1 = u16(C.EV_FLAG1) + + // returned values + kqueue_eof = u16(C.EV_EOF) + kqueue_error = u16(C.EV_ERROR) +) + +// ctl is a helper method for add, modify, and remove +fn (mut kn KqueueNotifier) ctl(fd int, filter i16, flags u16) ! { + event := [1]C.kevent{} + C.EV_SET(&event[0], fd, filter, flags, 0, 0, unsafe { nil }) + if C.__kevent__(kn.kqueue_fd, &event[0], 1, unsafe { nil }, 0, unsafe { nil }) == -1 { + return error(os.posix_get_error_msg(C.errno)) + } +} + +// add adds a file descriptor to the watch list +fn (mut kn KqueueNotifier) add(fd int, events FdEventType, conf ...FdConfigFlags) ! { + filter := filter_to_mask(events) + flags := flags_to_mask(...conf) + kn.ctl(fd, filter, flags)! +} + +// modify sets an existing entry in the watch list to the provided events and configuration +fn (mut kn KqueueNotifier) modify(fd int, events FdEventType, conf ...FdConfigFlags) ! { + kn.add(fd, events, ...conf)! +} + +// remove removes a file descriptor from the watch list +fn (mut kn KqueueNotifier) remove(fd int) ! { + filter := notify.kqueue_read | notify.kqueue_write | notify.kqueue_exception + flags := notify.kqueue_delete + kn.ctl(fd, filter, flags)! +} + +// wait waits to be notified of events on the watch list, +// returns at most 512 events +fn (mut kn KqueueNotifier) wait(timeout time.Duration) []FdEvent { + // arbitrary 512 limit; events will round robin on successive + // waits if the number exceeds this + // NOTE: we use a fixed size array here for stack allocation; this has + // the added bonus of making KqueueNotifier thread safe + events := [512]C.kevent{} + // populate events with the new events + to := &C.timespec{0, timeout.nanoseconds()} + count := C.__kevent__(kn.kqueue_fd, unsafe { nil }, 0, &events[0], events.len, to) + + if count > 0 { + mut arr := []FdEvent{cap: count} + for i := 0; i < count; i++ { + fd := int(events[i].ident) + kind := event_mask_to_flag(events[i].filter, events[i].flags) + if kind.is_empty() { + // NOTE: tcc only reports the first event for some + // reason, leaving subsequent structs in the array as 0 + // (or possibly garbage) + panic('encountered an empty event kind; this is most likely due to using tcc') + } + arr << &KqueueEvent{ + fd: fd + kind: kind + } + } + return arr + } + return [] +} + +// close closes the KqueueNotifier, +// any successive calls to add, modify, remove, and wait should fail +fn (mut kn KqueueNotifier) close() ! { + if C.close(kn.kqueue_fd) == -1 { + return error(os.posix_get_error_msg(C.errno)) + } +} + +// event_mask_to_flag is a helper function that converts a bitmask +// returned by kevent() wait to FdEventType +fn event_mask_to_flag(filter i16, flags u16) FdEventType { + mut res := FdEventType.read + + if filter & notify.kqueue_read != 0 { + res.set(.read) + } + if filter & notify.kqueue_write != 0 { + res.set(.write) + } + if filter & notify.kqueue_exception != 0 { + res.set(.exception) + } + + if flags & notify.kqueue_eof != 0 { + res.set(.hangup) + } + if flags & notify.kqueue_error != 0 { + res.set(.error) + } + + return res +} + +// filter_to_mask is a helper function that converts FdEventType +// to a bitmask used by the C functions +fn filter_to_mask(events FdEventType) i16 { + mut mask := i16(0) + if events.has(.read) { + mask |= notify.kqueue_read + } + if events.has(.write) { + mask |= notify.kqueue_write + } + if events.has(.exception) { + mask |= notify.kqueue_exception + } + if events.has(.peer_hangup) { + panic("Kqueue does not support 'peer_hangup' event type.") + } + if events.has(.error) { + panic("Kqueue does not support 'error' event type.") + } + if events.has(.hangup) { + panic("Kqueue does not support 'hangup' event type.") + } + return mask +} + +// flags_to_mask is a helper function that converts FdConfigFlags +// to a bitmask used by the C functions +fn flags_to_mask(confs ...FdConfigFlags) u16 { + mut mask := notify.kqueue_add | notify.kqueue_enable + for conf in confs { + if conf.has(.edge_trigger) { + mask |= notify.kqueue_edge_trigger + } + if conf.has(.one_shot) { + mask |= notify.kqueue_oneshot + } + if conf.has(.wake_up) { + panic("Kqueue does not support 'wake_up' flag.") + } + if conf.has(.exclusive) { + panic("Kqueue does not support 'exclusive' flag.") + } + } + return mask +} diff --git a/vlib/os/notify/kqueue.h b/vlib/os/notify/kqueue.h new file mode 100644 index 0000000000..6f19b68b21 --- /dev/null +++ b/vlib/os/notify/kqueue.h @@ -0,0 +1,12 @@ +#ifndef __KQUEUE_H +#define __KQUEUE_H + +#include + +// Due to the renaming of 'struct kevent' and function 'kevent', +// they are wrapped here to avoid conflicts. +int __kevent__(int handle, const struct kevent* changelist, int nchanges, struct kevent* eventlist, int nevents, const struct timespec* timeout) { + return kevent(handle, changelist, nchanges, eventlist, nevents, timeout); +} + +#endif diff --git a/vlib/os/notify/notify_test.v b/vlib/os/notify/notify_test.v index 34205397c4..c00f638e29 100644 --- a/vlib/os/notify/notify_test.v +++ b/vlib/os/notify/notify_test.v @@ -5,7 +5,7 @@ import os.notify // make a pipe and return the (read, write) file descriptors fn make_pipe() !(int, int) { - $if linux { + $if linux || macos { pipefd := [2]int{} if C.pipe(&pipefd[0]) != 0 { return error('error ${C.errno}: ' + os.posix_get_error_msg(C.errno)) @@ -16,8 +16,8 @@ fn make_pipe() !(int, int) { } fn test_level_trigger() { - // currently only linux is supported - $if linux { + // currently only linux and macos are supported + $if linux || macos { mut notifier := notify.new()! reader, writer := make_pipe()! defer { @@ -37,8 +37,8 @@ fn test_level_trigger() { } fn test_edge_trigger() { - // currently only linux is supported - $if linux { + // currently only linux and macos are supported + $if linux || macos { mut notifier := notify.new()! reader, writer := make_pipe()! defer { @@ -53,7 +53,27 @@ fn test_edge_trigger() { os.fd_write(writer, 'foobar') check_read_event(mut n, reader, 'foo') - assert notifier.wait(0).len == 0 + $if linux { + assert notifier.wait(0).len == 0 + } + $if macos { + /* + In the kqueue of macos, EV_CLEAR flag represents a clear event, + which is mainly used for pipeline and socket class events. When this flag is set, + kqueue will trigger the corresponding event when the data is readable or writable, + but it is not guaranteed that the event will only be triggered once. + Compared to EPOLLET, EV_CLEAR's behavior varies. In epoll, the edge triggered mode only triggers + an event once when the state changes from unreadable/non writable to readable/writable, + that is, when the data changes from unreadable to readable, + or when the data changes from unreadable to writable. In the kqueue of macos, + EV_CLEAR does not possess this precise edge triggering behavior. + Therefore, in the kqueue of macos, even if the data is not completely read, + it is possible to continue triggering read events. This means that if you don't process all the data, + the next kqueue event notification may still be triggered + */ + + // notifier.wait(0).len == 1 or 0 + } os.fd_write(writer, 'baz') // we do not get an event because there is still data @@ -65,7 +85,7 @@ fn test_edge_trigger() { } fn test_one_shot() { - $if linux { + $if linux || macos { mut notifier := notify.new()! reader, writer := make_pipe()! defer { @@ -89,6 +109,7 @@ fn test_one_shot() { } } +// Kqueue does not support 'hangup' event type. fn test_hangup() { $if linux { mut notifier := notify.new()! @@ -112,7 +133,7 @@ fn test_hangup() { } fn test_write() { - $if linux { + $if linux || macos { mut notifier := notify.new()! reader, writer := make_pipe()! defer { @@ -133,7 +154,7 @@ fn test_write() { } fn test_remove() { - $if linux { + $if linux || macos { mut notifier := notify.new()! reader, writer := make_pipe()! defer { From e5cd1724f901dd11960c5faccc0a9c6f92073154 Mon Sep 17 00:00:00 2001 From: Turiiya <34311583+ttytm@users.noreply.github.com> Date: Sat, 5 Aug 2023 06:28:12 +0200 Subject: [PATCH 03/11] time: fix `'h'`, `'hh'` in `custom_format` for 12pm (#19058) --- vlib/time/custom_format_test.v | 54 ++++++++++++++++++++++++++++++++++ vlib/time/format.v | 6 ++-- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/vlib/time/custom_format_test.v b/vlib/time/custom_format_test.v index 79290fe084..0e706c18ce 100644 --- a/vlib/time/custom_format_test.v +++ b/vlib/time/custom_format_test.v @@ -9,3 +9,57 @@ fn test_custom_format() { println(date.custom_format(test_str)) } + +fn test_hh() { + assert time.parse('2023-08-04 00:00:45')!.custom_format('hh') == '00' + assert time.parse('2023-08-04 01:00:45')!.custom_format('hh') == '01' + assert time.parse('2023-08-04 02:00:45')!.custom_format('hh') == '02' + assert time.parse('2023-08-04 03:00:45')!.custom_format('hh') == '03' + assert time.parse('2023-08-04 04:00:45')!.custom_format('hh') == '04' + assert time.parse('2023-08-04 05:00:45')!.custom_format('hh') == '05' + assert time.parse('2023-08-04 06:00:45')!.custom_format('hh') == '06' + assert time.parse('2023-08-04 07:00:45')!.custom_format('hh') == '07' + assert time.parse('2023-08-04 08:00:45')!.custom_format('hh') == '08' + assert time.parse('2023-08-04 09:00:45')!.custom_format('hh') == '09' + assert time.parse('2023-08-04 10:00:45')!.custom_format('hh') == '10' + assert time.parse('2023-08-04 11:00:45')!.custom_format('hh') == '11' + assert time.parse('2023-08-04 12:00:45')!.custom_format('hh') == '12' + assert time.parse('2023-08-04 13:00:45')!.custom_format('hh') == '01' + assert time.parse('2023-08-04 14:00:45')!.custom_format('hh') == '02' + assert time.parse('2023-08-04 15:00:45')!.custom_format('hh') == '03' + assert time.parse('2023-08-04 16:00:45')!.custom_format('hh') == '04' + assert time.parse('2023-08-04 17:00:45')!.custom_format('hh') == '05' + assert time.parse('2023-08-04 18:00:45')!.custom_format('hh') == '06' + assert time.parse('2023-08-04 19:00:45')!.custom_format('hh') == '07' + assert time.parse('2023-08-04 20:00:45')!.custom_format('hh') == '08' + assert time.parse('2023-08-04 21:00:45')!.custom_format('hh') == '09' + assert time.parse('2023-08-04 22:00:45')!.custom_format('hh') == '10' + assert time.parse('2023-08-04 23:00:45')!.custom_format('hh') == '11' +} + +fn test_h() { + assert time.parse('2023-08-04 00:00:45')!.custom_format('h') == '0' + assert time.parse('2023-08-04 01:00:45')!.custom_format('h') == '1' + assert time.parse('2023-08-04 02:00:45')!.custom_format('h') == '2' + assert time.parse('2023-08-04 03:00:45')!.custom_format('h') == '3' + assert time.parse('2023-08-04 04:00:45')!.custom_format('h') == '4' + assert time.parse('2023-08-04 05:00:45')!.custom_format('h') == '5' + assert time.parse('2023-08-04 06:00:45')!.custom_format('h') == '6' + assert time.parse('2023-08-04 07:00:45')!.custom_format('h') == '7' + assert time.parse('2023-08-04 08:00:45')!.custom_format('h') == '8' + assert time.parse('2023-08-04 09:00:45')!.custom_format('h') == '9' + assert time.parse('2023-08-04 10:00:45')!.custom_format('h') == '10' + assert time.parse('2023-08-04 11:00:45')!.custom_format('h') == '11' + assert time.parse('2023-08-04 12:00:45')!.custom_format('h') == '12' + assert time.parse('2023-08-04 13:00:45')!.custom_format('h') == '1' + assert time.parse('2023-08-04 14:00:45')!.custom_format('h') == '2' + assert time.parse('2023-08-04 15:00:45')!.custom_format('h') == '3' + assert time.parse('2023-08-04 16:00:45')!.custom_format('h') == '4' + assert time.parse('2023-08-04 17:00:45')!.custom_format('h') == '5' + assert time.parse('2023-08-04 18:00:45')!.custom_format('h') == '6' + assert time.parse('2023-08-04 19:00:45')!.custom_format('h') == '7' + assert time.parse('2023-08-04 20:00:45')!.custom_format('h') == '8' + assert time.parse('2023-08-04 21:00:45')!.custom_format('h') == '9' + assert time.parse('2023-08-04 22:00:45')!.custom_format('h') == '10' + assert time.parse('2023-08-04 23:00:45')!.custom_format('h') == '11' +} diff --git a/vlib/time/format.v b/vlib/time/format.v index eceb8b6246..6fca0b0fb4 100644 --- a/vlib/time/format.v +++ b/vlib/time/format.v @@ -221,10 +221,12 @@ pub fn (t Time) custom_format(s string) string { sb.write_string('${t.hour:02}') } 'h' { - sb.write_string((t.hour % 12).str()) + h := if t.hour > 12 { t.hour - 12 } else { t.hour } + sb.write_string(h.str()) } 'hh' { - sb.write_string('${(t.hour % 12):02}') + h := if t.hour > 12 { t.hour - 12 } else { t.hour } + sb.write_string('${h:02}') } 'm' { sb.write_string(t.minute.str()) From f72cb00b74b84dcce11a2b8a55c30b2d30d9b2b2 Mon Sep 17 00:00:00 2001 From: yuyi Date: Sat, 5 Aug 2023 12:33:13 +0800 Subject: [PATCH 04/11] ast: fix formatting fn header with parameter comments (#19059) --- vlib/v/ast/str.v | 15 +++++++++++++++ .../fn_headers_with_param_comments_expected.vv | 8 ++++++++ .../tests/fn_headers_with_param_comments_input.vv | 8 ++++++++ 3 files changed, 31 insertions(+) create mode 100644 vlib/v/fmt/tests/fn_headers_with_param_comments_expected.vv create mode 100644 vlib/v/fmt/tests/fn_headers_with_param_comments_input.vv diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index c324a1ca46..5f0b32f6e2 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -133,6 +133,7 @@ pub fn (t &Table) stringify_fn_decl(node &FnDecl, cur_mod string, m2a map[string fn (t &Table) stringify_fn_after_name(node &FnDecl, mut f strings.Builder, cur_mod string, m2a map[string]string) { mut add_para_types := true + mut is_wrap_needed := false if node.generic_names.len > 0 { if node.is_method { sym := t.sym(node.params[0].typ) @@ -168,6 +169,20 @@ fn (t &Table) stringify_fn_after_name(node &FnDecl, mut f strings.Builder, cur_m is_type_only := param.name == '' should_add_type := true // is_last_param || is_type_only || node.params[i + 1].typ != param.typ || // (node.is_variadic && i == node.params.len - 2) + pre_comments := param.comments.filter(it.pos.pos < param.pos.pos) + if pre_comments.len > 0 { + if i == 0 && !pre_comments.last().is_inline { + is_wrap_needed = true + f.write_string('\n\t') + } + write_comments(pre_comments, mut f) + if !f.last_n(1)[0].is_space() { + f.write_string(' ') + } + } + if is_wrap_needed { + f.write_string('\t') + } if param.is_mut { f.write_string(param.typ.share().str() + ' ') } diff --git a/vlib/v/fmt/tests/fn_headers_with_param_comments_expected.vv b/vlib/v/fmt/tests/fn_headers_with_param_comments_expected.vv new file mode 100644 index 0000000000..ff17cb4ba9 --- /dev/null +++ b/vlib/v/fmt/tests/fn_headers_with_param_comments_expected.vv @@ -0,0 +1,8 @@ +fn foo( + // Foo + s string) { +} + +fn bar( /* p1 */ a string, /* p2 */ b int) { + println('hello') +} diff --git a/vlib/v/fmt/tests/fn_headers_with_param_comments_input.vv b/vlib/v/fmt/tests/fn_headers_with_param_comments_input.vv new file mode 100644 index 0000000000..05faa8bce3 --- /dev/null +++ b/vlib/v/fmt/tests/fn_headers_with_param_comments_input.vv @@ -0,0 +1,8 @@ +fn foo( + // Foo + s string) { +} + +fn bar(/*p1*/a string, /*p2*/b int) { + println('hello') +} From da7a9bc8aeeb4e2c2b8963c0e4a4fbf1bd0ad14c Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 5 Aug 2023 09:51:56 +0300 Subject: [PATCH 05/11] ci: run users.v ui example --- .github/workflows/macos_ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/macos_ci.yml b/.github/workflows/macos_ci.yml index fafc3c4507..9327af0971 100644 --- a/.github/workflows/macos_ci.yml +++ b/.github/workflows/macos_ci.yml @@ -97,6 +97,7 @@ jobs: mkdir -p ~/.vmodules ln -s $(pwd) ~/.vmodules/ui ../v examples/rectangles.v + ../v examples/users.v ## ../v run examples/build_examples.vsh - name: V self compilation with -usecache run: | From 8e26ca3f5a0768a428871baeb46f8836619cc51a Mon Sep 17 00:00:00 2001 From: Turiiya <34311583+ttytm@users.noreply.github.com> Date: Sat, 5 Aug 2023 12:58:16 +0200 Subject: [PATCH 06/11] time: fix `'a'` and `'A'` in `custom_format` (#19060) --- vlib/time/custom_format_test.v | 77 +++++++++++----------------------- vlib/time/format.v | 12 +++--- 2 files changed, 31 insertions(+), 58 deletions(-) diff --git a/vlib/time/custom_format_test.v b/vlib/time/custom_format_test.v index 0e706c18ce..6ce238f634 100644 --- a/vlib/time/custom_format_test.v +++ b/vlib/time/custom_format_test.v @@ -10,56 +10,29 @@ fn test_custom_format() { println(date.custom_format(test_str)) } -fn test_hh() { - assert time.parse('2023-08-04 00:00:45')!.custom_format('hh') == '00' - assert time.parse('2023-08-04 01:00:45')!.custom_format('hh') == '01' - assert time.parse('2023-08-04 02:00:45')!.custom_format('hh') == '02' - assert time.parse('2023-08-04 03:00:45')!.custom_format('hh') == '03' - assert time.parse('2023-08-04 04:00:45')!.custom_format('hh') == '04' - assert time.parse('2023-08-04 05:00:45')!.custom_format('hh') == '05' - assert time.parse('2023-08-04 06:00:45')!.custom_format('hh') == '06' - assert time.parse('2023-08-04 07:00:45')!.custom_format('hh') == '07' - assert time.parse('2023-08-04 08:00:45')!.custom_format('hh') == '08' - assert time.parse('2023-08-04 09:00:45')!.custom_format('hh') == '09' - assert time.parse('2023-08-04 10:00:45')!.custom_format('hh') == '10' - assert time.parse('2023-08-04 11:00:45')!.custom_format('hh') == '11' - assert time.parse('2023-08-04 12:00:45')!.custom_format('hh') == '12' - assert time.parse('2023-08-04 13:00:45')!.custom_format('hh') == '01' - assert time.parse('2023-08-04 14:00:45')!.custom_format('hh') == '02' - assert time.parse('2023-08-04 15:00:45')!.custom_format('hh') == '03' - assert time.parse('2023-08-04 16:00:45')!.custom_format('hh') == '04' - assert time.parse('2023-08-04 17:00:45')!.custom_format('hh') == '05' - assert time.parse('2023-08-04 18:00:45')!.custom_format('hh') == '06' - assert time.parse('2023-08-04 19:00:45')!.custom_format('hh') == '07' - assert time.parse('2023-08-04 20:00:45')!.custom_format('hh') == '08' - assert time.parse('2023-08-04 21:00:45')!.custom_format('hh') == '09' - assert time.parse('2023-08-04 22:00:45')!.custom_format('hh') == '10' - assert time.parse('2023-08-04 23:00:45')!.custom_format('hh') == '11' -} - -fn test_h() { - assert time.parse('2023-08-04 00:00:45')!.custom_format('h') == '0' - assert time.parse('2023-08-04 01:00:45')!.custom_format('h') == '1' - assert time.parse('2023-08-04 02:00:45')!.custom_format('h') == '2' - assert time.parse('2023-08-04 03:00:45')!.custom_format('h') == '3' - assert time.parse('2023-08-04 04:00:45')!.custom_format('h') == '4' - assert time.parse('2023-08-04 05:00:45')!.custom_format('h') == '5' - assert time.parse('2023-08-04 06:00:45')!.custom_format('h') == '6' - assert time.parse('2023-08-04 07:00:45')!.custom_format('h') == '7' - assert time.parse('2023-08-04 08:00:45')!.custom_format('h') == '8' - assert time.parse('2023-08-04 09:00:45')!.custom_format('h') == '9' - assert time.parse('2023-08-04 10:00:45')!.custom_format('h') == '10' - assert time.parse('2023-08-04 11:00:45')!.custom_format('h') == '11' - assert time.parse('2023-08-04 12:00:45')!.custom_format('h') == '12' - assert time.parse('2023-08-04 13:00:45')!.custom_format('h') == '1' - assert time.parse('2023-08-04 14:00:45')!.custom_format('h') == '2' - assert time.parse('2023-08-04 15:00:45')!.custom_format('h') == '3' - assert time.parse('2023-08-04 16:00:45')!.custom_format('h') == '4' - assert time.parse('2023-08-04 17:00:45')!.custom_format('h') == '5' - assert time.parse('2023-08-04 18:00:45')!.custom_format('h') == '6' - assert time.parse('2023-08-04 19:00:45')!.custom_format('h') == '7' - assert time.parse('2023-08-04 20:00:45')!.custom_format('h') == '8' - assert time.parse('2023-08-04 21:00:45')!.custom_format('h') == '9' - assert time.parse('2023-08-04 22:00:45')!.custom_format('h') == '10' - assert time.parse('2023-08-04 23:00:45')!.custom_format('h') == '11' +fn test_hours() { + assert time.parse('2023-08-04 00:00:45')!.custom_format('hh A h a') == '00 AM 0 am' + assert time.parse('2023-08-04 01:00:45')!.custom_format('hh A h a') == '01 AM 1 am' + assert time.parse('2023-08-04 02:00:45')!.custom_format('hh A h a') == '02 AM 2 am' + assert time.parse('2023-08-04 03:00:45')!.custom_format('hh A h a') == '03 AM 3 am' + assert time.parse('2023-08-04 04:00:45')!.custom_format('hh A h a') == '04 AM 4 am' + assert time.parse('2023-08-04 05:00:45')!.custom_format('hh A h a') == '05 AM 5 am' + assert time.parse('2023-08-04 06:00:45')!.custom_format('hh A h a') == '06 AM 6 am' + assert time.parse('2023-08-04 07:00:45')!.custom_format('hh A h a') == '07 AM 7 am' + assert time.parse('2023-08-04 08:00:45')!.custom_format('hh A h a') == '08 AM 8 am' + assert time.parse('2023-08-04 09:00:45')!.custom_format('hh A h a') == '09 AM 9 am' + assert time.parse('2023-08-04 10:00:45')!.custom_format('hh A h a') == '10 AM 10 am' + assert time.parse('2023-08-04 11:00:45')!.custom_format('hh A h a') == '11 AM 11 am' + assert time.parse('2023-08-04 12:00:45')!.custom_format('hh A h a') == '12 PM 12 pm' + assert time.parse('2023-08-04 13:00:45')!.custom_format('hh A h a') == '01 PM 1 pm' + assert time.parse('2023-08-04 14:00:45')!.custom_format('hh A h a') == '02 PM 2 pm' + assert time.parse('2023-08-04 15:00:45')!.custom_format('hh A h a') == '03 PM 3 pm' + assert time.parse('2023-08-04 16:00:45')!.custom_format('hh A h a') == '04 PM 4 pm' + assert time.parse('2023-08-04 17:00:45')!.custom_format('hh A h a') == '05 PM 5 pm' + assert time.parse('2023-08-04 18:00:45')!.custom_format('hh A h a') == '06 PM 6 pm' + assert time.parse('2023-08-04 19:00:45')!.custom_format('hh A h a') == '07 PM 7 pm' + assert time.parse('2023-08-04 20:00:45')!.custom_format('hh A h a') == '08 PM 8 pm' + assert time.parse('2023-08-04 21:00:45')!.custom_format('hh A h a') == '09 PM 9 pm' + assert time.parse('2023-08-04 22:00:45')!.custom_format('hh A h a') == '10 PM 10 pm' + assert time.parse('2023-08-04 23:00:45')!.custom_format('hh A h a') == '11 PM 11 pm' } diff --git a/vlib/time/format.v b/vlib/time/format.v index 6fca0b0fb4..a1914a72ee 100644 --- a/vlib/time/format.v +++ b/vlib/time/format.v @@ -308,17 +308,17 @@ pub fn (t Time) custom_format(s string) string { } } 'a' { - if t.hour > 12 { - sb.write_string('pm') - } else { + if t.hour < 12 { sb.write_string('am') + } else { + sb.write_string('pm') } } 'A' { - if t.hour > 12 { - sb.write_string('PM') - } else { + if t.hour < 12 { sb.write_string('AM') + } else { + sb.write_string('PM') } } else { From cc97b8df1e74c25fa0e4409b32fe6b9fb2409b56 Mon Sep 17 00:00:00 2001 From: Subhomoy Haldar Date: Sat, 5 Aug 2023 21:00:40 +0100 Subject: [PATCH 07/11] tools: add support for skiping lines in `v bump` (#19064) --- cmd/tools/vbump.v | 19 +++++++++-- cmd/tools/vbump_test.v | 66 ++++++++++++++++++++++++++++++++++++-- vlib/v/help/other/bump.txt | 16 ++++++--- 3 files changed, 91 insertions(+), 10 deletions(-) diff --git a/cmd/tools/vbump.v b/cmd/tools/vbump.v index d4e0afc46f..332544aa73 100644 --- a/cmd/tools/vbump.v +++ b/cmd/tools/vbump.v @@ -9,7 +9,7 @@ import semver const ( tool_name = os.file_name(os.executable()) - tool_version = '0.0.1' + tool_version = '0.1.0' tool_description = '\n Bump the semantic version of the v.mod and/or specified files. The first instance of a version number is replaced with the new version. @@ -21,6 +21,11 @@ const ( version: \'0.2.42\' VERSION = "1.23.8" + If certain lines need to be skipped, use the --skip option. For instance, + the following command will skip lines containing "tool-version": + + v bump --patch --skip "tool-version" [files...] + Examples: Bump the patch version in v.mod if it exists v bump --patch @@ -37,6 +42,7 @@ struct Options { major bool minor bool patch bool + skip string } type ReplacementFunction = fn (re regex.RE, input string, start int, end int) string @@ -85,7 +91,8 @@ fn process_file(input_file string, options Options) { } // Check if replacement is necessary - updated_line := if line.to_lower().contains('version') { + updated_line := if line.to_lower().contains('version') && !(options.skip != '' + && line.contains(options.skip)) { replacement_complete = true re.replace_by_fn(line, repl_fn) } else { @@ -141,6 +148,12 @@ Try ${tool_name} -h for more help...') patch: fp.bool('patch', `p`, false, 'Bump the patch version.') minor: fp.bool('minor', `n`, false, 'Bump the minor version.') major: fp.bool('major', `m`, false, 'Bump the major version.') + skip: fp.string('skip', `s`, '', 'Skip lines matching this substring.').trim_space() + } + + remaining := fp.finalize() or { + println(fp.usage()) + exit(1) } if options.show_help { @@ -150,7 +163,7 @@ Try ${tool_name} -h for more help...') validate_options(options) or { panic(err) } - files := os.args[3..] + files := remaining[1..] if files.len == 0 { if !os.exists('v.mod') { diff --git a/cmd/tools/vbump_test.v b/cmd/tools/vbump_test.v index e091b69358..c886ebaf39 100644 --- a/cmd/tools/vbump_test.v +++ b/cmd/tools/vbump_test.v @@ -78,7 +78,7 @@ fn run_individual_test(case BumpTestCase) ! { os.rm(test_file) or {} os.write_file(test_file, case.contents)! - // + os.execute_or_exit('${os.quoted_path(vexe)} bump --patch ${os.quoted_path(test_file)}') patch_lines := os.read_lines(test_file)! assert patch_lines[case.line] == case.expected_patch @@ -90,7 +90,7 @@ fn run_individual_test(case BumpTestCase) ! { os.execute_or_exit('${os.quoted_path(vexe)} bump --major ${os.quoted_path(test_file)}') major_lines := os.read_lines(test_file)! assert major_lines[case.line] == case.expected_major - // + os.rm(test_file)! } @@ -99,3 +99,65 @@ fn test_all_bump_cases() { run_individual_test(case) or { panic(err) } } } + +struct SkipTestCase { + file_name string + contents string + skip string + line int + expected_patch string + expected_minor string + expected_major string +} + +const skip_test_cases = [ + SkipTestCase{ + file_name: 'CITATION.cff' + contents: 'abstract: A sample CLI tool made in V that prints geometric shapes to the screen. +authors: + - alias: hungrybluedev + family-names: Haldar + given-names: Subhomoy +cff-version: 1.2.0 +date-released: 2023-04-20 +license: MIT +message: Please cite this software using these information. +repository-code: https://github.com/hungrybluedev/geo +title: geo +url: https://github.com/hungrybluedev/geo +version: 0.2.4 +' + line: 12 + skip: 'cff-version' + expected_patch: 'version: 0.2.5' + expected_minor: 'version: 0.3.0' + expected_major: 'version: 1.0.0' + }, +] + +fn run_skip_test(case SkipTestCase) ! { + test_file := os.join_path_single(tfolder, case.file_name) + + os.rm(test_file) or {} + os.write_file(test_file, case.contents)! + + os.execute_or_exit('${os.quoted_path(vexe)} bump --patch --skip="${case.skip}" ${os.quoted_path(test_file)}') + patch_lines := os.read_lines(test_file)! + assert patch_lines[case.line] == case.expected_patch + + os.execute_or_exit('${os.quoted_path(vexe)} bump --minor --skip="${case.skip}" ${os.quoted_path(test_file)}') + minor_lines := os.read_lines(test_file)! + assert minor_lines[case.line] == case.expected_minor + + os.execute_or_exit('${os.quoted_path(vexe)} bump --major --skip="${case.skip}" ${os.quoted_path(test_file)}') + major_lines := os.read_lines(test_file)! + assert major_lines[case.line] == case.expected_major + + os.rm(test_file)! +} + +fn test_all_skip_bump_cases() ! { + for case in skip_test_cases { + run_skip_test(case) or { panic(err) } + } +} diff --git a/vlib/v/help/other/bump.txt b/vlib/v/help/other/bump.txt index e4f9fe4c2f..a5a8e60e4c 100644 --- a/vlib/v/help/other/bump.txt +++ b/vlib/v/help/other/bump.txt @@ -11,7 +11,12 @@ recognized by the heuristic: tool_version = '1.2.1' version: '0.2.42' VERSION = "1.23.8" - + +If certain lines need to be skipped, use the --skip option. For instance, +the following command will skip lines containing "tool-version": + + v bump --patch --skip "tool-version" [files...] + Examples: Bump the patch version in v.mod if it exists v bump --patch @@ -22,7 +27,8 @@ Examples: Options: - -h, --help Show this help text. - -m, --major Bump the major version. - -n, --minor Bump the minor version. - -p, --patch Bump the patch version. + -h, --help Show this help text. + -m, --major Bump the major version. + -n, --minor Bump the minor version. + -p, --patch Bump the patch version. + -s, --skip Skip lines matching this substring. From b9a523cefd1bdb8d019dffa1f9d71ee3a8bea417 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 5 Aug 2023 23:41:23 +0300 Subject: [PATCH 08/11] time: store time with nanosecond resolution in time.Time, deprecate Time.microsecond, add utility methods and tests (#19062) --- examples/clock/clock.v | 4 +- vlib/time/README.md | 3 +- vlib/time/duration_test.v | 6 ++ vlib/time/format.v | 16 +++-- vlib/time/operator.v | 13 ++-- vlib/time/operator_test.v | 84 ++++++++++++------------- vlib/time/parse.c.v | 22 ++++--- vlib/time/parse_test.v | 18 +++--- vlib/time/time.c.v | 13 +--- vlib/time/time.v | 109 ++++++++++++++++++++++++--------- vlib/time/time_addition_test.v | 4 +- vlib/time/time_darwin.c.v | 41 +++++-------- vlib/time/time_nix.c.v | 16 ++--- vlib/time/time_solaris.c.v | 4 +- vlib/time/time_test.v | 77 +++++++++++++---------- vlib/time/time_windows.c.v | 16 ++--- vlib/time/unix.v | 18 +++++- 17 files changed, 265 insertions(+), 199 deletions(-) diff --git a/examples/clock/clock.v b/examples/clock/clock.v index 8d503907a4..bb2b198caf 100644 --- a/examples/clock/clock.v +++ b/examples/clock/clock.v @@ -74,12 +74,12 @@ fn on_frame(mut app App) { // draw minute hand mut j := f32(n.minute) if n.second == 59 { // make minute hand move smoothly - j += f32(math.sin(f32(n.microsecond) / 1e6 * math.pi / 2.0)) + j += f32(math.sin(f32(n.nanosecond) / 1e9 * math.pi / 2.0)) } draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.minute_hand, hand_color, j * 6) // draw second hand with smooth transition - k := f32(n.second) + f32(math.sin(f32(n.microsecond) / 1e6 * math.pi / 2.0)) + k := f32(n.second) + f32(math.sin(f32(n.nanosecond) / 1e9 * math.pi / 2.0)) draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.second_hand, second_hand_color, 0 + k * 6) diff --git a/vlib/time/README.md b/vlib/time/README.md index 39f4375d4a..b653cd8f62 100644 --- a/vlib/time/README.md +++ b/vlib/time/README.md @@ -28,7 +28,7 @@ const time_to_test = time.Time{ hour: 21 minute: 23 second: 42 - microsecond: 123456 + nanosecond: 123456789 unix: 332198622 } @@ -38,6 +38,7 @@ assert '1980-07-11 21:23' == time_to_test.format() assert '1980-07-11 21:23:42' == time_to_test.format_ss() assert '1980-07-11 21:23:42.123' == time_to_test.format_ss_milli() assert '1980-07-11 21:23:42.123456' == time_to_test.format_ss_micro() +assert '1980-07-11 21:23:42.123456789' == time_to_test.format_ss_nano() ``` You can also parse strings to produce time.Time values, diff --git a/vlib/time/duration_test.v b/vlib/time/duration_test.v index b4fb2d8397..9732c43ed7 100644 --- a/vlib/time/duration_test.v +++ b/vlib/time/duration_test.v @@ -31,3 +31,9 @@ fn test_duration_str() { assert time.Duration(1 * time.hour + 5 * time.second).str() == '1:00:05' assert time.Duration(168 * time.hour + 5 * time.minute + 7 * time.second).str() == '168:05:07' } + +fn test_duration_debug() { + assert time.Duration(1 * time.nanosecond).debug() == 'Duration: 1ns' + assert time.Duration(169 * time.hour + 5 * time.minute + 7 * time.second).debug() == 'Duration: 7days, 1h, 5m, 7s' + assert (-time.Duration(169 * time.hour + 5 * time.minute + 7 * time.second)).debug() == 'Duration: - 7days, 1h, 5m, 7s' +} diff --git a/vlib/time/format.v b/vlib/time/format.v index a1914a72ee..247b8e49b1 100644 --- a/vlib/time/format.v +++ b/vlib/time/format.v @@ -17,7 +17,7 @@ pub fn (t Time) format_ss() string { // format_ss_milli returns a date string in "YYYY-MM-DD HH:mm:ss.123" format (24h). pub fn (t Time) format_ss_milli() string { - return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.microsecond / 1000):03d}' + return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.nanosecond / 1_000_000):03d}' } // format_rfc3339 returns a date string in "YYYY-MM-DDTHH:mm:ss.123Z" format (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html) @@ -25,12 +25,17 @@ pub fn (t Time) format_ss_milli() string { // It is intended to improve consistency and interoperability, when representing and using date and time in Internet protocols. pub fn (t Time) format_rfc3339() string { u := t.local_to_utc() - return '${u.year:04d}-${u.month:02d}-${u.day:02d}T${u.hour:02d}:${u.minute:02d}:${u.second:02d}.${(u.microsecond / 1000):03d}Z' + return '${u.year:04d}-${u.month:02d}-${u.day:02d}T${u.hour:02d}:${u.minute:02d}:${u.second:02d}.${(u.nanosecond / 1_000_000):03d}Z' } // format_ss_micro returns a date string in "YYYY-MM-DD HH:mm:ss.123456" format (24h). pub fn (t Time) format_ss_micro() string { - return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${t.microsecond:06d}' + return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.nanosecond / 1_000):06d}' +} + +// format_ss_nano returns a date string in "YYYY-MM-DD HH:mm:ss.123456789" format (24h). +pub fn (t Time) format_ss_nano() string { + return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${t.nanosecond:09d}' } // hhmm returns a date string in "HH:mm" format (24h). @@ -381,8 +386,9 @@ pub fn (t Time) get_fmt_time_str(fmt_time FormatTime) string { .hhmm24 { '${t.hour:02d}:${t.minute:02d}' } .hhmmss12 { '${hour_}:${t.minute:02d}:${t.second:02d} ${tp}' } .hhmmss24 { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}' } - .hhmmss24_milli { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.microsecond / 1000):03d}' } - .hhmmss24_micro { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${t.microsecond:06d}' } + .hhmmss24_milli { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.nanosecond / 1_000_000):03d}' } + .hhmmss24_micro { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.nanosecond / 1_000):06d}' } + .hhmmss24_nano { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${t.nanosecond:06d}' } else { 'unknown enumeration ${fmt_time}' } } } diff --git a/vlib/time/operator.v b/vlib/time/operator.v index f62854983d..bee2ddf34d 100644 --- a/vlib/time/operator.v +++ b/vlib/time/operator.v @@ -3,19 +3,22 @@ module time // operator `==` returns true if provided time is equal to time [inline] pub fn (t1 Time) == (t2 Time) bool { - return t1.unix == t2.unix && t1.microsecond == t2.microsecond + return t1.unix == t2.unix && t1.nanosecond == t2.nanosecond } // operator `<` returns true if provided time is less than time [inline] pub fn (t1 Time) < (t2 Time) bool { - return t1.unix < t2.unix || (t1.unix == t2.unix && t1.microsecond < t2.microsecond) + return t1.unix < t2.unix || (t1.unix == t2.unix && t1.nanosecond < t2.nanosecond) } // Time subtract using operator overloading. [inline] pub fn (lhs Time) - (rhs Time) Duration { - lhs_micro := lhs.unix * 1_000_000 + lhs.microsecond - rhs_micro := rhs.unix * 1_000_000 + rhs.microsecond - return (lhs_micro - rhs_micro) * microsecond + // lhs.unix * 1_000_000_000 + i64(lhs.nanosecond) will overflow i64, for years > 3000 . + // Doing the diff first, and *then* multiplying by `second`, is less likely to overflow, + // since lhs and rhs will be likely close to each other. + unixs := i64(lhs.unix - rhs.unix) * second + nanos := lhs.nanosecond - rhs.nanosecond + return unixs + nanos } diff --git a/vlib/time/operator_test.v b/vlib/time/operator_test.v index 5f3e1b7a3e..f209cfda9a 100644 --- a/vlib/time/operator_test.v +++ b/vlib/time/operator_test.v @@ -39,7 +39,7 @@ fn test_time1_should_be_same_as_time2() { hour: 22 minute: 11 second: 3 - microsecond: 100 + nanosecond: 100 }) t2 := new_time(Time{ year: 2000 @@ -48,7 +48,7 @@ fn test_time1_should_be_same_as_time2() { hour: 22 minute: 11 second: 3 - microsecond: 100 + nanosecond: 100 }) assert t1 == t2 } @@ -61,9 +61,9 @@ fn test_time1_should_not_be_same_as_time2() { hour: 22 minute: 11 second: 3 - microsecond: 100 + nanosecond: 100 }) - // Difference is one microsecond + // Difference is one nanosecond t2 := new_time(Time{ year: 2000 month: 5 @@ -71,7 +71,7 @@ fn test_time1_should_not_be_same_as_time2() { hour: 22 minute: 11 second: 3 - microsecond: 101 + nanosecond: 101 }) t3 := new_time(Time{ year: 2000 @@ -80,7 +80,7 @@ fn test_time1_should_not_be_same_as_time2() { hour: 22 minute: 11 second: 3 - microsecond: 0 + nanosecond: 0 }) // Difference is one second t4 := new_time(Time{ @@ -90,7 +90,7 @@ fn test_time1_should_not_be_same_as_time2() { hour: 22 minute: 11 second: 4 - microsecond: 0 + nanosecond: 0 }) assert t1 != t2 assert t3 != t4 @@ -104,9 +104,9 @@ fn test_time1_should_be_greater_than_time2() { hour: 22 minute: 11 second: 3 - microsecond: 102 + nanosecond: 102 }) - // Difference is one microsecond + // Difference is one nanosecond t2 := new_time(Time{ year: 2000 month: 5 @@ -114,7 +114,7 @@ fn test_time1_should_be_greater_than_time2() { hour: 22 minute: 11 second: 3 - microsecond: 101 + nanosecond: 101 }) t3 := new_time(Time{ year: 2000 @@ -123,7 +123,7 @@ fn test_time1_should_be_greater_than_time2() { hour: 22 minute: 11 second: 5 - microsecond: 0 + nanosecond: 0 }) // Difference is one second t4 := new_time(Time{ @@ -133,7 +133,7 @@ fn test_time1_should_be_greater_than_time2() { hour: 22 minute: 11 second: 4 - microsecond: 0 + nanosecond: 0 }) assert t1 > t2 assert t3 > t4 @@ -147,9 +147,9 @@ fn test_time2_should_be_less_than_time1() { hour: 22 minute: 11 second: 3 - microsecond: 102 + nanosecond: 102 }) - // Difference is one microsecond + // Difference is one nanosecond t2 := new_time(Time{ year: 2000 month: 5 @@ -157,7 +157,7 @@ fn test_time2_should_be_less_than_time1() { hour: 22 minute: 11 second: 3 - microsecond: 101 + nanosecond: 101 }) t3 := new_time(Time{ year: 2000 @@ -166,7 +166,7 @@ fn test_time2_should_be_less_than_time1() { hour: 22 minute: 11 second: 3 - microsecond: 0 + nanosecond: 0 }) // Difference is one second t4 := new_time(Time{ @@ -176,7 +176,7 @@ fn test_time2_should_be_less_than_time1() { hour: 22 minute: 11 second: 2 - microsecond: 0 + nanosecond: 0 }) assert t2 < t1 assert t4 < t3 @@ -190,9 +190,9 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_gt() { hour: 22 minute: 11 second: 3 - microsecond: 102 + nanosecond: 102 }) - // Difference is one microsecond + // Difference is one nanosecond t2 := new_time(Time{ year: 2000 month: 5 @@ -200,7 +200,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_gt() { hour: 22 minute: 11 second: 3 - microsecond: 101 + nanosecond: 101 }) t3 := new_time(Time{ year: 2000 @@ -209,7 +209,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_gt() { hour: 22 minute: 11 second: 5 - microsecond: 0 + nanosecond: 0 }) // Difference is one second t4 := new_time(Time{ @@ -219,7 +219,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_gt() { hour: 22 minute: 11 second: 4 - microsecond: 0 + nanosecond: 0 }) assert t1 >= t2 assert t3 >= t4 @@ -233,9 +233,9 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_eq() { hour: 22 minute: 11 second: 3 - microsecond: 100 + nanosecond: 100 }) - // Difference is one microsecond + // Difference is one nanosecond t2 := new_time(Time{ year: 2000 month: 5 @@ -243,7 +243,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_eq() { hour: 22 minute: 11 second: 3 - microsecond: 100 + nanosecond: 100 }) t3 := new_time(Time{ year: 2000 @@ -252,7 +252,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_eq() { hour: 22 minute: 11 second: 3 - microsecond: 0 + nanosecond: 0 }) // Difference is one second t4 := new_time(Time{ @@ -262,7 +262,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_eq() { hour: 22 minute: 11 second: 3 - microsecond: 0 + nanosecond: 0 }) assert t1 >= t2 assert t3 >= t4 @@ -276,9 +276,9 @@ fn test_time1_should_be_less_or_equal_to_time2_when_lt() { hour: 22 minute: 11 second: 3 - microsecond: 100 + nanosecond: 100 }) - // Difference is one microsecond + // Difference is one nanosecond t2 := new_time(Time{ year: 2000 month: 5 @@ -286,7 +286,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_lt() { hour: 22 minute: 11 second: 3 - microsecond: 101 + nanosecond: 101 }) t3 := new_time(Time{ year: 2000 @@ -295,7 +295,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_lt() { hour: 22 minute: 11 second: 3 - microsecond: 0 + nanosecond: 0 }) // Difference is one second t4 := new_time(Time{ @@ -305,7 +305,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_lt() { hour: 22 minute: 11 second: 4 - microsecond: 0 + nanosecond: 0 }) assert t1 <= t2 assert t3 <= t4 @@ -319,9 +319,9 @@ fn test_time1_should_be_less_or_equal_to_time2_when_eq() { hour: 22 minute: 11 second: 3 - microsecond: 100 + nanosecond: 100 }) - // Difference is one microsecond + // Difference is one nanosecond t2 := new_time(Time{ year: 2000 month: 5 @@ -329,7 +329,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_eq() { hour: 22 minute: 11 second: 3 - microsecond: 100 + nanosecond: 100 }) t3 := new_time(Time{ year: 2000 @@ -338,7 +338,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_eq() { hour: 22 minute: 11 second: 3 - microsecond: 0 + nanosecond: 0 }) // Difference is one second t4 := new_time(Time{ @@ -348,7 +348,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_eq() { hour: 22 minute: 11 second: 3 - microsecond: 0 + nanosecond: 0 }) assert t1 <= t2 assert t3 <= t4 @@ -362,7 +362,7 @@ fn test_time2_copied_from_time1_should_be_equal() { hour: 22 minute: 11 second: 3 - microsecond: 100 + nanosecond: 100 }) t2 := new_time(t1) assert t2 == t1 @@ -370,8 +370,8 @@ fn test_time2_copied_from_time1_should_be_equal() { fn test_subtract() { d_seconds := 3 - d_microseconds := 13 - duration := d_seconds * second + d_microseconds * microsecond + d_nanoseconds := 13 + duration := d_seconds * second + d_nanoseconds * nanosecond t1 := new_time(Time{ year: 2000 month: 5 @@ -379,9 +379,9 @@ fn test_subtract() { hour: 22 minute: 11 second: 3 - microsecond: 100 + nanosecond: 100 }) - t2 := unix2(i64(t1.unix) + d_seconds, t1.microsecond + d_microseconds) + t2 := unix_nanosecond(i64(t1.unix) + d_seconds, t1.nanosecond + d_nanoseconds) d1 := t2 - t1 d2 := t1 - t2 assert d1 > 0 diff --git a/vlib/time/parse.c.v b/vlib/time/parse.c.v index d4b7fe97df..775d3fef23 100644 --- a/vlib/time/parse.c.v +++ b/vlib/time/parse.c.v @@ -35,13 +35,13 @@ pub fn parse_rfc3339(s string) !Time { } // Check if sn is time only if !parts[0].contains('-') && parts[0].contains(':') { - mut hour_, mut minute_, mut second_, mut microsecond_, mut unix_offset, mut is_local_time := 0, 0, 0, 0, i64(0), true - hour_, minute_, second_, microsecond_, unix_offset, is_local_time = parse_iso8601_time(parts[0])! + mut hour_, mut minute_, mut second_, mut microsecond_, mut nanosecond_, mut unix_offset, mut is_local_time := 0, 0, 0, 0, 0, i64(0), true + hour_, minute_, second_, microsecond_, nanosecond_, unix_offset, is_local_time = parse_iso8601_time(parts[0])! t = new_time(Time{ hour: hour_ minute: minute_ second: second_ - microsecond: microsecond_ + nanosecond: nanosecond_ }) if is_local_time { return t // Time is already local time @@ -52,7 +52,7 @@ pub fn parse_rfc3339(s string) !Time { } else if unix_offset > 0 { unix_time += unix_offset } - t = unix2(i64(unix_time), t.microsecond) + t = unix_nanosecond(i64(unix_time), t.nanosecond) return t } @@ -171,9 +171,9 @@ pub fn parse_iso8601(s string) !Time { return error_invalid_time(12, 'malformed date') } year, month, day := parse_iso8601_date(parts[0])! - mut hour_, mut minute_, mut second_, mut microsecond_, mut unix_offset, mut is_local_time := 0, 0, 0, 0, i64(0), true + mut hour_, mut minute_, mut second_, mut microsecond_, mut nanosecond_, mut unix_offset, mut is_local_time := 0, 0, 0, 0, 0, i64(0), true if parts.len == 2 { - hour_, minute_, second_, microsecond_, unix_offset, is_local_time = parse_iso8601_time(parts[1])! + hour_, minute_, second_, microsecond_, nanosecond_, unix_offset, is_local_time = parse_iso8601_time(parts[1])! } mut t := new_time( year: year @@ -182,7 +182,7 @@ pub fn parse_iso8601(s string) !Time { hour: hour_ minute: minute_ second: second_ - microsecond: microsecond_ + nanosecond: nanosecond_ ) if is_local_time { return t // Time already local time @@ -193,7 +193,7 @@ pub fn parse_iso8601(s string) !Time { } else if unix_offset > 0 { unix_time += unix_offset } - t = unix2(i64(unix_time), t.microsecond) + t = unix_nanosecond(i64(unix_time), t.nanosecond) return t } @@ -237,7 +237,7 @@ fn parse_iso8601_date(s string) !(int, int, int) { return year, month, day } -fn parse_iso8601_time(s string) !(int, int, int, int, i64, bool) { +fn parse_iso8601_time(s string) !(int, int, int, int, int, i64, bool) { hour_ := 0 minute_ := 0 second_ := 0 @@ -281,6 +281,7 @@ fn parse_iso8601_time(s string) !(int, int, int, int, i64, bool) { if count < 4 { return error_invalid_time(10, 'malformed date') } + nanosecond_ = microsecond_ * 1000 } is_local_time := plus_min_z == `a` && count == 4 is_utc := plus_min_z == `Z` && count == 5 @@ -300,5 +301,6 @@ fn parse_iso8601_time(s string) !(int, int, int, int, i64, bool) { if plus_min_z == `+` { unix_offset *= -1 } - return hour_, minute_, second_, microsecond_, unix_offset, is_local_time + // eprintln('parse_iso8601_time s: $s | hour_: $hour_ | minute_: $minute_ | second_: $second_ | microsecond_: $microsecond_ | nanosecond_: $nanosecond_ | unix_offset: $unix_offset | is_local_time: $is_local_time') + return hour_, minute_, second_, microsecond_, nanosecond_, unix_offset, is_local_time } diff --git a/vlib/time/parse_test.v b/vlib/time/parse_test.v index 071adc5d5b..b58766e077 100644 --- a/vlib/time/parse_test.v +++ b/vlib/time/parse_test.v @@ -65,11 +65,11 @@ fn test_parse_iso8601() { ] times := [ [2020, 6, 5, 15, 38, 6, 0], - [2020, 6, 5, 15, 38, 6, 15959], - [2020, 6, 5, 15, 38, 6, 15959], - [2020, 6, 5, 13, 38, 6, 15959], - [2020, 6, 5, 17, 38, 6, 15959], - [2020, 11, 5, 15, 38, 6, 15959], + [2020, 6, 5, 15, 38, 6, 15959000], + [2020, 6, 5, 15, 38, 6, 15959000], + [2020, 6, 5, 13, 38, 6, 15959000], + [2020, 6, 5, 17, 38, 6, 15959000], + [2020, 11, 5, 15, 38, 6, 15959000], ] for i, format in formats { t := time.parse_iso8601(format) or { @@ -89,8 +89,8 @@ fn test_parse_iso8601() { assert t.minute == minute second := times[i][5] assert t.second == second - microsecond := times[i][6] - assert t.microsecond == microsecond + nanosecond := times[i][6] + assert t.nanosecond == nanosecond } } @@ -107,7 +107,7 @@ fn test_parse_iso8601_local() { assert t.hour == 15 assert t.minute == 38 assert t.second == 6 - assert t.microsecond == 15959 + assert t.nanosecond == 15959_000 } fn test_parse_iso8601_invalid() { @@ -145,7 +145,7 @@ fn test_parse_iso8601_date_only() { assert t.hour == 0 assert t.minute == 0 assert t.second == 0 - assert t.microsecond == 0 + assert t.nanosecond == 0 } fn check_invalid_date(s string) { diff --git a/vlib/time/time.c.v b/vlib/time/time.c.v index ce1a72d53c..34c52d5f48 100644 --- a/vlib/time/time.c.v +++ b/vlib/time/time.c.v @@ -53,13 +53,6 @@ pub fn utc() Time { return solaris_utc() } return linux_utc() - /* - // defaults to most common feature, the microsecond precision is not available - // in this API call - t := C.time(0) - _ = C.time(&t) - return unix2(i64(t), 0) - */ } // new_time returns a time struct with the calculated Unix time. @@ -90,7 +83,7 @@ pub fn ticks() i64 { } $else { ts := C.timeval{} C.gettimeofday(&ts, 0) - return i64(ts.tv_sec * u64(1000) + (ts.tv_usec / u64(1000))) + return i64(ts.tv_sec * u64(1000) + (ts.tv_usec / u64(1_000))) } // t := i64(C.mach_absolute_time()) // # Nanoseconds elapsedNano = AbsoluteToNanoseconds( *(AbsoluteTime *) &t ); @@ -105,7 +98,7 @@ pub fn (t Time) str() string { } // convert_ctime converts a C time to V time. -fn convert_ctime(t C.tm, microsecond int) Time { +fn convert_ctime(t C.tm, nanosecond int) Time { return Time{ year: t.tm_year + 1900 month: t.tm_mon + 1 @@ -113,7 +106,7 @@ fn convert_ctime(t C.tm, microsecond int) Time { hour: t.tm_hour minute: t.tm_min second: t.tm_sec - microsecond: microsecond + nanosecond: nanosecond unix: make_unix_time(t) // for the actual code base when we // call convert_ctime, it is always diff --git a/vlib/time/time.v b/vlib/time/time.v index 4407c08cc9..7a0bd68f0d 100644 --- a/vlib/time/time.v +++ b/vlib/time/time.v @@ -40,15 +40,17 @@ pub const ( // Time contains various time units for a point in time. pub struct Time { pub: - year int - month int - day int - hour int - minute int - second int - microsecond int - unix i64 - is_local bool // used to make time.now().local().local() == time.now().local() + year int + month int + day int + hour int + minute int + second int + nanosecond int + unix i64 + is_local bool // used to make time.now().local().local() == time.now().local() + // + microsecond int [deprecated: 'use t.nanosecond / 1000 instead'; deprecated_after: '2023-08-05'] } // FormatDelimiter contains different time formats. @@ -59,6 +61,7 @@ pub enum FormatTime { hhmmss24 hhmmss24_milli hhmmss24_micro + hhmmss24_nano no_time } @@ -99,7 +102,7 @@ pub fn (t Time) smonth() string { return time.months_string[i * 3..(i + 1) * 3] } -// unix_time returns the UNIX time. +// unix_time returns the UNIX time with second resolution. [inline] pub fn (t Time) unix_time() i64 { return t.unix @@ -108,18 +111,39 @@ pub fn (t Time) unix_time() i64 { // unix_time_milli returns the UNIX time with millisecond resolution. [inline] pub fn (t Time) unix_time_milli() i64 { - return t.unix * 1000 + (t.microsecond / 1000) + return t.unix * 1_000 + (i64(t.nanosecond) / 1_000_000) +} + +// unix_time_micro returns the UNIX time with microsecond resolution. +[inline] +pub fn (t Time) unix_time_micro() i64 { + return t.unix * 1_000_000 + (i64(t.nanosecond) / 1_000) +} + +// unix_time_nano returns the UNIX time with nanosecond resolution. +[inline] +pub fn (t Time) unix_time_nano() i64 { + // TODO: use i128 here, when V supports it, since the following expression overflows for years like 3001: + return t.unix * 1_000_000_000 + i64(t.nanosecond) } // add returns a new time with the given duration added. pub fn (t Time) add(d Duration) Time { - microseconds := i64(t.unix) * 1_000_000 + t.microsecond + d.microseconds() - unix := microseconds / 1_000_000 - micro := microseconds % 1_000_000 - if t.is_local { - return unix2(unix, int(micro)).as_local() + // This expression overflows i64 for big years (and we do not have i128 yet): + // nanos := t.unix * 1_000_000_000 + i64(t.nanosecond) <- + // ... so instead, handle the addition manually in parts ¯\_(ツ)_/¯ + mut unixs := t.unix + mut nanos := i64(t.nanosecond) + d.nanoseconds() + unixs += nanos / time.second + nanos = nanos % time.second + if nanos < 0 { + unixs-- + nanos += time.second } - return unix2(unix, int(micro)) + if t.is_local { + return unix_nanosecond(unixs, int(nanos)).as_local() + } + return unix_nanosecond(unixs, int(nanos)) } // add_seconds returns a new time struct with an added number of seconds. @@ -311,9 +335,9 @@ pub fn days_in_month(month int, year int) !int { return res } -// debug returns detailed breakdown of time (`Time{ year: YYYY month: MM day: dd hour: HH: minute: mm second: ss microsecond: micros unix: unix }`) +// debug returns detailed breakdown of time (`Time{ year: YYYY month: MM day: dd hour: HH: minute: mm second: ss nanosecond: nanos unix: unix }`) pub fn (t Time) debug() string { - return 'Time{ year: ${t.year:04} month: ${t.month:02} day: ${t.day:02} hour: ${t.hour:02} minute: ${t.minute:02} second: ${t.second:02} microsecond: ${t.microsecond:06} unix: ${t.unix:07} }' + return 'Time{ year: ${t.year:04} month: ${t.month:02} day: ${t.day:02} hour: ${t.hour:02} minute: ${t.minute:02} second: ${t.second:02} nanosecond: ${t.nanosecond:09} unix: ${t.unix:07} }' } // A lot of these are taken from the Go library. @@ -326,6 +350,7 @@ pub const ( second = Duration(1000 * millisecond) minute = Duration(60 * second) hour = Duration(60 * minute) + // day = Duration(24 * hour) infinite = Duration(i64(9223372036854775807)) ) @@ -348,23 +373,22 @@ pub fn (d Duration) milliseconds() i64 { // consider all of them in sub-one intervals // seconds returns the duration as a floating point number of seconds. pub fn (d Duration) seconds() f64 { - sec := d / time.second - nsec := d % time.second - return f64(sec) + f64(nsec) / time.second + return f64(d) / f64(time.second) } // minutes returns the duration as a floating point number of minutes. pub fn (d Duration) minutes() f64 { - min := d / time.minute - nsec := d % time.minute - return f64(min) + f64(nsec) / time.minute + return f64(d) / f64(time.minute) } // hours returns the duration as a floating point number of hours. pub fn (d Duration) hours() f64 { - hr := d / time.hour - nsec := d % time.hour - return f64(hr) + f64(nsec) / time.hour + return f64(d) / f64(time.hour) +} + +// days returns the duration as a floating point number of days. +pub fn (d Duration) days() f64 { + return f64(d) / f64(time.hour * 24) } // str pretty prints the duration @@ -412,6 +436,35 @@ pub fn (d Duration) str() string { return '${ns}ns' } +// debug returns a detailed breakdown of the Duration, as: 'Duration: - 50days, 4h, 3m, 7s, 541ms, 78us, 9ns' +pub fn (d Duration) debug() string { + mut res := []string{} + mut x := i64(d) + mut sign := '' + if x < 0 { + sign = '- ' + x = -x + } + for label, v in { + 'days': 24 * time.hour + 'h': time.hour + 'm': time.minute + 's': time.second + 'ms': time.millisecond + 'us': time.microsecond + } { + if x > v { + xx := x / v + x = x % v + res << xx.str() + label + } + } + if x > 0 { + res << '${x}ns' + } + return 'Duration: ${sign}${res.join(', ')}' +} + // offset returns time zone UTC offset in seconds. pub fn offset() int { t := utc() diff --git a/vlib/time/time_addition_test.v b/vlib/time/time_addition_test.v index 0976262f48..5f5c7f11e9 100644 --- a/vlib/time/time_addition_test.v +++ b/vlib/time/time_addition_test.v @@ -3,8 +3,6 @@ import time fn test_add_to_day_in_the_previous_century() { a := time.parse_iso8601('1900-01-01')! aa := a.add_days(180) - dump(a.debug()) - dump(aa.debug()) assert aa.ymmdd() == '1900-06-30' } @@ -23,6 +21,8 @@ fn test_add_to_day_in_the_recent_past() { fn test_add_to_day_in_the_future_1() { a := time.parse_iso8601('3000-11-01')! aa := a.add_days(180) + dump(a.debug()) + dump(aa.debug()) assert aa.ymmdd() == '3001-04-30' } diff --git a/vlib/time/time_darwin.c.v b/vlib/time/time_darwin.c.v index 3de9657c0e..c4b9bf50ae 100644 --- a/vlib/time/time_darwin.c.v +++ b/vlib/time/time_darwin.c.v @@ -2,11 +2,10 @@ module time #include -const ( - // start_time is needed on Darwin and Windows because of potential overflows - start_time = C.mach_absolute_time() - time_base = init_time_base() -) +// start_time is needed on Darwin and Windows because of potential overflows +const start_time = C.mach_absolute_time() + +const time_base = init_time_base() [typedef] struct C.mach_timebase_info_data_t { @@ -25,11 +24,6 @@ struct InternalTimeBase { denom u32 = 1 } -pub struct C.timeval { - tv_sec u64 - tv_usec u64 -} - fn init_time_base() C.mach_timebase_info_data_t { tb := C.mach_timebase_info_data_t{} C.mach_timebase_info(&tb) @@ -62,29 +56,22 @@ fn vpc_now_darwin() u64 { return (tm - time.start_time) * time.time_base.numer / time.time_base.denom } -// darwin_now returns a better precision current time for Darwin based operating system -// this should be implemented with native system calls eventually -// but for now a bit tweaky. It uses the deprecated gettimeofday clock to get -// the microseconds seconds part and converts to local time +// darwin_now returns a better precision current time for macos fn darwin_now() Time { - // get the high precision time as UTC clock - tv := C.timeval{} - C.gettimeofday(&tv, 0) + // get the high precision time as UTC realtime clock, and use the nanoseconds part + mut ts := C.timespec{} + C.clock_gettime(C.CLOCK_REALTIME, &ts) loc_tm := C.tm{} - asec := voidptr(&tv.tv_sec) - C.localtime_r(asec, &loc_tm) - return convert_ctime(loc_tm, int(tv.tv_usec)) + C.localtime_r(voidptr(&ts.tv_sec), &loc_tm) + return convert_ctime(loc_tm, int(ts.tv_nsec)) } -// darwin_utc returns a better precision current time for Darwin based operating system -// this should be implemented with native system calls eventually -// but for now a bit tweaky. It uses the deprecated gettimeofday clock to get -// the microseconds seconds part and normal local time to get correct local time +// darwin_utc returns a better precision current time for macos fn darwin_utc() Time { // get the high precision time as UTC clock - tv := C.timeval{} - C.gettimeofday(&tv, 0) - return unix2(i64(tv.tv_sec), int(tv.tv_usec)) + mut ts := C.timespec{} + C.clock_gettime(C.CLOCK_REALTIME, &ts) + return unix_nanosecond(i64(ts.tv_sec), int(ts.tv_nsec)) } // dummy to compile with all compilers diff --git a/vlib/time/time_nix.c.v b/vlib/time/time_nix.c.v index 0b2d24b257..0065468bfd 100644 --- a/vlib/time/time_nix.c.v +++ b/vlib/time/time_nix.c.v @@ -36,7 +36,7 @@ pub fn (t Time) local() Time { } loc_tm := C.tm{} C.localtime_r(voidptr(&t.unix), &loc_tm) - return convert_ctime(loc_tm, t.microsecond) + return convert_ctime(loc_tm, t.nanosecond) } // in most systems, these are __quad_t, which is an i64 @@ -58,7 +58,7 @@ pub fn sys_mono_now() u64 { } $else { ts := C.timespec{} C.clock_gettime(C.CLOCK_MONOTONIC, &ts) - return u64(ts.tv_sec) * 1000000000 + u64(ts.tv_nsec) + return u64(ts.tv_sec) * 1_000_000_000 + u64(ts.tv_nsec) } } @@ -68,7 +68,7 @@ pub fn sys_mono_now() u64 { fn vpc_now() u64 { ts := C.timespec{} C.clock_gettime(C.CLOCK_MONOTONIC, &ts) - return u64(ts.tv_sec) * 1000000000 + u64(ts.tv_nsec) + return u64(ts.tv_sec) * 1_000_000_000 + u64(ts.tv_nsec) } // The linux_* functions are placed here, since they're used on Android as well @@ -83,7 +83,7 @@ fn linux_now() Time { C.clock_gettime(C.CLOCK_REALTIME, &ts) loc_tm := C.tm{} C.localtime_r(voidptr(&ts.tv_sec), &loc_tm) - return convert_ctime(loc_tm, int(ts.tv_nsec / 1000)) + return convert_ctime(loc_tm, int(ts.tv_nsec)) } fn linux_utc() Time { @@ -91,7 +91,7 @@ fn linux_utc() Time { // and use the nanoseconds part mut ts := C.timespec{} C.clock_gettime(C.CLOCK_REALTIME, &ts) - return unix2(i64(ts.tv_sec), int(ts.tv_nsec / 1000)) + return unix_nanosecond(i64(ts.tv_sec), int(ts.tv_nsec)) } // dummy to compile with all compilers @@ -104,12 +104,6 @@ fn win_utc() Time { return Time{} } -// dummy to compile with all compilers -pub struct C.timeval { - tv_sec u64 - tv_usec u64 -} - // return absolute timespec for now()+d pub fn (d Duration) timespec() C.timespec { mut ts := C.timespec{} diff --git a/vlib/time/time_solaris.c.v b/vlib/time/time_solaris.c.v index fd037f46dc..c977359515 100644 --- a/vlib/time/time_solaris.c.v +++ b/vlib/time/time_solaris.c.v @@ -10,7 +10,7 @@ fn solaris_now() Time { C.clock_gettime(C.CLOCK_REALTIME, &ts) loc_tm := C.tm{} C.localtime_r(voidptr(&ts.tv_sec), &loc_tm) - return convert_ctime(loc_tm, int(ts.tv_nsec / 1000)) + return convert_ctime(loc_tm, int(ts.tv_nsec)) } fn solaris_utc() Time { @@ -18,7 +18,7 @@ fn solaris_utc() Time { // and use the nanoseconds part mut ts := C.timespec{} C.clock_gettime(C.CLOCK_REALTIME, &ts) - return unix2(i64(ts.tv_sec), int(ts.tv_nsec / 1000)) + return unix_nanosecond(i64(ts.tv_sec), int(ts.tv_nsec)) } // dummy to compile with all compilers diff --git a/vlib/time/time_test.v b/vlib/time/time_test.v index f1caa2d9b4..23c13351c5 100644 --- a/vlib/time/time_test.v +++ b/vlib/time/time_test.v @@ -1,18 +1,16 @@ import time import math -const ( - time_to_test = time.Time{ - year: 1980 - month: 7 - day: 11 - hour: 21 - minute: 23 - second: 42 - microsecond: 123456 - unix: 332198622 - } -) +const time_to_test = time.Time{ + year: 1980 + month: 7 + day: 11 + hour: 21 + minute: 23 + second: 42 + nanosecond: 123456789 + unix: 332198622 +} fn test_is_leap_year() { // 1996 % 4 = 0 and 1996 % 100 > 0 @@ -83,6 +81,14 @@ fn test_unix() { assert t6.second == 29 } +fn test_format_rfc3339() { + // assert '1980-07-11T19:23:42.123Z' + res := time_to_test.format_rfc3339() + assert res.ends_with('23:42.123Z') + assert res.starts_with('1980-07-1') + assert res.contains('T') +} + fn test_format_ss() { assert '11.07.1980 21:23:42' == time_to_test.get_fmt_str(.dot, .hhmmss24, .ddmmyyyy) } @@ -93,20 +99,18 @@ fn test_format_ss_milli() { assert '1980-07-11 21:23:42.123' == time_to_test.format_ss_milli() } -fn test_format_rfc3339() { - // assert '1980-07-11T19:23:42.123Z' - res := time_to_test.format_rfc3339() - assert res.ends_with('23:42.123Z') - assert res.starts_with('1980-07-1') - assert res.contains('T') -} - fn test_format_ss_micro() { assert '11.07.1980 21:23:42.123456' == time_to_test.get_fmt_str(.dot, .hhmmss24_micro, .ddmmyyyy) assert '1980-07-11 21:23:42.123456' == time_to_test.format_ss_micro() } +fn test_format_ss_nano() { + assert '11.07.1980 21:23:42.123456789' == time_to_test.get_fmt_str(.dot, .hhmmss24_nano, + .ddmmyyyy) + assert '1980-07-11 21:23:42.123456789' == time_to_test.format_ss_nano() +} + fn test_smonth() { month_names := ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] @@ -180,21 +184,29 @@ fn test_weekday_str() { fn test_add() { d_seconds := 3 - d_microseconds := 13 - duration := time.Duration(d_seconds * time.second + d_microseconds * time.microsecond) + d_nanoseconds := 13 + duration := time.Duration(d_seconds * time.second + d_nanoseconds * time.nanosecond) + // dump(duration.debug()) t1 := time_to_test + // dump(t1.debug()) t2 := time_to_test.add(duration) + // dump(t2.debug()) assert t2.second == t1.second + d_seconds - assert t2.microsecond == t1.microsecond + d_microseconds + assert t2.nanosecond == t1.nanosecond + d_nanoseconds assert t2.unix == t1.unix + d_seconds assert t2.is_local == t1.is_local + // t3 := time_to_test.add(-duration) + // dump(t3.debug()) assert t3.second == t1.second - d_seconds - assert t3.microsecond == t1.microsecond - d_microseconds + assert t3.nanosecond == t1.nanosecond - d_nanoseconds assert t3.unix == t1.unix - d_seconds assert t3.is_local == t1.is_local + // t4 := time_to_test.as_local() + // dump(t4.debug()) t5 := t4.add(duration) + // dump(t5.debug()) assert t5.is_local == t4.is_local } @@ -220,13 +232,14 @@ fn test_now() { assert now.minute < 60 assert now.second >= 0 assert now.second <= 60 // <= 60 cause of leap seconds - assert now.microsecond >= 0 - assert now.microsecond < 1000000 + assert now.nanosecond >= 0 + assert now.nanosecond < time.second } fn test_utc() { now := time.utc() // The year the test was built + // dump(now.debug()) assert now.year >= 2020 assert now.month > 0 assert now.month <= 12 @@ -234,20 +247,20 @@ fn test_utc() { assert now.minute < 60 assert now.second >= 0 assert now.second <= 60 // <= 60 cause of leap seconds - assert now.microsecond >= 0 - assert now.microsecond < 1000000 + assert now.nanosecond >= 0 + assert now.nanosecond < time.second } fn test_unix_time() { t1 := time.utc() time.sleep(50 * time.millisecond) t2 := time.utc() - eprintln('t1: ${t1}') - eprintln('t2: ${t2}') + eprintln(' t1: ${t1}') + eprintln(' t2: ${t2}') ut1 := t1.unix_time() ut2 := t2.unix_time() - eprintln('ut1: ${ut1}') - eprintln('ut2: ${ut2}') + eprintln(' ut1: ${ut1}') + eprintln(' ut2: ${ut2}') assert ut2 - ut1 < 2 // utm1 := t1.unix_time_milli() diff --git a/vlib/time/time_windows.c.v b/vlib/time/time_windows.c.v index 0d76b4e8d1..7f7c69ea9c 100644 --- a/vlib/time/time_windows.c.v +++ b/vlib/time/time_windows.c.v @@ -39,6 +39,8 @@ fn C.SystemTimeToTzSpecificLocalTime(lpTimeZoneInformation &C.TIME_ZONE_INFORMAT fn C.localtime_s(t &C.time_t, tm &C.tm) +fn C.timespec_get(t &C.timespec, base int) int + const ( // start_time is needed on Darwin and Windows because of potential overflows start_time = init_win_time_start() @@ -107,7 +109,7 @@ pub fn (t Time) local() Time { hour: u16(t.hour) minute: u16(t.minute) second: u16(t.second) - millisecond: u16(t.microsecond / 1000) + millisecond: u16(t.nanosecond / 1_000_000) } st_local := SystemTime{} C.SystemTimeToTzSpecificLocalTime(unsafe { nil }, &st_utc, &st_local) @@ -118,7 +120,7 @@ pub fn (t Time) local() Time { hour: st_local.hour minute: st_local.minute second: st_local.second // These are the same - microsecond: int(st_local.millisecond) * 1000 + nanosecond: int(st_local.millisecond) * 1_000_000 unix: st_local.unix_time() } return t_local @@ -141,7 +143,7 @@ fn win_now() Time { hour: st_local.hour minute: st_local.minute second: st_local.second - microsecond: int(st_local.millisecond) * 1000 + nanosecond: int(st_local.millisecond) * 1_000_000 unix: st_local.unix_time() is_local: true } @@ -163,7 +165,7 @@ fn win_utc() Time { hour: st_utc.hour minute: st_utc.minute second: st_utc.second - microsecond: int(st_utc.millisecond) * 1000 + nanosecond: int(st_utc.millisecond) * 1_000_000 unix: st_utc.unix_time() is_local: false } @@ -213,12 +215,6 @@ fn solaris_utc() Time { return Time{} } -// dummy to compile with all compilers -pub struct C.timeval { - tv_sec u64 - tv_usec u64 -} - // sleep makes the calling thread sleep for a given duration (in nanoseconds). pub fn sleep(duration Duration) { C.Sleep(int(duration / millisecond)) diff --git a/vlib/time/unix.v b/vlib/time/unix.v index 7add150e43..b0e7403051 100644 --- a/vlib/time/unix.v +++ b/vlib/time/unix.v @@ -3,7 +3,7 @@ // that can be found in the LICENSE file. module time -// unix returns a time struct from Unix time. +// unix returns a time struct from an Unix timestamp (number of seconds since 1970-01-01) pub fn unix(abs i64) Time { // Split into day and time mut day_offset := abs / seconds_per_day @@ -24,8 +24,20 @@ pub fn unix(abs i64) Time { } } -// unix2 returns a time struct from Unix time and microsecond value +// unix2 returns a Time struct, given an Unix timestamp in seconds, and a microsecond value +[deprecated: 'use unix_microsecond(unix_ts, us) instead'] +[deprecated_after: '2023-09-05'] pub fn unix2(abs i64, microsecond int) Time { + return unix_nanosecond(abs, microsecond * 1000) +} + +// unix_microsecond returns a Time struct, given an Unix timestamp in seconds, and a microsecond value +pub fn unix_microsecond(abs i64, microsecond int) Time { + return unix_nanosecond(abs, microsecond * 1000) +} + +// unix_nanosecond returns a Time struct, given an Unix timestamp in seconds, and a nanosecond value +pub fn unix_nanosecond(abs i64, nanosecond int) Time { // Split into day and time mut day_offset := abs / seconds_per_day if abs % seconds_per_day < 0 { @@ -41,7 +53,7 @@ pub fn unix2(abs i64, microsecond int) Time { hour: hr minute: min second: sec - microsecond: microsecond + nanosecond: nanosecond unix: abs } } From 7ca23f6316ea4ee9e70ad8acb95ba5f11e34434f Mon Sep 17 00:00:00 2001 From: jhuntos Date: Sun, 6 Aug 2023 06:09:16 +0200 Subject: [PATCH 09/11] picoev: add initial values for struct field callbacks, to fix new compiler notices (#19065) Missing initial value for struct values --- vlib/picoev/picoev.v | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vlib/picoev/picoev.v b/vlib/picoev/picoev.v index d8e78d2a14..f8e1d92a2f 100644 --- a/vlib/picoev/picoev.v +++ b/vlib/picoev/picoev.v @@ -23,7 +23,7 @@ pub mut: fd int loop_id int = -1 events u32 - cb fn (int, int, voidptr) + cb fn (int, int, voidptr) = unsafe { nil } // used internally by the kqueue implementation backend int } @@ -31,7 +31,7 @@ pub mut: pub struct Config { pub: port int = 8080 - cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response) + cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response) = unsafe { nil } err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError) = default_err_cb user_data voidptr = unsafe { nil } timeout_secs int = 8 @@ -42,7 +42,7 @@ pub: [heap] pub struct Picoev { - cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response) + cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response) = unsafe { nil } err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError) = default_err_cb user_data voidptr = unsafe { nil } From c3f7fe39ec9c58889fc3e1b3319529cc343e0d59 Mon Sep 17 00:00:00 2001 From: yuyi Date: Sun, 6 Aug 2023 18:18:48 +0800 Subject: [PATCH 10/11] checker: fix struct field fntype value call (#19067) --- vlib/v/checker/fn.v | 2 +- vlib/v/tests/struct_field_fn_call_test.v | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 vlib/v/tests/struct_field_fn_call_test.v diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 7b2d2a7698..957c4252b2 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -1779,7 +1779,7 @@ fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type { // cannot hide interface expected type to make possible to pass its interface type automatically earg_types << if targ.idx() != param.typ.idx() { param.typ } else { targ } } else { - earg_types << targ + earg_types << param.typ } param_share := param.typ.share() if param_share == .shared_t diff --git a/vlib/v/tests/struct_field_fn_call_test.v b/vlib/v/tests/struct_field_fn_call_test.v new file mode 100644 index 0000000000..ba4eafb6e1 --- /dev/null +++ b/vlib/v/tests/struct_field_fn_call_test.v @@ -0,0 +1,18 @@ +struct Foo { + f fn (Foo) int = dummy +} + +fn dummy(s Foo) int { + return 22 +} + +fn (mut s Foo) fun() int { + return s.f(s) +} + +fn test_struct_field_fn_call() { + mut s := Foo{} + ret := s.fun() + println(ret) + assert ret == 22 +} From 23a7c40c44319105605e1cd2100535a0f5f3fb11 Mon Sep 17 00:00:00 2001 From: Rodrigo Villablanca Date: Sun, 6 Aug 2023 15:24:43 -0400 Subject: [PATCH 11/11] tools: remove panics in favor of errors in `v bump` (#19066) --- cmd/tools/vbump.v | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/cmd/tools/vbump.v b/cmd/tools/vbump.v index 332544aa73..6d57683d7a 100644 --- a/cmd/tools/vbump.v +++ b/cmd/tools/vbump.v @@ -73,10 +73,10 @@ fn get_replacement_function(options Options) ReplacementFunction { return replace_with_increased_patch_version } -fn process_file(input_file string, options Options) { - lines := os.read_lines(input_file) or { panic('Failed to read file: ${input_file}') } +fn process_file(input_file string, options Options) ! { + lines := os.read_lines(input_file) or { return error('Failed to read file: ${input_file}') } - mut re := regex.regex_opt(semver_query) or { panic('Could not create a RegEx parser.') } + mut re := regex.regex_opt(semver_query) or { return error('Could not create a RegEx parser.') } repl_fn := get_replacement_function(options) @@ -110,11 +110,11 @@ fn process_file(input_file string, options Options) { os.rm(backup_file) or {} // Rename the original to the backup. - os.mv(input_file, backup_file) or { panic('Failed to copy file: ${input_file}') } + os.mv(input_file, backup_file) or { return error('Failed to copy file: ${input_file}') } // Process the old file and write it back to the original. os.write_file(input_file, new_lines.join_lines()) or { - panic('Failed to write file: ${input_file}') + return error('Failed to write file: ${input_file}') } // Remove the backup file. @@ -129,7 +129,7 @@ fn process_file(input_file string, options Options) { fn main() { if os.args.len < 2 { - println('Usage: ${tool_name} [options] [file1 file2 ...] + eprintln('Usage: ${tool_name} [options] [file1 file2 ...] ${tool_description} Try ${tool_name} -h for more help...') exit(1) @@ -152,7 +152,7 @@ Try ${tool_name} -h for more help...') } remaining := fp.finalize() or { - println(fp.usage()) + eprintln(fp.usage()) exit(1) } @@ -161,24 +161,33 @@ Try ${tool_name} -h for more help...') exit(0) } - validate_options(options) or { panic(err) } + validate_options(options) or { + eprintln(fp.usage()) + exit(1) + } files := remaining[1..] if files.len == 0 { if !os.exists('v.mod') { - println('v.mod does not exist. You can create one using "v init".') + eprintln('v.mod does not exist. You can create one using "v init".') + exit(1) + } + process_file('v.mod', options) or { + eprintln('Failed to process v.mod: ${err}') exit(1) } - process_file('v.mod', options) } for input_file in files { if !os.exists(input_file) { - println('File not found: ${input_file}') + eprintln('File not found: ${input_file}') + exit(1) + } + process_file(input_file, options) or { + eprintln('Failed to process ${input_file}: ${err}') exit(1) } - process_file(input_file, options) } }