String to Time
Code snippet to parse strings to a time struct, in a human friendly way.
Say you have a command that creates events (much like the -event create built-in command). You would like to accept a time when the event will begin. You could be very strict in your parsing and make the user input their time in a very specific format, but why not allow a variety of human-friendly input like tomorrow, tomorrow at 8:30pm, and so on? That's what this code snippet does.
Accepted Formats
Date:
- dd/mm/yyyyor- dd.mm.yyyyor- dd-mm-yyyyor- dd,mm,yyyy
- String format with year mentioned with 4 digits and both short and long month names supported. Date components (i.e day , month , year) need not be present together. eg: 12 Feb 11:50 am,2020are supported.
- todayand- tomorrowis supported.
TimeZone:
By default timezone is UTC. If user has timezone set using setz command, timezone adjustment is also possible. UTC time is parsed if explicitly specified UTC in this case.
Time:
Time is mentioned as hh:mm:ss or hh:mm or hh. May or may not be followed by AM or PM.
Code
{{/*
  Parses a string to a time struct, in a human-friendly way.
  See <https://yagpdb-cc.github.io/code-snippets/string2time> for more information.
  Author: Satty9361 <https://github.com/Satty9361>
*/}}
{{/* Let $timeString be the input string to be parsed. */}}
{{ $timeString := .StrippedMsg }}
{{/* Variable Declarations */}}
{{ $dTime := sdict "Day" currentTime.Day "Month" (toInt (printf "%d" currentTime.Month)) "Year" currentTime.Year  "Hour" currentTime.Hour "Min" currentTime.Minute "Sec" currentTime.Second }}
{{ $time := sdict "Day" $dTime.Day "Month" $dTime.Month "Year" $dTime.Year "Hour" 0 "Min" 0 "Sec" 0  }}
{{ $dateSet := false }} {{ $timeSet := false }}
{{ $timeConverted := 0 }}
{{ $months := sdict 
	"jan" 1
	"feb" 2
	"mar" 3
	"apr" 4
	"may" 5
	"jun" 6
	"jul" 7
	"aug" 8
	"sep" 9
	"oct" 10
	"nov" 11
	"dec" 12
 }}
{{/* Actual Code */}}
{{ $timeString = lower $timeString }}
{{/* Fetching Dates */}}
{{/* Fetching dates written in format dd.mm.yyyy or dd/mm/yyyy or dd-mm-yyyy or dd,mm,yyyy */}} 
{{ with reFindAllSubmatches `((\s|^)((\d{1,2})(\-|\.|\/|\,)(\d{1,2})(\-|\.|\/|\,)(\d{1,4}))(\s|$))` $timeString }}
	{{ $time.Set "Day" (toInt (index . 0 4)) }}
	{{ $time.Set "Month" (toInt (index . 0 6)) }}
	{{ $time.Set "Year" (toInt (index . 0 8)) }}
	{{ $dateSet = true }}
{{ else }}
{{/* Fetching dates written as a string with both long or short month names supported. Date , Month and Year need not be present together but year must be written in full form(with 4 digits) eg: 20 sept 1am ,2019 is supported */}}
	{{ with (reFindAllSubmatches `(?:[^a-z]|^)(jan((uary)?)|feb((ruary)?)|mar((ch)?)|apr((il)?)|may|jun(e?)|jul(y?)|aug((ust)?)|sep((t(ember)?)?)|oct((ober)?)|nov((ember)?)|dec((ember)?))(?:[^a-z]|$)` $timeString) }}
	{{ $time.Set "Month" ($months.Get (slice (index . 0 1) 0 3)) }}
	{{ $temp:= reReplace `(([^:]|^)((\d+)((:(\d+)){1,2}))((\s?(am|pm))?))|(((\d+))(\s?(am|pm)))` $timeString "" }}
	{{ with (reFindAllSubmatches `(?:\D|^)(\d{1,2})(?:\D|$)` $temp) }}
		{{ $time.Set "Day" (toInt (index . 0 1)) }}
	{{ end }}
	{{ with (reFindAllSubmatches `(?:\D|^)(\d{4})(?:\D|$)` $temp) }}
		{{ $time.Set "Year" (toInt (index . 0 1)) }}
	{{ end }}
	{{ $dateSet = true }}
	{{ end }}
{{ end }}
{{/* Fetching dates specified as today or tomorrow and assigning default values to invalid dates */}}
{{ if $dateSet }}
{{ if not $time.Day }}{{ $time.Set "Day" $dTime.Day }}{{ end }}
{{ if not $time.Month }}{{ $time.Set "Month" $dTime.Month }}{{ end }}
{{ if not $time.Year }}{{ $time.Set "Year" $dTime.Year }}{{ end }}
{{ else }}
	{{ with reFind `(today)|(tomorrow)` $timeString }}
		{{ if eq . "tomorrow" }}
			{{ $time.Set "Day" (add $dTime.Day 1) }}
		{{ end }}
	{{ end }}
{{ end }}
{{/* Fetching time specified as hh:mm or hh:mm:ss or hh. Can be followed by am/pm as well. */}}
{{ with reFind `(([^:]|^)((\d+)((:(\d+)){1,2}))((\s?(am|pm))?))|(((\d+))(\s?(am|pm)))` $timeString }}
	{{ with reFindAllSubmatches `(\d+)` . }}
		{{ $time.Set "Hour" (toInt (index . 0 0)) }}
		{{ if (gt (len .) 1) }}
			{{ $time.Set "Min" (toInt (index . 1 0) ) }}
		{{ end }}
		{{ if (gt (len .) 2) }}
			{{ $time.Set "Sec" (toInt (index . 2 0)) }}
		{{ end }}
	{{ end }}
	{{ with reFind `(am|pm)` . }}
		{{ if and (eq $time.Hour 12) (eq . "am") }}
			{{ $time.Set "Hour" 0 }}
		{{ else if and (eq . "pm" ) (lt $time.Hour 12) }}
			{{ $time.Set "Hour" (add $time.Hour 12) }}
		{{ end }}
	{{ end }}
	{{ $timeSet = true }}
{{ end }}
{{/* Setting time to current time when both explicit date and time setting was not done */}}
{{ if and (not $timeSet) (not $dateSet) }}
	{{ $time.Set "Hour" $dTime.Hour }}
	{{ $time.Set "Min" $dTime.Min }}
	{{ $time.Set "Sec" $dTime.Sec }}
{{ end }}
{{/* Conversion to time.Time datatype */}}
{{ $timeConverted = (newDate $time.Year $time.Month $time.Day $time.Hour $time.Min $time.Sec) }}
{{/*timezone adjustment - Remove if you only want UTC times*/}}
{{ if and (or $timeSet $dateSet) (not (reFind `([^a-z]|^)utc([^a-z]|$)` $timeString )) }}
	{{ $TimeHour := .TimeHour }}
	{{ with (reFind `(\-)?\d+(:\d+)?` (exec "setz -u") ) }}
		{{ $timeConverted = $timeConverted.Add (toDuration (mult -1.0 (toFloat (reReplace ":" . ".")) $TimeHour)) }}
	{{ end }}
{{ end }}
Usage
First, add the code snippet above:
{{/* code snippet goes here */}}
Next, change the value of $timeString to the value you want to parse. Say we wanted to use .Message.Content rather than .StrippedMsg:
{{/* Let $timeString be the input string to be parsed. */}}
- {{ $timeString := .StrippedMsg }}
+ {{ $timeString := .Message.Content }}
{{/* rest of code snippet goes here */}}
You can now reference the parsed time using $timeConverted, which will be a time.Time:
{{/* code snippet goes here */}}
Resulting time: {{$timeConverted}}
Author
This code snippet was written by @Satty9361.