diff --git a/README.md b/README.md index ae23c2e..c5219f1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ In another project, I decided to use cron expression syntax to encode scheduling The time-matching algorithm in this implementation is efficient, it avoids as much as possible to guess the next matching time stamp, a common technique seen in a number of implementations out there. -There is also a companion command-line utility to evaluate cron time expressions: (which of course uses this library). +There is also a companion command-line utility to evaluate cron time expressions: (which of course uses this library). Implementation -------------- diff --git a/cronexpr/README.md b/cronexpr/README.md new file mode 100644 index 0000000..685c278 --- /dev/null +++ b/cronexpr/README.md @@ -0,0 +1,122 @@ +cronexpr: command-line utility +============================== + +A command-line utility written in Go to evaluate cron time expressions. + +It is based on the standalone Go library . + +## Install + + go get github.com/gorhill/cronexpr + go install github.com/gorhill/cronexpr + +## Usage + + cronexpr [options] "{cron expression}" + +## Options + +`-l`: + +Go-compliant time layout to use for outputting time value(s), see . + +Default is `"Mon, 02 Jan 2006 15:04:05 MST"` + +`-n`: + +Number of resulting time values to output. + +Default is 1. + +`-t`: + +Whole or partial RFC3339 time value (i.e. `2006-01-02T15:04:05Z07:00`) against which the cron expression is evaluated. Examples of valid values include (assuming EST time zone): + +`13` = 2013-01-01T00:00:00-05:00 +`2013` = 2013-01-01T00:00:00-05:00 +`2013-08` = 2013-08-01T00:00:00-05:00 +`2013-08-31` = 2013-08-31T00:00:00-05:00 +`2013-08-31T12` = 2013-08-31T12:00:00-05:00 +`2013-08-31T12:40` = 2013-08-31T12:40:00-05:00 +`2013-08-31T12:40:35` = 2013-08-31T12:40:35-05:00 +`2013-08-31T12:40:35-10:00` = 2013-08-31T12:40:35-10:00 + +Default time is current time, and default time zone is local time zone. + +## Examples + +#### Example 1 + +Midnight on December 31st of any year. + +Command: + + cronexpr -t="2013-08-31" -n=5 "0 0 31 12 *" + +Output (assuming computer is in EST time zone): + + # "0 0 31 12 *" + "2013-08-31T00:00:00-04:00" = + Tue, 31 Dec 2013 00:00:00 EST + Wed, 31 Dec 2014 00:00:00 EST + Thu, 31 Dec 2015 00:00:00 EST + Sat, 31 Dec 2016 00:00:00 EST + Sun, 31 Dec 2017 00:00:00 EST + +#### Example 2 + +2pm on February 29th of any year. + +Command: + + cronexpr -t=2000 -n=10 "0 14 29 2 *" + +Output (assuming computer is in EST time zone): + + # "0 14 29 2 *" + "2000-01-01T00:00:00-05:00" = + Tue, 29 Feb 2000 14:00:00 EST + Sun, 29 Feb 2004 14:00:00 EST + Fri, 29 Feb 2008 14:00:00 EST + Wed, 29 Feb 2012 14:00:00 EST + Mon, 29 Feb 2016 14:00:00 EST + Sat, 29 Feb 2020 14:00:00 EST + Thu, 29 Feb 2024 14:00:00 EST + Tue, 29 Feb 2028 14:00:00 EST + Sun, 29 Feb 2032 14:00:00 EST + Fri, 29 Feb 2036 14:00:00 EST + +#### Example 3 + +12pm on the work day closest to the 15th of March and every three month +thereafter. + +Command: + + cronexpr -t=2013-09-01 -n=5 "0 12 15W 3/3 *" + +Output (assuming computer is in EST time zone): + + # "0 12 15W 3/3 *" + "2013-09-01T00:00:00-04:00" = + Mon, 16 Sep 2013 12:00:00 EDT + Mon, 16 Dec 2013 12:00:00 EST + Fri, 14 Mar 2014 12:00:00 EDT + Mon, 16 Jun 2014 12:00:00 EDT + Mon, 15 Sep 2014 12:00:00 EDT + +#### Example 4 + +Midnight on the fifth Saturday of any month (twist: not all months have a 5th +specific day of week). + +Command: + + cronexpr -t=2013-09-02 -n 5 "0 0 * * 6#5" + +Output (assuming computer is in EST time zone): + + # "0 0 * * 6#5" + "2013-09-02T00:00:00-04:00" = + Sat, 30 Nov 2013 00:00:00 EST + Sat, 29 Mar 2014 00:00:00 EDT + Sat, 31 May 2014 00:00:00 EDT + Sat, 30 Aug 2014 00:00:00 EDT + Sat, 29 Nov 2014 00:00:00 EST + diff --git a/cronexpr/main.go b/cronexpr/main.go new file mode 100644 index 0000000..c6c8b91 --- /dev/null +++ b/cronexpr/main.go @@ -0,0 +1,111 @@ +/*! + * Copyright 2013 Raymond Hill + * + * Project: github.com/gorhill/cronexprdo + * File: main.go + * Version: 1.0 + * License: GPL v3 see + * + */ + +package main + +/******************************************************************************/ + +import ( + "flag" + "fmt" + "os" + "time" + + "github.com/gorhill/cronexpr" +) + +/******************************************************************************/ + +var ( + usage = func() { + fmt.Fprintf(os.Stderr, "usage:\n %s [options] \"{cron expression}\"\noptions:\n", os.Args[0]) + flag.PrintDefaults() + } + inTimeStr string + outTimeCount uint + outTimeLayout string +) + +/******************************************************************************/ + +func main() { + var err error + + flag.Usage = usage + flag.StringVar(&inTimeStr, "t", "", `whole or partial RFC3339 time value (i.e. "2006-01-02T15:04:05Z07:00") against which the cron expression is evaluated, now if not present`) + flag.UintVar(&outTimeCount, "n", 1, `number of resulting time values to output`) + flag.StringVar(&outTimeLayout, "l", "Mon, 02 Jan 2006 15:04:05 MST", `Go-compliant time layout to use for outputting time value(s), see `) + flag.Parse() + + cronStr := flag.Arg(0) + if len(cronStr) == 0 { + flag.Usage() + return + } + + inTime := time.Now() + inTimeLayout := "" + timeStrLen := len(inTimeStr) + if timeStrLen == 2 { + inTimeLayout = "06" + } else if timeStrLen >= 4 { + inTimeLayout += "2006" + if timeStrLen >= 7 { + inTimeLayout += "-01" + if timeStrLen >= 10 { + inTimeLayout += "-02" + if timeStrLen >= 13 { + inTimeLayout += "T15" + if timeStrLen >= 16 { + inTimeLayout += ":04" + if timeStrLen >= 19 { + inTimeLayout += ":05" + if timeStrLen >= 20 { + inTimeLayout += "Z07:00" + } + } + } + } + } + } + } + + if len(inTimeLayout) > 0 { + // default to local time zone + if timeStrLen < 20 { + inTime, err = time.ParseInLocation(inTimeLayout, inTimeStr, time.Local) + } else { + inTime, err = time.Parse(inTimeLayout, inTimeStr) + } + if err != nil { + fmt.Fprintf(os.Stderr, "# error: unparseable time value: \"%s\"\n", inTimeStr) + os.Exit(1) + } + } + + expr, err := cronexpr.Parse(cronStr) + if err != nil { + fmt.Fprintf(os.Stderr, "# %s: %s\n", os.Args[0], err) + os.Exit(1) + } + + // Anything on the output which starts with '#' can be ignored if the caller + // is interested only in the time values. There is only one time + // value per line, and they are always in chronological ascending order. + fmt.Printf("# \"%s\" + \"%s\" =\n", cronStr, inTime.Format(time.RFC3339)) + + if outTimeCount < 1 { + outTimeCount = 1 + } + outTimes := expr.NextN(inTime, outTimeCount) + for _, outTime := range outTimes { + fmt.Println(outTime.Format(outTimeLayout)) + } +}