Time formatting and converting from string - time

I'm quite new to Go and mainly work in C#. I am currently working on something that will be used as a command to get the next bus. Currently it does not compile because I am struggling with understanding how to use the time package in Go.
I have an array of strings formatted as times when it is scheduled and another for the minutes when it is a regular service like so (there are many more times these are just an example:
var ScheduledTimes = []string{"06:34", "06:54", "17:09", "17:19"}
var RegularTimes = []string{"05", "50"}
So what I am currently doing is getting the current time and checking if it is in regular service by doing this:
func isWithinRegularService(check time.Time) bool {
RegularStart, err := time.Parse(time.Kitchen, "10:05")
RegularEnd, err := time.Parse(time.Kitchen, "13:52")
return check.After(RegularStart) && check.Before(RegularEnd)
}
time := time.Now().UTC().Format("15:04")
if isWithinRegularService(time) { }
If it is in regular service, I will then determine what hour needs looking at (i.e this one or is the next service within the next hour)
if time.Minutes < 5 && time.Minutes >= 0 {
RegularTimes[0] = fmt.Sprintf("%s%s", time.Hour+1, RegularTimes[0])
RegularTimes[1] = fmt.Sprintf("%s%s", time.Hour+1, RegularTimes[1])
RegularTimes[2] = fmt.Sprintf("%s%s", time.Hour+1, RegularTimes[2])
RegularTimes[3] = fmt.Sprintf("%s%s", time.Hour+1, RegularTimes[3])
} else {
RegularTimes[0] = fmt.Sprintf("%s%s", time.Hour, RegularTimes[0])
RegularTimes[1] = fmt.Sprintf("%s%s", time.Hour, RegularTimes[1])
RegularTimes[2] = fmt.Sprintf("%s%s", time.Hour, RegularTimes[2])
RegularTimes[3] = fmt.Sprintf("%s%s", time.Hour, RegularTimes[3])
}
Before I pass that array to another func to give me the times to check between
type BusTime struct {
betweenStart string
betweenEnd string
}
func getBusTime(busTimes []array, iteration int) BusTime {
var timesToReturn BusTime
if iteration == busTimes.Count()-1 {
timesToReturn.betweenStart = busTimes[iteration]
timesToReturn.betweenEnd = busTimes[0]
} else {
timesToReturn.betweenStart = busTimes[iteration]
timesToReturn.betweenEnd = busTimes[iteration+1]
}
return timesToReturn
}
busTimes := getBusTime(quaylinkRegularTimes, i)
And lastly I then check if the time provided is between the times to compare:
check.After(start) && check.Before(end)
That is about it for this project. If it is not with regular service it will do the exact same but skip out determining what hour to look at.
Currently this does not build because I am using strings where I should be using times, I was hoping to get some guidance, some examples, and corrections on how to use times correctly to achieve what I'm looking to do. So far I believe my logic to the way this will flow is correct, but I'm stuck getting it to compile.

Since time.Time is a particular instant, at a particular location, using it for arbitrary clock time without a day or timezone can be awkward. What you're really looking for is the duration since 00:00, so a time.Duration makes more sense. You could even use your own type based on minutes since midnight for example, but if you use a time.Duration you'll be able to compose your times with time.Time more easily.
Here's an example function to parse your clock times, and minutes into a time.Duration
func ParseTime(t string) (time.Duration, error) {
var mins, hours int
var err error
parts := strings.SplitN(t, ":", 2)
switch len(parts) {
case 1:
mins, err = strconv.Atoi(parts[0])
if err != nil {
return 0, err
}
case 2:
hours, err = strconv.Atoi(parts[0])
if err != nil {
return 0, err
}
mins, err = strconv.Atoi(parts[1])
if err != nil {
return 0, err
}
default:
return 0, fmt.Errorf("invalid time: %s", t)
}
if mins > 59 || mins < 0 || hours > 23 || hours < 0 {
return 0, fmt.Errorf("invalid time: %s", t)
}
return time.Duration(hours)*time.Hour + time.Duration(mins)*time.Minute, nil
}
You can also combine this with your own type, using the same underlying int64 type, so that you can easily format the time.Duration and add your own methods. It's up to you if returning a Time or a time.Duration from ParseTime is more convenient. The two here are directly convertible.
type Time int64
func (t Time) Hours() int {
return int(time.Duration(t) / time.Hour)
}
func (t Time) Minutes() int {
return int((time.Duration(t) % time.Hour) / time.Minute)
}
func (t Time) String() string {
return fmt.Sprintf("%02d:%02d", t.Hours(), t.Minutes())
}
You could combine these like so: http://play.golang.org/p/E679m2wlUO

Related

How to update default values from http.Request elegantly?

I'm doing book gopl's exercise 1.12, Basically, the code need to update several default values from http.Request if it is present in URL parameters.
Say here is the code I'm working on:
var ( // Need to update those values if corresponding parameter present in URL
cycles = 5
res = 0.001
size = 100
)
I can do the updating one by one:
if c := r.FormValue("cycles"); c != "" { // r is a *http.Request
i, err := strconv.ParseInt(c); err != nil {
cycles = i
}
}
if r := r.FormValue("res"); r != "" {
if f, err := strconv.ParseFloat(r); err != nil {
res = f
}
}
// ...
But I'm not satisfied by this solution:
If I have dozens of params, this is very cumbersome
How to handle the conversion errors?
The repeating pattern seems requires a function, like this (I don't know how to implement it yet, just showing my thought)
func setParam(p interface{}, name string, r *http.Request) error {
if f := r.FormValue(name); f != "" {
switch p.(type) {
case int:
// strconv.ParseInt
case float64:
// strconv.PraseFloat
// ...
}
}
This looks better, but still cumbersome. I don't know if this is the best solution. Or I overlooked some feature in Go that should be used in this situation.
So, what's the idiomatic way to do this?
Write functions that get a form value as a specific type or return default when value is missing. Example:
func intValue(r *http.Request, name string, def int) (int, error) {
if _, ok := r.Form[name]; !ok {
return def, nil
}
return strconv.Atoi(r.FormValue(name))
}
Call these functions from your handler. This is repetitive like the code in the question, but combines variable declaration, default value and fetching value in a single line of code.
cycles, err := intValue(r, "cycles", 5)
if err != nil {
// TODO; handle bad value
}

How to write a function to check if current time is inside time window

I am trying to find a way to check if the current time is inside a time window.
The inputs are:
upgradeDay []string - a slice of days (for instance ["Sunday", "Tuesday"])
upgradetime string - hour:minute (for instance "22:04")
upgradeDuration int64 - amount of time, from the upgradetime at which the time window is valid. it can be up to 12 hours.
Full example:
upgradeDay = ["Sunday", Tuesday"] , upgradetime = "10:00", upgradeDuration = 2 -> the time windows is at every Sunday and Tuesday, from 10:00 to 12:00 o'clock.
I tried to write the following function, but it's not working in transition between days/months/years:
func isInsideTimeWindow(upgradeDay []string, upgradeTime string, upgradeDuration int64) bool {
now := time.Now()
ut := strings.Split(upgradeTime, ":")
hour, _ := strconv.Atoi(ut[0])
min, _ := strconv.Atoi(ut[1])
// !! not working when now it's Monday 00:01 and got: upgradeDay = ["Sunday"], upgradeTime = 23:59, upgradeDuration = 2
twStart := time.Date(now.Year(), now.Month(), now.Day(), hour, min, 0, 0, now.Location())
twEnd := twStart.Add(time.Hour * time.Duration(upgradeDuration))
if !(now.After(twStart) && now.Before(twEnd)) {
return false
}
wd := now.Weekday().String()
for i := range upgradeDay {
if upgradeDay[i] == wd {
return true
}
}
return false
}
Does someone got an idea on how to solve that in Go?
Here is one approach to the problem:
package main
import "time"
type window struct { time.Time }
func (w window) isDay(s string) bool {
return w.Weekday().String() == s
}
func (w window) isHourRange(begin, end int) bool {
return w.Hour() >= begin && w.Hour() <= end
}
func main() {
w := window{
time.Now(),
}
{
b := w.isDay("Friday")
println(b)
}
{
b := w.isHourRange(20, 23)
println(b)
}
}
This assume only one day is valid, so you would need to modify this to handle
multiple days. This should get you started though.
There is a lot of complexity in times. For instance:
What if an upgrade day is "Søndag" (Danish) instead of "Sunday"?
Should we work in local time, or UTC? If local, whose location counts? If the server is in London and I am in San Francisco, do we use the server's time, or my time?
If the upgrade interval includes 2 AM, does that count 2 AM PDT and then 2 AM PST as well? These times are one hour apart where I live. If the interval starts at 2 AM and ends at 2:59:59, that time does not exist on one day of the year in many areas with one hour DST shift.
If you get to ignore all these complexities—internationalization (i18n), localization (l10n), DST, and so on—there's still a bit of a problem with the fact that someone can set the date and time, or the upgrade itself might take some time, but usually we get to ignore these too.
Note that Go's time.Now() returns local time—but, whose location? As we have not yet answered the whose time zone to use question yet, we might want to avoid worrying about this. Given the rest of your input constraints, let's write a function to determine if a supplied time meets the input constraints, rather than if time.Now() does so. The caller can then provide either a UTC time or a wall-clock time in the user's location:
someNow = time.Time()
localNow = someNow.In(location) // from time.LoadLocation() or similar
We also have something that seems at odds with your types:
upgradeDuration int64 - amount of time, from the upgradetime at which the time window is valid. it can be up to 12 hours
A value in hours that is between 0 and 12 inclusive fits easily in plain int. Is this already a time.Duration value expressed in nanoseconds? If so, why is it int64 and not time.Duration? Or is it a value in seconds, and therefore can be between 0 and 43200? If so, it still fits in int.
I made a bunch of assumptions and came up with the following, which you can try out on the Go Playground.
package main
import (
"fmt"
"strconv"
"strings"
"time"
)
// startOK determines whether the given starting-time is within a
// time window.
//
// The window starts at a time given as two integers,
// h and m, representing hours and minutes, and extends for
// the given duration d in hours, which in general should not
// extend into another day. If it does extend past the end of
// the day into the next day, we ignore the extension.
//
// The days on which the time *is* in that window are further
// limited by the days[] slice of Weekday values.
//
// Note: it would probably be sensible to return a time.Duration
// value that is how long it will be until the next OK time, but
// we leave that as an exercise.
//
// It would also be sensible to allow the duration d to extend
// into the next day, which is also left as an exercise.
func startOK(when time.Time, days []time.Weekday, h, m, d int) bool {
// Find OK-to-start time, and end-time. If end exceeds
// 24*60, we ignore the extra end time, rather than
// allowing some minutes into the next day.
start := h*60 + m
end := start + d*60
// Convert when to hour-and-minute and see if we are
// in the allowed range.
wh, wm, _ := when.Clock()
now := wh*60 + wm
if now < start || now >= end {
// Not in hh:mm through hh+d:mm; say no.
return false
}
// The time-of-day is OK; check the day-of-week.
// We could do this earlier but by positioning it
// here, we leave room to check to see if it's
// the *next* day, if needed.
if !func(wd time.Weekday) bool {
for _, allowed := range days {
if wd == allowed {
return true
}
}
return false
}(when.Weekday()) {
return false // when.Weekday() not in days[]
}
// time is OK, day is OK
return true
}
// startOKstr is like startOK but the window starts at a time
// given as a string encoded as hh:mm, with the days being a
// slice of strings instead of Weekday. Because of these strings,
// parsing can produce an error, so this function has an error
// return.
func startOKStr(when time.Time, days []string, hhmm string, d int) (bool, error) {
parts := strings.Split(hhmm, ":")
// optional: be strict about two-digit values
if len(parts) != 2 {
return false, fmt.Errorf("invalid time string %q", hhmm)
}
h, err := strconv.Atoi(parts[0])
if err != nil {
return false, err
}
if h < 0 || h >= 60 {
return false, fmt.Errorf("invalid hour value %s", parts[0])
}
m, err := strconv.Atoi(parts[1])
if err != nil {
return false, err
}
if m < 0 || m >= 60 {
return false, fmt.Errorf("invalid minute value %s", parts[1])
}
var wd []time.Weekday
for _, s := range days {
w, err := parseWeekday(s)
if err != nil {
return false, err
}
wd = append(wd, w)
}
ok := startOK(when, wd, h, m, d)
return ok, nil
}
// parseWeekday handles weekday strings.
//
// Ideally we'd use time.Parse for this, as it already has
// these in it, but they are not exported in usable form.
func parseWeekday(s string) (time.Weekday, error) {
strToWeekday := map[string]time.Weekday{
"Sunday": time.Sunday,
"Monday": time.Monday,
"Tuesday": time.Tuesday,
"Wednesday": time.Wednesday,
"Thursday": time.Thursday,
"Friday": time.Friday,
"Saturday": time.Saturday,
}
if v, ok := strToWeekday[s]; ok {
return v, nil
}
return time.Sunday, fmt.Errorf("invalid day-of-week %q", s)
}
// tests should be converted to real tests and put in
// a separate file.
func tests() {
okDays := []string{"Sunday", "Wednesday"}
okStart := "04:00"
okDuration := 2 // hours
tfmt := "Mon Jan 2 15:04:05 2006"
t1 := "Sat Sep 5 04:30:00 2020" // time OK, day not
t2 := "Sun Sep 6 04:30:00 2020" // time OK, day OK
check := func(s string, expect bool) {
when, err := time.Parse(tfmt, s)
if err != nil {
panic(err)
}
result, err := startOKStr(when, okDays, okStart, okDuration)
if err != nil {
panic(err)
}
if result != expect {
fmt.Printf("fail: expected %v for %q\n", expect, s)
}
}
check(t1, false)
check(t2, true)
fmt.Println("2 tests run")
}
func main() {
tests()
}

TIme golang Telegram Bot

I have a telegram bot that helps users to notify if they late for work. So they write as I will come at 12:00 and I get string I have to check if the time they wrote is permitted.
For Example: If they write to me I will come at 12:00 but time.Now() is 11:00 so I give them error
The problem is how to compare their local time with time which they sent via bot if they located around the world and my bot server tunning in another country
How to get their local time?
I have 2 solution
Get user location via IP, GPS
use if, case statement
Do u have any other idea? I will grateful if u help me.
Code snippet to compare with my local time
func getArrivalTime(text string, status string) (time.Time, error) {
var arrivalTime time.Time
rule := regexp.MustCompile(`([0-9]|1[0-9]|2[0-3])((\:)|(\.))[0-5][0-9]`)
timeStr := rule.FindStringSubmatch(text)
if len(timeStr) == 0 {
log.Println("ERROR OCCURED")
return time.Time{}, errors.New("Wrong time format or omitted")
}
timeFormatted := strings.Replace(timeStr[0], ".", ":", 1)
date := fmt.Sprintf("%vT%v:00+00:00", time.Now().Format("2006-01-02"), timeFormatted)
arrivalTime, err := time.Parse(time.RFC3339, date)
if strings.Contains(text, "/завтра") && status == "late" {
arrivalTime = arrivalTime.Add(time.Hour * 24)
}
arrivalTimeUtc, _ := ConvertToUTC(arrivalTime)
nowUtc, _ := ConvertToUTC(time.Now())
nowUtc = nowUtc.Add(time.Hour * 5)
if arrivalTimeUtc.After(nowUtc) == false && status != "lateButInOffice" {
log.Println("ERROR OCCURED")
return time.Time{}, errors.New("Wrong time format or omitted")
}
if err != nil {
log.Println(err)
}
return arrivalTime, err
}

Missing milliseconds in the Go code performing redis operation

Below is the sample snippet for getting value from Redis. I'm pipeling 3 redis commands and getting the values. The problem here is "missing milliseconds". The time taken by redis pipeline is significantly lower ( less than 5ms) but the overall time taken to perform a Get Operation is more than 10ms. Not sure which operation is taking time, unmarshal is not the issue, as I measured the len(bytes) and timing. Any help is much appreciated.
Request/Second = 300, running on 3 AWS large instances with a powerful 25GB redis instance. Using 10 default connections.
func Get(params...) <-chan CacheResult {
start := time.Now()
var res CacheResult
defer func() {
resCh <- res
}()
type timers struct {
total time.Duration
pipeline time.Duration
unmarshal time.Duration
}
t := timers{}
startPipeTime := time.Now()
// pipe line commands
pipe := c.client.Pipeline()
// 3 commands pipelined (HGET, HEGT, GET)
if _, res.Err = pipe.Exec(); res.Err != nil && res.Err != redis.Nil {
return resCh
}
sinceStartPipeTime := time.Since(startPipeTime)
// get query values like below for HGET & GET
if val, res.Err = cachedValue.Bytes(); res.Err != nil {
return resCh
}
// Unmarshal the query value
startUnmarshalTime := time.Now()
var cv common.CacheValue
if res.Err = json.Unmarshal(val, &cv); res.Err != nil {
return resCh
}
sinceStartUnmarshalTime := time.Since(startUnmarshalTime)
t.unmarshal = sinceStartUnmarshalTime
endTime := time.Since(start)
xlog.Infof("Timings total:%s, "+
"pipeline(redis):%s, unmarshaling(%vB):%s", t.total, t.pipeline, len(val), t.unmarshal)
return resCh
}
Time to execute a redis command include:
App server pre-processing
Round trip time between app server and redis server
Redis server processing time
In normal operation, (2) takes the most significant time.

Array seems to lose values after end of for loop

I'm a beginner at go (and not a good programmer) but I wanted to write a small program which would dump from a switch the list of mac addresses & interfaces name using snmp. I store the snmp values into an array of struct using multiple loops (the code here is to show the behavior).
During the first loop, I store Ports Vlan id & mac addresses into an array of struct (var allTableArray [30]allTable). At the end of this loop, I print the content of the array to be sure the mac addresses are in the array.
But when the second loop begins (to register bridge port number), the array seems empty (fmt.Printf("deux %x\n",allTableArray[i].macAddr) and fmt.Printf("trois %s\n",allTableArray[i].ptVlan1id)).
I don't understand why my array seems empty. Do you have any idea ?
package main
import (
"flag"
"fmt"
"os"
"time"
"strings"
"github.com/soniah/gosnmp"
"math/big"
)
type oidMacAddr struct {
oid string
macaddr string
}
type allTable struct {
ptVlan1id string
macAddr []byte
brPortNb *big.Int
ifIndex *big.Int
ifName string
}
var macAddrTable [30]oidMacAddr
func main() {
flag.Parse()
if len(flag.Args()) < 1 {
flag.Usage()
os.Exit(1)
}
target := flag.Args()[0]
showMacAddrTable(target)
}
func printValue(pdu gosnmp.SnmpPDU) error {
fmt.Printf("%s = ", pdu.Name)
//fmt.Println(reflect.TypeOf(pdu.Value.([]byte)))
switch pdu.Type {
case gosnmp.OctetString:
b := pdu.Value.([]byte)
fmt.Printf("STRING: %x\n", b)
default:
fmt.Printf("TYPE %d: %d\n", pdu.Type, gosnmp.ToBigInt(pdu.Value))
}
return nil
}
func showMacAddrTable(target string) () {
var allTableArray [30]allTable
ptVlan1Oid := ".1.3.6.1.2.1.17.4.3.1.1"
brPortOid := ".1.3.6.1.2.1.17.4.3.1.2"
brPortIfIndex := ".1.3.6.1.2.1.17.1.4.1.2"
ifIndexIfName := ".1.3.6.1.2.1.31.1.1.1.1"
community := "public"
gosnmp.Default.Target = target
gosnmp.Default.Community = community
gosnmp.Default.Timeout = time.Duration(10 * time.Second) // Timeout better suited to walking
err := gosnmp.Default.Connect()
if err != nil {
fmt.Printf("Connect err: %v\n", err)
os.Exit(1)
}
var essai []gosnmp.SnmpPDU
essai, err = gosnmp.Default.BulkWalkAll(ptVlan1Oid)
if err != nil {
fmt.Printf("Walk Error: %v\n", err)
os.Exit(1)
}
for i :=0 ; i < len(essai); i++ {
s := strings.TrimPrefix(essai[i].Name, ".1.3.6.1.2.1.17.4.3.1.1")
fmt.Printf("%s = ", s)
fmt.Printf("%x\n", essai[i].Value.([]byte))
bytes := essai[i].Value.([]byte)
macAddrTable[i] = oidMacAddr {s, string(bytes)}
allTableArray[i] = allTable {ptVlan1id: s, macAddr: bytes}
if(allTableArray[i].macAddr != nil){
fmt.Printf("%x\n",allTableArray[i].macAddr)
}
}
essai, err = gosnmp.Default.BulkWalkAll(brPortOid)
if err != nil {
fmt.Printf("Walk Error: %v\n", err)
os.Exit(1)
}
for i:=0 ; i < len(essai); i++ {
s := strings.TrimPrefix(essai[i].Name, ".1.3.6.1.2.1.17.4.3.1.2")
fmt.Printf("%s = ", s)
fmt.Printf("%d\n", essai[i].Value)
for j:=0 ; j < len(allTableArray); j++ {
if (s == allTableArray[j].ptVlan1id) {
allTableArray[j] = allTable {brPortNb: gosnmp.ToBigInt(essai[i].Value) }
}
}
fmt.Printf("deux %x\n",allTableArray[i].macAddr)
fmt.Printf("trois %s\n",allTableArray[i].ptVlan1id)
}
os.Exit(1)
}
Apparently this line
allTableArray[j] = allTable {brPortNb: gosnmp.ToBigInt(essai[i].Value) }
Update each member with a new allTable instance, where every field other than brPortNb is not defined thus becomes nil.
If what you were trying to do is to update each member's brPortNb field, you could have done so by accessing the field and assign the value to it instead of assigning a new allTable to every member.
allTableArray[j].brPortNb = gosnmp.ToBigInt(essai[i].Value)
Also, try simplifying your loops like this, provided len(essai) == len(allTableArray):
for i, v := range essai {
s := strings.TrimPrefix(v.Name, ".1.3.6.1.2.1.17.4.3.1.1")
bytes := v.Value.([]byte)
macAddrTable[i] = oidMacAddr { s, string(bytes) }
allTableArray[i] = allTable { ptVlan1id: s, macAddr: bytes }
s = strings.TrimPrefix(v.Name, ".1.3.6.1.2.1.17.4.3.1.2")
if s == allTableArray[i].ptVlan1id {
allTableArray[i].brPortNb = gosnmp.ToBigInt(v.Value)
}
}
Notice that by using for i, v := range essai syntax, you have access to both the index and the value without having to use essai[i] for the value.
Now your two loops can become just one, plus no embedded loops which are really hard to make sense of.
I Also recommend you work with slice instead of array. It's more flexible that way.

Resources