From 373a9b26c691de465ccb6c111da5b12b5e7e6e3f Mon Sep 17 00:00:00 2001 From: David Reiss Date: Wed, 24 Aug 2022 23:18:38 -0700 Subject: [PATCH 1/9] Schedule API updates --- temporal/api/schedule/v1/message.proto | 133 +++++++++++++++++++------ 1 file changed, 104 insertions(+), 29 deletions(-) diff --git a/temporal/api/schedule/v1/message.proto b/temporal/api/schedule/v1/message.proto index 772e567d..b0587a3b 100644 --- a/temporal/api/schedule/v1/message.proto +++ b/temporal/api/schedule/v1/message.proto @@ -20,6 +20,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +// (-- api-linter: core::0203::optional=disabled +// aip.dev/not-precedent: field_behavior annotation not available in our gogo fork --) +// (-- api-linter: core::0203::input-only=disabled +// aip.dev/not-precedent: field_behavior annotation not available in our gogo fork --) + syntax = "proto3"; package temporal.api.schedule.v1; @@ -41,7 +46,8 @@ import "temporal/api/enums/v1/schedule.proto"; import "temporal/api/workflow/v1/message.proto"; // CalendarSpec describes an event specification relative to the calendar, -// similar to a traditional cron specification. Each field can be one of: +// similar to a traditional cron specification, but with labeled fields. Each +// field can be one of: // *: matches always // x: matches when the field equals x // x/y : matches when the field equals x+n*y where n is an integer @@ -49,13 +55,14 @@ import "temporal/api/workflow/v1/message.proto"; // w,x,y,...: matches when the field is one of the listed values // Each x, y, z, ... is either a decimal integer, or a month or day of week name // or abbreviation (in the appropriate fields). -// A second in time matches if all fields match. +// A timestamp matches if all fields match. +// Note that fields have different default values, for convenience. // Note that the special case that some cron implementations have for treating // day_of_month and day_of_week as "or" instead of "and" when both are set is // not implemented. // day_of_week can accept 0 or 7 as Sunday -// TODO: add relative-to-end-of-month -// TODO: add nth day-of-week in month +// CalendarSpec gets compiled into StructuredCalendarSpec, which is what will be +// returned if you describe the schedule. message CalendarSpec { // Expression to match seconds. Default: 0 string second = 1; @@ -73,6 +80,73 @@ message CalendarSpec { string year = 6; // Expression to match days of the week. Default: * string day_of_week = 7; + // Free-form comment describing the intention of this spec. + string comment = 8; +} + +// CronString holds a traditional cron specification as a string. It accepts 5, +// 6, or 7 fields, separated by spaces, and interprets them the same way as +// CalendarSpec. +// 5 fields: minute, hour, day_of_month, month, day_of_week +// 6 fields: minute, hour, day_of_month, month, day_of_week, year +// 7 fields: second, minute, hour, day_of_month, month, day_of_week, year +// If year is not given, it defaults to *. If second is not given, it defaults +// to 0. +// Shorthands @yearly, @monthly, @weekly, @daily, and @hourly are also accepted +// in place of the 5-7 time fields. +// Optionally, the string can be preceded by TZ= or +// CRON_TZ= to set the timezone_name field in ScheduleSpec. +// Note that the special case that some cron implementations have for treating +// day_of_month and day_of_week as "or" instead of "and" when both are set is +// not implemented. +// A CronString gets compiled into a StructuredCalendarSpec, which will be +// returned if you describe the schedule. +message CronString { + string cron_spec = 1; + // Free-form comment describing the intention of this spec. + string comment = 2; +} + +// Range represents a set of integer values, used to match fields of a calendar +// time in StructuredCalendarSpec. If end < start, then end is interpreted as +// equal to start. This means you can use a Range with start set to a value, and +// end and step unset (defaulting to 0) to represent a single value. +message Range { + // Start of range (inclusive). + int32 start = 1; + // End of range (inclusive). + int32 end = 2; + // Step (optional, default 1). + int32 step = 3; +} + +// StructuredCalendarSpec describes an event specification relative to the +// calendar, in a form that's easy to work with programmatically. Each field is +// be one or more ranges. +// A timestamp matches if at least one range of _each_ field match the +// corresponding fields of the timestamp. (That implies that all fields must +// have at least one range if you want to match anything.) +// TODO: add relative-to-end-of-month +// TODO: add nth day-of-week in month +message StructuredCalendarSpec { + // Match seconds (0-59) + repeated Range second = 1; + // Match minutes (0-59) + repeated Range minute = 2; + // Match hours (0-23) + repeated Range hour = 3; + // Match days of the month (1-31) + // (-- api-linter: core::0140::prepositions=disabled + // aip.dev/not-precedent: standard name of field --) + repeated Range day_of_month = 4; + // Match months (1-12) + repeated Range month = 5; + // Match years (2000-). + repeated Range year = 6; + // Match days of the week (0-6; 0 is Sunday). + repeated Range day_of_week = 7; + // Free-form comment describing the intention of this spec. + string comment = 8; } // IntervalSpec matches times that can be expressed as: @@ -98,23 +172,28 @@ message IntervalSpec { // saving time policy changes for an area). To create a totally self-contained // ScheduleSpec, use UTC or include timezone_data. message ScheduleSpec { - // Calendar-based specifications of times. + // Calendar-based specifications of times. Note that calendar and + // cron_string are input-only! They will be compiled into + // structured_calendar when the schedule is created or updated, so when you + // describe a schedule, you'll only see that. + repeated StructuredCalendarSpec structured_calendar = 7; + repeated CronString cron_string = 8; repeated CalendarSpec calendar = 1; // Interval-based specifications of times. repeated IntervalSpec interval = 2; - // Any timestamps matching any of the exclude_calendar specs will be - // skipped. - repeated CalendarSpec exclude_calendar = 3; - // Any timestamps before start_time will be skipped. Together, start_time - // and end_time make an inclusive interval. + // Any timestamps matching any of exclude_* will be skipped. + repeated CalendarSpec exclude_calendar = 3 [deprecated = true]; // use exclude_structured_calendar + repeated StructuredCalendarSpec exclude_structured_calendar = 9; + // If start_time is set, any timestamps before start_time will be skipped. + // (Together, start_time and end_time make an inclusive interval.) google.protobuf.Timestamp start_time = 4 [(gogoproto.stdtime) = true]; - // Any timestamps after end_time will be skipped. + // If end_time is set, any timestamps after end_time will be skipped. google.protobuf.Timestamp end_time = 5 [(gogoproto.stdtime) = true]; // All timestamps will be incremented by a random value from 0 to this - // amount of jitter. Default: 1 second + // amount of jitter. Default: 0 google.protobuf.Duration jitter = 6 [(gogoproto.stdduration) = true]; - // Time zone to interpret all CalendarSpecs in. + // Time zone to interpret all calendar-based specs in. // // If unset, defaults to UTC. We recommend using UTC for your application if // at all possible, to avoid various surprising properties of time zones. @@ -134,21 +213,17 @@ message ScheduleSpec { // at 2:30am and specify a time zone that follows DST, that action will not // be triggered on the day that has no 2:30am. Similarly, an action that // fires at 1:30am will be triggered twice on the day that has two 1:30s. + // + // Also note that no actions are taken on leap-seconds (e.g. 23:59:60 UTC). string timezone_name = 10; bytes timezone_data = 11; } message SchedulePolicies { // Policy for overlaps. - // Note that this can be changed after a schedule has taken some actions, and we can't - // provide 100% sensible semantics for all changes. The most confusing case would be - // changes to/from ALLOW_ALL: with that policy multiple scheduled workflows can run - // concurrently, but for all other policies only one can run at a time. Changing - // between these two classes will leave all workflows with the other class alone. - // E.g., if changing from ALLOW_ALL to CANCEL_OTHER, and there are workflows running, - // those workflows will not be cancelled. If changing from ALLOW_ALL to SKIP with - // workflows running, the running workflows will not cause the next action to be - // skipped. + // Note that this can be changed after a schedule has taken some actions, + // and some changes might produce unintuitive results. In general, the later + // policy overrides the earlier policy. temporal.api.enums.v1.ScheduleOverlapPolicy overlap_policy = 1; // Policy for catchups: @@ -195,10 +270,11 @@ message ScheduleState { // If true, do not take any actions based on the schedule spec. bool paused = 2; - // If limited_actions is true, decrement remaining_actions after each action, and do - // not take any more scheduled actions if remaining_actions is zero. Actions may still - // be taken by explicit request. Skipped actions (due to overlap policy) do not count - // against remaining actions. + // If limited_actions is true, decrement remaining_actions after each + // action, and do not take any more scheduled actions if remaining_actions + // is zero. Actions may still be taken by explicit request (i.e. trigger + // immediately or backfill). Skipped actions (due to overlap policy) do not + // count against remaining actions. bool limited_actions = 3; int64 remaining_actions = 4; } @@ -258,8 +334,7 @@ message ScheduleInfo { google.protobuf.Timestamp create_time = 6 [(gogoproto.stdtime) = true]; google.protobuf.Timestamp update_time = 7 [(gogoproto.stdtime) = true]; - // Error for invalid schedule. If this is set, no actions will be taken. - string invalid_schedule_error = 8; + string invalid_schedule_error = 8 [deprecated = true]; } message Schedule { @@ -274,7 +349,7 @@ message Schedule { message ScheduleListInfo { // From spec: // Some fields are too large/unimportant for the purpose of listing, so we'll clear them - // from this copy of spec: exclude_calendar, jitter, timezone_data. + // from this copy of spec: exclude_*, jitter, timezone_data. ScheduleSpec spec = 1; // From action: From 76af104a81074368aaffe52bb24ffe4d0c306c57 Mon Sep 17 00:00:00 2001 From: David Reiss Date: Wed, 24 Aug 2022 23:25:40 -0700 Subject: [PATCH 2/9] more comment --- temporal/api/workflowservice/v1/request_response.proto | 3 +++ 1 file changed, 3 insertions(+) diff --git a/temporal/api/workflowservice/v1/request_response.proto b/temporal/api/workflowservice/v1/request_response.proto index 0471d46b..5b1d0561 100644 --- a/temporal/api/workflowservice/v1/request_response.proto +++ b/temporal/api/workflowservice/v1/request_response.proto @@ -900,6 +900,9 @@ message DescribeScheduleRequest { message DescribeScheduleResponse { // The complete current schedule details. This may not match the schedule as // created because: + // - some types of schedule specs may get compiled into others (e.g. + // CronString into StructuredCalendarSpec) + // - some unspecified fields may be replaced by defaults // - some fields in the state are modified automatically // - the schedule may have been modified by UpdateSchedule or PatchSchedule temporal.api.schedule.v1.Schedule schedule = 1; From ece04c18b49876f11001f5b2ac73819dc3fa7d2c Mon Sep 17 00:00:00 2001 From: David Reiss Date: Fri, 26 Aug 2022 14:02:03 -0700 Subject: [PATCH 3/9] @every --- temporal/api/schedule/v1/message.proto | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/temporal/api/schedule/v1/message.proto b/temporal/api/schedule/v1/message.proto index b0587a3b..4316d36b 100644 --- a/temporal/api/schedule/v1/message.proto +++ b/temporal/api/schedule/v1/message.proto @@ -99,8 +99,10 @@ message CalendarSpec { // Note that the special case that some cron implementations have for treating // day_of_month and day_of_week as "or" instead of "and" when both are set is // not implemented. -// A CronString gets compiled into a StructuredCalendarSpec, which will be -// returned if you describe the schedule. +// A CronString in the format above gets compiled into a StructuredCalendarSpec, +// which is what will be returned if you describe the schedule. +// @every is accepted and gets compiled into an IntervalSpec instead. +// should have a unit suffix s, m, h, or d. message CronString { string cron_spec = 1; // Free-form comment describing the intention of this spec. From 4ac012b30c351cffa8ac4d986ba54f0877b633af Mon Sep 17 00:00:00 2001 From: David Reiss Date: Wed, 7 Sep 2022 23:32:49 -0700 Subject: [PATCH 4/9] couple more tweaks from review comments --- temporal/api/schedule/v1/message.proto | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/temporal/api/schedule/v1/message.proto b/temporal/api/schedule/v1/message.proto index 4316d36b..f02a1cae 100644 --- a/temporal/api/schedule/v1/message.proto +++ b/temporal/api/schedule/v1/message.proto @@ -123,7 +123,7 @@ message Range { } // StructuredCalendarSpec describes an event specification relative to the -// calendar, in a form that's easy to work with programmatically. Each field is +// calendar, in a form that's easy to work with programmatically. Each field can // be one or more ranges. // A timestamp matches if at least one range of _each_ field match the // corresponding fields of the timestamp. (That implies that all fields must @@ -176,8 +176,8 @@ message IntervalSpec { message ScheduleSpec { // Calendar-based specifications of times. Note that calendar and // cron_string are input-only! They will be compiled into - // structured_calendar when the schedule is created or updated, so when you - // describe a schedule, you'll only see that. + // describe a schedule, you won't see calendar or cron_string, you'll see + // only structured_calendar and interval. repeated StructuredCalendarSpec structured_calendar = 7; repeated CronString cron_string = 8; repeated CalendarSpec calendar = 1; @@ -350,8 +350,7 @@ message Schedule { // that's returned in ListSchedules. message ScheduleListInfo { // From spec: - // Some fields are too large/unimportant for the purpose of listing, so we'll clear them - // from this copy of spec: exclude_*, jitter, timezone_data. + // Some fields are dropped from this copy of spec: timezone_data ScheduleSpec spec = 1; // From action: From 4d69920b547505718909cfc80873490b8c10b8ab Mon Sep 17 00:00:00 2001 From: David Reiss Date: Wed, 7 Sep 2022 23:36:45 -0700 Subject: [PATCH 5/9] @every with phase --- temporal/api/schedule/v1/message.proto | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/temporal/api/schedule/v1/message.proto b/temporal/api/schedule/v1/message.proto index f02a1cae..38da7a75 100644 --- a/temporal/api/schedule/v1/message.proto +++ b/temporal/api/schedule/v1/message.proto @@ -101,8 +101,9 @@ message CalendarSpec { // not implemented. // A CronString in the format above gets compiled into a StructuredCalendarSpec, // which is what will be returned if you describe the schedule. -// @every is accepted and gets compiled into an IntervalSpec instead. -// should have a unit suffix s, m, h, or d. +// @every [/] is accepted and gets compiled into an +// IntervalSpec instead. and should have a unit suffix s, m, +// h, or d. message CronString { string cron_spec = 1; // Free-form comment describing the intention of this spec. From 0a9f5d83840a7e3e3bb2b1368bd10c40aff4440d Mon Sep 17 00:00:00 2001 From: David Reiss Date: Fri, 9 Sep 2022 20:26:33 -0700 Subject: [PATCH 6/9] cron_string is just a string --- temporal/api/schedule/v1/message.proto | 51 ++++++++++++-------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/temporal/api/schedule/v1/message.proto b/temporal/api/schedule/v1/message.proto index 38da7a75..4e63bced 100644 --- a/temporal/api/schedule/v1/message.proto +++ b/temporal/api/schedule/v1/message.proto @@ -84,32 +84,6 @@ message CalendarSpec { string comment = 8; } -// CronString holds a traditional cron specification as a string. It accepts 5, -// 6, or 7 fields, separated by spaces, and interprets them the same way as -// CalendarSpec. -// 5 fields: minute, hour, day_of_month, month, day_of_week -// 6 fields: minute, hour, day_of_month, month, day_of_week, year -// 7 fields: second, minute, hour, day_of_month, month, day_of_week, year -// If year is not given, it defaults to *. If second is not given, it defaults -// to 0. -// Shorthands @yearly, @monthly, @weekly, @daily, and @hourly are also accepted -// in place of the 5-7 time fields. -// Optionally, the string can be preceded by TZ= or -// CRON_TZ= to set the timezone_name field in ScheduleSpec. -// Note that the special case that some cron implementations have for treating -// day_of_month and day_of_week as "or" instead of "and" when both are set is -// not implemented. -// A CronString in the format above gets compiled into a StructuredCalendarSpec, -// which is what will be returned if you describe the schedule. -// @every [/] is accepted and gets compiled into an -// IntervalSpec instead. and should have a unit suffix s, m, -// h, or d. -message CronString { - string cron_spec = 1; - // Free-form comment describing the intention of this spec. - string comment = 2; -} - // Range represents a set of integer values, used to match fields of a calendar // time in StructuredCalendarSpec. If end < start, then end is interpreted as // equal to start. This means you can use a Range with start set to a value, and @@ -174,13 +148,36 @@ message IntervalSpec { // definition of a time zone can change over time (most commonly, when daylight // saving time policy changes for an area). To create a totally self-contained // ScheduleSpec, use UTC or include timezone_data. +// +// cron_string holds a traditional cron specification as a string. It accepts 5, +// 6, or 7 fields, separated by spaces, and interprets them the same way as +// CalendarSpec. +// 5 fields: minute, hour, day_of_month, month, day_of_week +// 6 fields: minute, hour, day_of_month, month, day_of_week, year +// 7 fields: second, minute, hour, day_of_month, month, day_of_week, year +// If year is not given, it defaults to *. If second is not given, it defaults +// to 0. +// Shorthands @yearly, @monthly, @weekly, @daily, and @hourly are also accepted +// in place of the 5-7 time fields. +// Optionally, the string can be preceded by TZ= or +// CRON_TZ= to set the timezone_name field in ScheduleSpec. +// Optionally, a comment can appear after a "#". +// Note that the special case that some cron implementations have for treating +// day_of_month and day_of_week as "or" instead of "and" when both are set is +// not implemented. +// A cron_string in the format above gets compiled into a StructuredCalendarSpec, +// which is what will be returned if you describe the schedule. +// @every [/] is accepted and gets compiled into an +// IntervalSpec instead. and should have a unit suffix s, m, +// h, or d. message ScheduleSpec { // Calendar-based specifications of times. Note that calendar and // cron_string are input-only! They will be compiled into // describe a schedule, you won't see calendar or cron_string, you'll see // only structured_calendar and interval. repeated StructuredCalendarSpec structured_calendar = 7; - repeated CronString cron_string = 8; + // See comment above for cron_string format. + repeated string cron_string = 8; repeated CalendarSpec calendar = 1; // Interval-based specifications of times. repeated IntervalSpec interval = 2; From 9b46b5241991b0454be249efae48b8aae6990f73 Mon Sep 17 00:00:00 2001 From: David Reiss Date: Mon, 12 Sep 2022 12:56:22 -0700 Subject: [PATCH 7/9] cron_string comment --- temporal/api/schedule/v1/message.proto | 43 ++++++++++++-------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/temporal/api/schedule/v1/message.proto b/temporal/api/schedule/v1/message.proto index 4e63bced..30ce2747 100644 --- a/temporal/api/schedule/v1/message.proto +++ b/temporal/api/schedule/v1/message.proto @@ -148,35 +148,32 @@ message IntervalSpec { // definition of a time zone can change over time (most commonly, when daylight // saving time policy changes for an area). To create a totally self-contained // ScheduleSpec, use UTC or include timezone_data. -// -// cron_string holds a traditional cron specification as a string. It accepts 5, -// 6, or 7 fields, separated by spaces, and interprets them the same way as -// CalendarSpec. -// 5 fields: minute, hour, day_of_month, month, day_of_week -// 6 fields: minute, hour, day_of_month, month, day_of_week, year -// 7 fields: second, minute, hour, day_of_month, month, day_of_week, year -// If year is not given, it defaults to *. If second is not given, it defaults -// to 0. -// Shorthands @yearly, @monthly, @weekly, @daily, and @hourly are also accepted -// in place of the 5-7 time fields. -// Optionally, the string can be preceded by TZ= or -// CRON_TZ= to set the timezone_name field in ScheduleSpec. -// Optionally, a comment can appear after a "#". -// Note that the special case that some cron implementations have for treating -// day_of_month and day_of_week as "or" instead of "and" when both are set is -// not implemented. -// A cron_string in the format above gets compiled into a StructuredCalendarSpec, -// which is what will be returned if you describe the schedule. -// @every [/] is accepted and gets compiled into an -// IntervalSpec instead. and should have a unit suffix s, m, -// h, or d. message ScheduleSpec { // Calendar-based specifications of times. Note that calendar and // cron_string are input-only! They will be compiled into // describe a schedule, you won't see calendar or cron_string, you'll see // only structured_calendar and interval. repeated StructuredCalendarSpec structured_calendar = 7; - // See comment above for cron_string format. + // cron_string holds a traditional cron specification as a string. It + // accepts 5, 6, or 7 fields, separated by spaces, and interprets them the + // same way as CalendarSpec. + // 5 fields: minute, hour, day_of_month, month, day_of_week + // 6 fields: minute, hour, day_of_month, month, day_of_week, year + // 7 fields: second, minute, hour, day_of_month, month, day_of_week, year + // If year is not given, it defaults to *. If second is not given, it + // defaults to 0. + // Shorthands @yearly, @monthly, @weekly, @daily, and @hourly are also + // accepted instead of the 5-7 time fields. + // Optionally, the string can be preceded by CRON_TZ= or + // TZ=, which will get copied to timezone_name. (There must + // not also be a timezone_name present.) + // Optionally "#" followed by a comment can appear at the end of the string. + // Note that the special case that some cron implementations have for + // treating day_of_month and day_of_week as "or" instead of "and" when both + // are set is not implemented. + // @every [/] is accepted and gets compiled into an + // IntervalSpec instead. and should be a decimal integer + // with a unit suffix s, m, h, or d. repeated string cron_string = 8; repeated CalendarSpec calendar = 1; // Interval-based specifications of times. From 15e4dab1f6d9568b6381790c8904993393546739 Mon Sep 17 00:00:00 2001 From: David Reiss Date: Mon, 12 Sep 2022 13:11:58 -0700 Subject: [PATCH 8/9] comment --- temporal/api/schedule/v1/message.proto | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/temporal/api/schedule/v1/message.proto b/temporal/api/schedule/v1/message.proto index 30ce2747..f785f41d 100644 --- a/temporal/api/schedule/v1/message.proto +++ b/temporal/api/schedule/v1/message.proto @@ -148,11 +148,17 @@ message IntervalSpec { // definition of a time zone can change over time (most commonly, when daylight // saving time policy changes for an area). To create a totally self-contained // ScheduleSpec, use UTC or include timezone_data. +// +// For input, you can provide zero or more of: structured_calendar, calendar, +// cron_string, interval, and exclude_structured_calendar, and all of them will +// be used (the schedule will take action at the union of all of their times, +// minus the ones that match exclude_structured_calendar). +// +// On input, calendar and cron_string fields will be compiled into +// structured_calendar (and maybe interval and timezone_name), so if you +// Describe a schedule, you'll see only structured_calendar, interval, etc. message ScheduleSpec { - // Calendar-based specifications of times. Note that calendar and - // cron_string are input-only! They will be compiled into - // describe a schedule, you won't see calendar or cron_string, you'll see - // only structured_calendar and interval. + // Calendar-based specifications of times. repeated StructuredCalendarSpec structured_calendar = 7; // cron_string holds a traditional cron specification as a string. It // accepts 5, 6, or 7 fields, separated by spaces, and interprets them the @@ -175,6 +181,7 @@ message ScheduleSpec { // IntervalSpec instead. and should be a decimal integer // with a unit suffix s, m, h, or d. repeated string cron_string = 8; + // Calendar-based specifications of times. repeated CalendarSpec calendar = 1; // Interval-based specifications of times. repeated IntervalSpec interval = 2; From c48113e6f245e15282c5e902f47db778bd52a6e1 Mon Sep 17 00:00:00 2001 From: David Reiss Date: Mon, 12 Sep 2022 13:15:23 -0700 Subject: [PATCH 9/9] missing year --- temporal/api/schedule/v1/message.proto | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/temporal/api/schedule/v1/message.proto b/temporal/api/schedule/v1/message.proto index f785f41d..542c7c55 100644 --- a/temporal/api/schedule/v1/message.proto +++ b/temporal/api/schedule/v1/message.proto @@ -100,9 +100,10 @@ message Range { // StructuredCalendarSpec describes an event specification relative to the // calendar, in a form that's easy to work with programmatically. Each field can // be one or more ranges. -// A timestamp matches if at least one range of _each_ field match the -// corresponding fields of the timestamp. (That implies that all fields must -// have at least one range if you want to match anything.) +// A timestamp matches if at least one range of each field matches the +// corresponding fields of the timestamp, except for year: if year is missing, +// that means all years match. For all fields besides year, at least one Range +// must be present to match anything. // TODO: add relative-to-end-of-month // TODO: add nth day-of-week in month message StructuredCalendarSpec { @@ -118,7 +119,7 @@ message StructuredCalendarSpec { repeated Range day_of_month = 4; // Match months (1-12) repeated Range month = 5; - // Match years (2000-). + // Match years. repeated Range year = 6; // Match days of the week (0-6; 0 is Sunday). repeated Range day_of_week = 7;