Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@
<br />
</p>

cron is a Go library that parses a cron expression and outputs a human readable description of the cron schedule.
For example, given the expression `*/5 * * * *` it will output `Every 5 minutes`.
cron is a Go library that parses a cron expression and outputs a human readable description of the cron schedule.
For example, given the expression `*/5 * * * *` it will output `Every 5 minutes`.

Translated to Go from [cron-expression-descriptor](https://github.com/bradymholt/cron-expression-descriptor) (C#) via [cRonstrue](https://github.com/bradymholt/cRonstrue) (Javascript).
Translated to Go from [cron-expression-descriptor](https://github.com/bradymholt/cron-expression-descriptor) (C#) via [cRonstrue](https://github.com/bradymholt/cRonstrue) (Javascript).
Original Author & Credit: Brady Holt (http://www.geekytidbits.com).

## Features
- Zero dependencies
- Supports all cron expression special characters including `* / , - ? L W #`
- Supports 5, 6 (w/ seconds or year), or 7 (w/ seconds and year) part cron expressions
- Supports [Quartz Job Scheduler](http://www.quartz-scheduler.org/) cron expressions
- i18n support with 26 locales.
- i18n support with 27 locales.

## Installation
`cron` module can be used with both Go module (>= 1.11) and earlier Go versions.
Expand All @@ -34,10 +34,10 @@ go get -u -v github.com/lnquy/cron
exprDesc, _ := cron.NewDescriptor()

desc, _ := exprDesc.ToDescription("* * * * *", cron.Locale_en)
// "Every minute"
// "Every minute"

desc, _ := exprDesc.ToDescription("0 23 ? * MON-FRI", cron.Locale_en)
// "At 11:00 PM, Monday through Friday"
// "At 11:00 PM, Monday through Friday"

desc, _ := exprDesc.ToDescription("23 14 * * SUN#2", cron.Locale_en)
// "At 02:23 PM, on the second Sunday of the month"
Expand Down Expand Up @@ -83,6 +83,7 @@ By default, `ExpressionDescriptor` always load the `Locale_en`. If you pass an u
| fi | Finnish | [Mikael Rosenberg](https://github.com/MR77FI) |
| fr | French | [Arnaud TAMAILLON](https://github.com/Greybird) |
| he | Hebrew | [Ilan Firsov](https://github.com/IlanF) |
| id | Indonesian | [Kiki L Hakiem](https://github.com/kikihakiem) |
| it | Italian | [rinaldihno](https://github.com/rinaldihno) |
| ja | Japanese | [Alin Sarivan](https://github.com/asarivan) |
| ko | Korean | [Ion Mincu](https://github.com/ionmincu) |
Expand All @@ -105,12 +106,12 @@ By default, `ExpressionDescriptor` always load the `Locale_en`. If you pass an u

## hcron

`hcron` is the CLI tool to convert the CRON expression to human readable string.
`hcron` is the CLI tool to convert the CRON expression to human readable string.
You can pass the CRON expressions as the program argument, piped `hcron` with stdin or given the path to crontab file.

### Install

You can find the pre-built binaries for Linux, MacOS, FreeBSD and Windows from the [Release](https://github.com/lnquy/cron/releases).
You can find the pre-built binaries for Linux, MacOS, FreeBSD and Windows from the [Release](https://github.com/lnquy/cron/releases).

For other OS or architecture, you can build the code using Go as below:

Expand Down Expand Up @@ -154,7 +155,7 @@ Examples:
$ hcron "0 */10 9 * * 1-5 2020"
$ hcron -locale fr "0 */10 9 * * 1-5 2020"
$ hcron -file /var/spool/cron/crontabs/mycronfile
$ another-app | hcron
$ another-app | hcron
$ another-app | hcron --dow-starts-at-one --24-hour -locale es
```

Expand Down
1 change: 1 addition & 0 deletions cron_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var (
Locale_fi: fi_TestCases(),
Locale_fr: fr_TestCases(),
Locale_he: he_TestCases(),
Locale_id: id_TestCases(),
Locale_it: it_TestCases(),
Locale_ja: ja_TestCases(),
Locale_ko: ko_TestCases(),
Expand Down
75 changes: 75 additions & 0 deletions i18n/id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package i18n

// Indonesian
const Locale_id = `
{
"confSetPeriodBeforeTime": false,
"everyMinute": "setiap menit",
"everyHour": "setiap jam",
"atSpace": "Pukul ",
"everyMinuteBetweenX0AndX1": "Setiap menit pukul %s hingga %s",
"at": "Pukul",
"spaceAnd": " dan",
"everySecond": "setiap detik",
"everyX0Seconds": "setiap %s detik",
"secondsX0ThroughX1PastTheMinute": "detik %s hingga %s setiap menit",
"atX0SecondsPastTheMinute": "detik %s setiap menit",
"everyX0Minutes": "setiap %s menit",
"minutesX0ThroughX1PastTheHour": "menit %s hingga %s setiap jam",
"atX0MinutesPastTheHour": "menit %s setiap jam",
"everyX0Hours": "setiap %s jam",
"betweenX0AndX1": "pukul %s hingga %s",
"atX0": "pukul %s",
"commaEveryDay": ", setiap hari",
"commaEveryX0DaysOfTheWeek": ", setiap %s hari sekali dalam seminggu",
"commaX0ThroughX1": ", %s hingga %s",
"first": "pertama",
"second": "kedua",
"third": "ketiga",
"fourth": "keempat",
"fifth": "kelima",
"commaOnThe": ", pada %s ",
"spaceX0OfTheMonth": " setiap bulan",
"lastDay": "hari terakhir setiap bulan",
"commaOnTheLastX0OfTheMonth": ", pada %s terakhir setiap bulan",
"commaOnlyOnX0": ", hanya di hari %s",
"commaAndOnX0": ", dan di hari %s",
"commaEveryX0Months": ", setiap %s bulan sekali",
"commaOnlyInMonthX0": ", hanya di bulan %s",
"commaOnlyInYearX0": ", hanya di tahun %s",
"commaOnTheLastDayOfTheMonth": ", pada hari terakhir setiap bulan",
"commaOnTheLastWeekdayOfTheMonth": ", pada hari kerja terakhir setiap bulan",
"commaDaysBeforeTheLastDayOfTheMonth": ", %s hari menjelang akhir bulan",
"firstWeekday": "hari kerja pertama",
"weekdayNearestDayX0": "hari kerja terdekat dengan tanggal %s",
"commaOnTheX0OfTheMonth": ", pada %s setiap bulan",
"commaEveryX0Days": ", setiap %s hari sekali",
"commaBetweenDayX0AndX1OfTheMonth": ", setiap tanggal %s hingga %s",
"commaOnDayX0OfTheMonth": ", setiap tanggal %s",
"commaEveryX0Years": ", setiap %s tahun sekali",
"commaStartingX0": ", mulai %s",
"daysOfTheWeek": [
"Minggu",
"Senin",
"Selasa",
"Rabu",
"Kamis",
"Jumat",
"Sabtu"
],
"monthsOfTheYear": [
"Januari",
"Februari",
"Maret",
"April",
"Mei",
"Juni",
"Juli",
"Agustus",
"September",
"Oktober",
"November",
"Desember"
]
}
`
6 changes: 6 additions & 0 deletions locale.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
Locale_fi LocaleType = "fi"
Locale_fr LocaleType = "fr"
Locale_he LocaleType = "he"
Locale_id LocaleType = "id"
Locale_it LocaleType = "it"
Locale_ja LocaleType = "ja"
Locale_ko LocaleType = "ko"
Expand Down Expand Up @@ -50,6 +51,7 @@ var (
Locale_fi,
Locale_fr,
Locale_he,
Locale_id,
Locale_it,
Locale_ja,
Locale_ko,
Expand Down Expand Up @@ -124,6 +126,8 @@ func newLocaleLoader(typ LocaleType) (loaders []Locale, err error) {
rawData = i18n.Locale_fr
case Locale_he:
rawData = i18n.Locale_he
case Locale_id:
rawData = i18n.Locale_id
case Locale_it:
rawData = i18n.Locale_it
case Locale_ja:
Expand Down Expand Up @@ -305,6 +309,8 @@ func ParseLocale(s string) (l LocaleType, err error) {
return Locale_fr, nil
case "he":
return Locale_he, nil
case "id":
return Locale_id, nil
case "it":
return Locale_it, nil
case "ja":
Expand Down
133 changes: 133 additions & 0 deletions locale_id_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package cron

func id_TestCases() []localeTestCase {
return []localeTestCase{
// Every
{inExpr: "* * * * * *", outErr: nil, outDesc: "Setiap detik"},
{inExpr: "* * * * *", outErr: nil, outDesc: "Setiap menit"},
{inExpr: "* * * * *", isVerbose: true, outErr: nil, outDesc: "Setiap menit, setiap jam, setiap hari"},
{inExpr: "*/1 * * * *", outErr: nil, outDesc: "Setiap menit"},
{inExpr: "*/5 * * * *", outErr: nil, outDesc: "Setiap 5 menit"},
{inExpr: "0 0/1 * * * ?", outErr: nil, outDesc: "Setiap menit"},
{inExpr: "0 0 * * * ?", outErr: nil, outDesc: "Setiap jam"},
{inExpr: "0 0 0/1 * * ?", outErr: nil, outDesc: "Setiap jam"},
{inExpr: "* * * 3 *", outErr: nil, outDesc: "Setiap menit, hanya di bulan Maret"},
{inExpr: "* * * 3,6 *", outErr: nil, outDesc: "Setiap menit, hanya di bulan Maret dan Juni"},
{inExpr: "* * * * * * 2013", outErr: nil, outDesc: "Setiap detik, hanya di tahun 2013"},
{inExpr: "* * * * * 2013", outErr: nil, outDesc: "Setiap menit, hanya di tahun 2013"},
{inExpr: "* * * * * 2013,2014", outErr: nil, outDesc: "Setiap menit, hanya di tahun 2013 dan 2014"},

// Interval
{inExpr: "*/45 * * * * *", outErr: nil, outDesc: "Setiap 45 detik"},
{inExpr: "*/5 * * * *", outErr: nil, outDesc: "Setiap 5 menit"},
{inExpr: "*/10 * * * *", outErr: nil, outDesc: "Setiap 10 menit"},
{inExpr: "0 */5 * * * *", outErr: nil, outDesc: "Setiap 5 menit"},
{inExpr: "0 9-17 * * *", outErr: nil, outDesc: "Setiap jam, pukul 09:00 AM hingga 05:59 PM"},
{inExpr: "0 * ? * 2/1 *", outErr: nil, outDesc: "Setiap menit, Februari hingga Desember"},
{inExpr: "0 * ? * 2/1", outErr: nil, outDesc: "Setiap jam, Selasa hingga Sabtu"},
{inExpr: "0 52 13 ? * 3/1", outErr: nil, outDesc: "Pukul 01:52 PM, Rabu hingga Sabtu"},

// Range
{inExpr: "0 23 ? * MON-FRI", outErr: nil, outDesc: "Pukul 11:00 PM, Senin hingga Jumat"},
{inExpr: "30 11 * * 1-5", outErr: nil, outDesc: "Pukul 11:30 AM, Senin hingga Jumat"},
{inExpr: "0-10 11 * * *", outErr: nil, outDesc: "Setiap menit pukul 11:00 AM hingga 11:10 AM"},
{inExpr: "23 12 * Jan-Mar *", outErr: nil, outDesc: "Pukul 12:23 PM, Januari hingga Maret"},
{inExpr: "23 12 * JAN-FEB *", outErr: nil, outDesc: "Pukul 12:23 PM, Januari hingga Februari"},
{inExpr: "1 1,3-4 * * *", outErr: nil, outDesc: "Menit 1 setiap jam, pukul 01:00 AM dan 03:00 AM hingga 04:59 AM"},
{inExpr: "* 0 */4 * * *", outErr: nil, outDesc: "Setiap detik, menit 0 setiap jam, setiap 4 jam"},
{inExpr: "*/10 0 * * * *", outErr: nil, outDesc: "Setiap 10 detik, menit 0 setiap jam"},
{inExpr: "* 0 0 * * *", outErr: nil, outDesc: "Setiap detik, menit 0 setiap jam, pukul 12:00 AM hingga 12:59 AM"},
{inExpr: "* 0 * * *", outErr: nil, outDesc: "Setiap menit, pukul 12:00 AM hingga 12:59 AM"},
{inExpr: "* 0 * * * *", outErr: nil, outDesc: "Setiap detik, menit 0 setiap jam"},

// At
{inExpr: "30 11 * * *", outErr: nil, outDesc: "Pukul 11:30 AM"},
{inExpr: "23 12 * * SUN", outErr: nil, outDesc: "Pukul 12:23 PM, hanya di hari Minggu"},
{inExpr: "30 02 14 * * *", outErr: nil, outDesc: "Pukul 02:02:30 PM"},
{inExpr: "0 0 6 1/1 * ?", outErr: nil, outDesc: "Pukul 06:00 AM"},
{inExpr: "0 5 0/1 * * ?", outErr: nil, outDesc: "Menit 5 setiap jam"},
{inExpr: "46 9 * * 1", outErr: nil, outDesc: "Pukul 09:46 AM, hanya di hari Senin"},
{inExpr: "46 9 * * 7", outErr: nil, outDesc: "Pukul 09:46 AM, hanya di hari Minggu", name: "7 should mean Sunday"},
{inExpr: "23 12 15 * *", outErr: nil, outDesc: "Pukul 12:23 PM, setiap tanggal 15"},
{inExpr: "23 12 * JAN *", outErr: nil, outDesc: "Pukul 12:23 PM, hanya di bulan Januari"},
{inExpr: "23 12 ? JAN *", outErr: nil, outDesc: "Pukul 12:23 PM, hanya di bulan Januari"},
{inExpr: "0 7 * * *", outErr: nil, outDesc: "Pukul 07:00 AM", name: "trailing space"},
{inExpr: "30 14,16 * * *", outErr: nil, outDesc: "Pukul 02:30 PM dan 04:30 PM"},
{inExpr: "30 6,14,16 * * *", outErr: nil, outDesc: "Pukul 06:30 AM, 02:30 PM dan 04:30 PM"},
{inExpr: "0 * 31 * 1", outErr: nil, outDesc: "Setiap jam, setiap tanggal 31, dan di hari Senin"},

// Weekday
{inExpr: "* * LW * *", outErr: nil, outDesc: "Setiap menit, pada hari kerja terakhir setiap bulan"},
{inExpr: "* * WL * *", outErr: nil, outDesc: "Setiap menit, pada hari kerja terakhir setiap bulan"},
{inExpr: "* * 1W * *", outErr: nil, outDesc: "Setiap menit, pada hari kerja pertama setiap bulan"},
{inExpr: "* * 13W * *", outErr: nil, outDesc: "Setiap menit, pada hari kerja terdekat dengan tanggal 13 setiap bulan"},
{inExpr: "* * W1 * *", outErr: nil, outDesc: "Setiap menit, pada hari kerja pertama setiap bulan"},
{inExpr: "* * 5W * *", outErr: nil, outDesc: "Setiap menit, pada hari kerja terdekat dengan tanggal 5 setiap bulan"},
{inExpr: "* * W5 * *", outErr: nil, outDesc: "Setiap menit, pada hari kerja terdekat dengan tanggal 5 setiap bulan"},

// Last
{inExpr: "* * * * 4L", outErr: nil, outDesc: "Setiap menit, pada Kamis terakhir setiap bulan"},
{inExpr: "*/5 * L JAN *", outErr: nil, outDesc: "Setiap 5 menit, pada hari terakhir setiap bulan, hanya di bulan Januari"},
{inExpr: "0 20 15,L * *", outErr: nil, outDesc: "Pukul 08:00 PM, setiap tanggal 15 dan hari terakhir setiap bulan"},
{inExpr: "0 20 1-10,20-L * *", outErr: nil, outDesc: "Pukul 08:00 PM, setiap tanggal 1 hingga 10 dan 20 hingga hari terakhir setiap bulan"},
{inExpr: "0 15 10 * * L", outErr: nil, outDesc: "Pukul 10:15 AM, hanya di hari Sabtu"},
{inExpr: "0 15 10 L * *", outErr: nil, outDesc: "Pukul 10:15 AM, pada hari terakhir setiap bulan"},
{inExpr: "0 0 0 L-5 * ?", outErr: nil, outDesc: "Pukul 12:00 AM, 5 hari menjelang akhir bulan"},

// DOWStartsAtOne
{inExpr: "23 12 * * 1#2", outErr: nil, outDesc: "Pukul 12:23 PM, pada Minggu kedua setiap bulan", isDOWStartsAtOne: true},
{inExpr: "* * * ? * 2-6/2", outErr: nil, outDesc: "Setiap detik, setiap 2 hari sekali dalam seminggu, Senin hingga Jumat", isDOWStartsAtOne: true},
{inExpr: "* * * ? * 7", outErr: nil, outDesc: "Setiap detik, hanya di hari Sabtu", isDOWStartsAtOne: true},
{inExpr: "* * * ? * 1,2,3,4,5", outErr: nil, outDesc: "Setiap detik, hanya di hari Minggu, Senin, Selasa, Rabu, dan Kamis", isDOWStartsAtOne: true},

// Non-trivial
{inExpr: "*/5 15 * * MON-FRI", outErr: nil, outDesc: "Setiap 5 menit, pukul 03:00 PM hingga 03:59 PM, Senin hingga Jumat"},
{inExpr: "* * * * MON#1", outErr: nil, outDesc: "Setiap menit, pada Senin pertama setiap bulan"},
{inExpr: "* * * * MON#2", outErr: nil, outDesc: "Setiap menit, pada Senin kedua setiap bulan"},
{inExpr: "* * * * MON#3", outErr: nil, outDesc: "Setiap menit, pada Senin ketiga setiap bulan"},
{inExpr: "* * * * MON#4", outErr: nil, outDesc: "Setiap menit, pada Senin keempat setiap bulan"},
{inExpr: "* * * * MON#5", outErr: nil, outDesc: "Setiap menit, pada Senin kelima setiap bulan"},
{inExpr: "5-10 * * * * *", outErr: nil, outDesc: "Detik 5 hingga 10 setiap menit"},
{inExpr: "5-10 30-35 10-12 * * *", outErr: nil, outDesc: "Detik 5 hingga 10 setiap menit, menit 30 hingga 35 setiap jam, pukul 10:00 AM hingga 12:59 PM"},
{inExpr: "30 */5 * * * *", outErr: nil, outDesc: "Detik 30 setiap menit, setiap 5 menit"},
{inExpr: "10 0/5 * * * ?", outErr: nil, outDesc: "Detik 10 setiap menit, setiap 5 menit"},
{inExpr: "2-59/3 1,9,22 11-26 1-6 ?", outErr: nil, outDesc: "Setiap 3 menit, menit 2 hingga 59 setiap jam, pukul 01:00 AM, 09:00 AM, dan 10:00 PM, setiap tanggal 11 hingga 26, Januari hingga Juni"},
{inExpr: "23 12 * JAN-FEB * 2013-2014", outErr: nil, outDesc: "Pukul 12:23 PM, Januari hingga Februari, 2013 hingga 2014"},
{inExpr: "23 12 * JAN-MAR * 2013-2015", outErr: nil, outDesc: "Pukul 12:23 PM, Januari hingga Maret, 2013 hingga 2015"},
{inExpr: "12-50 0-10 6 * * * 2022", outErr: nil, outDesc: "Detik 12 hingga 50 setiap menit, menit 0 hingga 10 setiap jam, pukul 06:00 AM, hanya di tahun 2022"},
{inExpr: "0 0/30 8-9 5,20 * ?", outErr: nil, outDesc: "Setiap 30 menit, pukul 08:00 AM hingga 09:59 AM, setiap tanggal 5 dan 20"},
{inExpr: "23 12 * * SUN#2", outErr: nil, outDesc: "Pukul 12:23 PM, pada Minggu kedua setiap bulan"},
{inExpr: "0 25 7-19/8 ? * *", outErr: nil, outDesc: "Menit 25 setiap jam, setiap 8 jam, pukul 07:00 AM hingga 07:59 PM"},
{inExpr: "0 25 7-20/13 ? * *", outErr: nil, outDesc: "Menit 25 setiap jam, setiap 13 jam, pukul 07:00 AM hingga 08:59 PM"},
{inExpr: "0 0 8 1/3 * ? *", outErr: nil, outDesc: "Pukul 08:00 AM, setiap 3 hari sekali"},
{inExpr: "0 15 10 ? * */3", outErr: nil, outDesc: "Pukul 10:15 AM, setiap 3 hari sekali dalam seminggu"},
{inExpr: "* * * ? * 1-5/2", outErr: nil, outDesc: "Setiap detik, setiap 2 hari sekali dalam seminggu, Senin hingga Jumat"},
{inExpr: "0 5 7 2 1/3 ? *", outErr: nil, outDesc: "Pukul 07:05 AM, setiap tanggal 2, setiap 3 bulan sekali"},
{inExpr: "0 15 6 1 1 ? 1/2", outErr: nil, outDesc: "Pukul 06:15 AM, setiap tanggal 1, hanya di bulan Januari, setiap 2 tahun sekali"},
{inExpr: "2,4-5 1 * * *", outErr: nil, outDesc: "Menit 2 dan 4 hingga 5 setiap jam, pukul 01:00 AM"},
{inExpr: "2,26-28 18 * * *", outErr: nil, outDesc: "Menit 2 dan 26 hingga 28 setiap jam, pukul 06:00 PM"},
{inExpr: "5/30 * * * * ?", outErr: nil, outDesc: "Setiap 30 detik, mulai detik 5 setiap menit"},
{inExpr: "0 5/30 * * * ?", outErr: nil, outDesc: "Setiap 30 menit, mulai menit 5 setiap jam"},
{inExpr: "* * 5/8 * * ?", outErr: nil, outDesc: "Setiap detik, setiap 8 jam, mulai pukul 05:00 AM"},
{inExpr: "0 5 7 2/3 * ? *", outErr: nil, outDesc: "Pukul 07:05 AM, setiap 3 hari sekali, mulai setiap tanggal 2"},
{inExpr: "0 5 7 ? 3/2 ? *", outErr: nil, outDesc: "Pukul 07:05 AM, setiap 2 bulan sekali, Maret hingga Desember"},
{inExpr: "0 5 7 ? * 2/3 *", outErr: nil, outDesc: "Pukul 07:05 AM, setiap 3 hari sekali dalam seminggu, Selasa hingga Sabtu"},
{inExpr: "0 5 7 ? * ? 2016/4", outErr: nil, outDesc: "Pukul 07:05 AM, setiap 4 tahun sekali, 2016 hingga 2099"},
{inExpr: "0 30 10-13 ? * wed,FRI", outErr: nil, outDesc: "Menit 30 setiap jam, pukul 10:00 AM hingga 01:59 PM, hanya di hari Rabu dan Jumat"},
{inExpr: "0 00 10 ? * MON-THU,SUN *", outErr: nil, outDesc: "Pukul 10:00 AM, hanya di hari Senin hingga Kamis dan Minggu"},
{inExpr: "0 0 0 1,2,3 * WED,FRI", outErr: nil, outDesc: "Pukul 12:00 AM, setiap tanggal 1, 2, dan 3, dan di hari Rabu dan Jumat"},
{inExpr: "0 2,16 1,8,15,22 * 1,2", outErr: nil, outDesc: "Pukul 02:00 AM dan 04:00 PM, setiap tanggal 1, 8, 15, dan 22, dan di hari Senin dan Selasa"},
{inExpr: "0 */4,6 * * * ", outErr: nil, outDesc: "Menit 0 setiap jam, setiap 4,6 jam"},
{inExpr: "5 30 6,14,16 5 * *", outErr: nil, outDesc: "Detik 5 setiap menit, menit 30 setiap jam, pukul 06:00 AM, 02:00 PM, dan 04:00 PM, setiap tanggal 5"},
{inExpr: "0-20/3 9 * * *", outErr: nil, outDesc: "Setiap 3 menit, menit 0 hingga 20 setiap jam, pukul 09:00 AM hingga 09:59 AM"},

// Verbose
{inExpr: "30 4 1 * *", isVerbose: true, outErr: nil, outDesc: "Pukul 04:30 AM, setiap tanggal 1"},
{inExpr: "0 13 * * 1", isVerbose: true, outErr: nil, outDesc: "Pukul 01:00 PM, hanya di hari Senin"},

// Error
{inExpr: "sdlksldksldksd", outErr: InvalidExprError, outDesc: ""},
{inExpr: "", outErr: InvalidExprError, outDesc: ""},
{inExpr: "0 30 14 1W,15W * ? *", outErr: InvalidExprDayOfMonthError, outDesc: ""},
}
}