Adds a FieldSet type which defines which parts of a timestamp to merge into another in a new `mergeTime` function. With this two timestamps can be merged granularly, enabling more queries like `Sep 03 18:00` to return useful results (e.g., in this case, in the current year and location). Change-Id: I8db161608c5f68c8f9c61de8c6fa46067eced39b Reviewed-on: https://cl.tvl.fyi/c/depot/+/11601 Autosubmit: tazjin <tazjin@tvl.su> Reviewed-by: tazjin <tazjin@tvl.su> Tested-by: BuildkiteCI
		
			
				
	
	
		
			206 lines
		
	
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			206 lines
		
	
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package main
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| const usage = `usage: when <time>
 | |
| 
 | |
| This program converts the given time into various formats (currently a local
 | |
| timestamp, UTC timestamp, and UNIX epoch). It tries to guess what the input is.
 | |
| 
 | |
| Some valid queries:
 | |
| 
 | |
|   2024-01-05
 | |
|   1715079241
 | |
|   tomorrow 5PM
 | |
|   -22h
 | |
|   -7h10m
 | |
|   Mar 15
 | |
|   Sep 3 18:00
 | |
| 
 | |
| For now a single timestamp and a single duration (which is added either to the
 | |
| current time, or the given time) is supported.`
 | |
| 
 | |
| func printTime(t time.Time) {
 | |
| 	fmt.Println("Local:", t.Format("Mon 02 January 2006 at 15:04:05 MST"))
 | |
| 	fmt.Println("UTC:  ", t.UTC().Format(time.RFC3339))
 | |
| 	fmt.Println("UNIX: ", t.Unix())
 | |
| }
 | |
| 
 | |
| type FieldSet uint8
 | |
| 
 | |
| const (
 | |
| 	SetYear FieldSet = 1 << iota
 | |
| 	SetDay
 | |
| 	SetMonth
 | |
| 	SetHour
 | |
| 	SetMinute
 | |
| 	SetSecond
 | |
| 	SetLocation
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	SetDate  = SetYear | SetDay | SetMonth
 | |
| 	SetClock = SetHour | SetMinute | SetSecond
 | |
| )
 | |
| 
 | |
| // mergeTimes returns a new time.Time with all fields in this overridden with the
 | |
| // specified fields from that.
 | |
| func mergeTimes(this time.Time, that time.Time, set FieldSet) time.Time {
 | |
| 	year, month, day := this.Date()
 | |
| 	hour, min, sec := this.Clock()
 | |
| 	loc := this.Location()
 | |
| 
 | |
| 	if set&SetYear == SetYear {
 | |
| 		year = that.Year()
 | |
| 	}
 | |
| 	if set&SetMonth == SetMonth {
 | |
| 		month = that.Month()
 | |
| 	}
 | |
| 	if set&SetDay == SetDay {
 | |
| 		day = that.Day()
 | |
| 	}
 | |
| 	if set&SetHour == SetHour {
 | |
| 		hour = that.Hour()
 | |
| 	}
 | |
| 	if set&SetMinute == SetMinute {
 | |
| 		min = that.Minute()
 | |
| 	}
 | |
| 	if set&SetSecond == SetSecond {
 | |
| 		sec = that.Second()
 | |
| 	}
 | |
| 	if set&SetLocation == SetLocation {
 | |
| 		loc = that.Location()
 | |
| 	}
 | |
| 
 | |
| 	return time.Date(year, month, day, hour, min, sec, 0, loc)
 | |
| }
 | |
| 
 | |
