a) Start and end with a number
b) Hyphen should start and end with a number
c) Comma should start and end with a number
d) Range of number should be from 1-31
I propose the following regex:
(?[1-9]|[12]\d|3[01]){0}(?\g-\g|\g){0}^(\g,)*\g$
It looks awful but it isn't :) In fact the construction (? allows us to define a named regex and to say that it doesn't match where it is defined. Thus I defined a pattern for numbers called number and a pattern for what I called a thing i.e. a range or number called thing. Next I know that your expression is a sequence of those things, so I use the named regex thing to build it with the construct \g. It gives (\g. That's easy to read and understand. If you allow whitespaces to be non significant in your regex, you could even indent it like this:
(?[1-9]|[12]\d|3[01]){0}
(?\g-\g|\g){0}
^(\g,)*\g$/
I tested it with Ruby 1.9.2. Your regex engine should support named groups to allow that kind of clarity.
irb(main):001:0> s1 = '1-5,5,15-29'
=> "1-5,5,15-29"
irb(main):002:0> s2 = '1,28,1-31,15'
=> "1,28,1-31,15"
irb(main):003:0> s3 = '15,25,3'
=> "15,25,3"
irb(main):004:0> s4 = '1-24,5-6,2-9'
=> "1-24,5-6,2-9"
irb(main):005:0> r = /(?[1-9]|[12]\d|3[01]){0}(?\g-\g|\g){0}^(\g,)*\g$/
=> /(?[1-9]|[12]\d|3[01]){0}(?\g-\g|\g){0}^(\g,)*\g$/
irb(main):006:0> s1.match(r)
=> #
irb(main):007:0> s2.match(r)
=> #
irb(main):008:0> s3.match(r)
=> #
irb(main):009:0> s4.match(r)
=> #
irb(main):010:0> '1-1-1-1'.match(r)
=> nil