| func parseTime(input string) (time.Time, error) {
 | |
| 	// try unix times
 | |
| 	if i, err := strconv.ParseInt(input, 10, 64); err == nil {
 | |
| 		if i < 9999999999 {
 | |
| 			return time.Unix(i, 0), nil
 | |
| 		}
 | |
| 		if i < 9999999999999 {
 | |
| 			return time.UnixMilli(i), nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// try simple date/time formats
 | |
| 	if t, err := time.Parse(time.DateOnly, input); err == nil {
 | |
| 		return t, nil
 | |
| 	}
 | |
| 
 | |
| 	if t, err := time.Parse(time.Kitchen, input); err == nil {
 | |
| 		now := time.Now()
 | |
| 		return mergeTimes(now, t, SetClock), nil
 | |
| 	}
 | |
| 
 | |
| 	if t, err := time.Parse(time.TimeOnly, input); err == nil {
 | |
| 		now := time.Now()
 | |
| 		return mergeTimes(now, t, SetClock), nil
 | |
| 	}
 | |
| 
 | |
| 	if t, err := time.Parse("15:04", input); err == nil {
 | |
| 		now := time.Now()
 | |
| 		return mergeTimes(now, t, SetClock), nil
 | |
| 	}
 | |
| 
 | |
| 	if t, err := time.Parse("3PM", input); err == nil {
 | |
| 		now := time.Now()
 | |
| 		return mergeTimes(now, t, SetClock), nil
 | |
| 	}
 | |
| 
 | |
| 	if t, err := time.Parse(time.DateTime, input); err == nil {
 | |
| 		return t, nil
 | |
| 	}
 | |
| 
 | |
| 	if t, err := time.Parse(time.Stamp, input); err == nil {
 | |
| 		now := time.Now()
 | |
| 		return mergeTimes(t, now, SetYear|SetLocation), nil
 | |
| 	}
 | |
| 
 | |
| 	if t, err := time.Parse("Jan _2 15:04", input); err == nil {
 | |
| 		now := time.Now()
 | |
| 		return mergeTimes(t, now, SetYear|SetLocation), nil
 | |
| 	}
 | |
| 
 | |
| 	if t, err := time.Parse("Jan _2", input); err == nil {
 | |
| 		now := time.Now()
 | |
| 		return mergeTimes(t, now, SetYear|SetLocation), nil
 | |
| 	}
 | |
| 
 | |
| 	return time.Time{}, fmt.Errorf("could not parse time: %q", input)
 | |
| }
 | |
| 
 | |
| func parseDuration(input string) (time.Duration, error) {
 | |
| 	// some simple rewriting
 | |
| 	switch input {
 | |
| 	case "yesterday":
 | |
| 		input = "-24h"
 | |
| 	case "tomorrow":
 | |
| 		input = "24h"
 | |
| 	case "today", "now":
 | |
| 		return time.Duration(0), nil
 | |
| 	}
 | |
| 
 | |
| 	// TODO: days, months, weeks, ...
 | |
| 	return time.ParseDuration(input)
 | |
| }
 | |
| 
 | |
| func main() {
 | |
| 	if len(os.Args) < 2 {
 | |
| 		fmt.Fprintln(os.Stderr, usage)
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 
 | |
| 	var d time.Duration
 | |
| 	var t time.Time
 | |
| 	var err error
 | |
| 	var haveTime, haveDuration bool
 | |
| 
 | |
| 	// Try to parse entire input as one full thing, before getting more
 | |
| 	// clever.
 | |
| 	if t, err = parseTime(strings.Join(os.Args[1:], " ")); err == nil {
 | |
| 		printTime(t)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	for _, arg := range os.Args[1:] {
 | |
| 		if !haveTime {
 | |
| 			if t, err = parseTime(arg); err == nil {
 | |
| 				haveTime = true
 | |
| 				continue
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if !haveDuration {
 | |
| 			if d, err = parseDuration(arg); err == nil {
 | |
| 				haveDuration = true
 | |
| 				continue
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if err != nil {
 | |
| 		fmt.Fprintln(os.Stderr, "Not sure what you want, try another time.")
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 
 | |
| 	if haveTime && haveDuration {
 | |
| 		printTime(t.Add(d))
 | |
| 	} else if haveTime {
 | |
| 		printTime(t)
 | |
| 	} else if haveDuration {
 | |
| 		printTime(time.Now().Add(d))
 | |
| 	} else {
 | |
| 		fmt.Fprintln(os.Stderr, "Not sure what you want, try another time.")
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| }
 |