Date Occurrences

9

Given three non-negative integers y, m, and d (of which at least one must be positive) and a valid date with a positive year (in any reasonable format that includes the year, month, and day, and no additional information), output the date that is y years, m months, and d days after the original date.

The Gregorian calendar is to be used for all dates (even dates prior to the adoption of the Gregorian calendar).

The method for computing the next date is as follows:

  1. Add y to the year
  2. Add m to the month
  3. Normalize the date by applying rollovers (e.g. 2018-13-01 -> 2019-01-01)
  4. If the day is past the last day of the month, change it to the last day in the month (e.g. 2018-02-30 -> 2018-02-28)
  5. Add d to the day
  6. Normalize the date by applying rollovers (e.g. 2019-01-32 -> 2019-02-01)

Leap years (years divisible by 4, but not divisible by 100 unless also divisible by 400) must be handled appropriately. All inputs and outputs will be within the representable integer range of your language.

Test Cases

Test cases are provided in the format input => output, where input is a JSON object.

{"date":"2018-01-01","add":{"d":1}} => 2018-01-02
{"date":"2018-01-01","add":{"M":1}} => 2018-02-01
{"date":"2018-01-01","add":{"Y":1}} => 2019-01-01
{"date":"2018-01-30","add":{"M":1}} => 2018-02-28
{"date":"2018-01-30","add":{"M":2}} => 2018-03-30
{"date":"2000-02-29","add":{"Y":1}} => 2001-02-28
{"date":"2000-02-29","add":{"Y":4}} => 2004-02-29
{"date":"2000-01-30","add":{"d":2}} => 2000-02-01
{"date":"2018-01-01","add":{"Y":2,"M":3,"d":4}} => 2020-04-05
{"date":"2018-01-01","add":{"Y":5,"M":15,"d":40}} => 2024-05-11

You may use this JSFiddle for testing.

This is , so the shortest solution (in each language) wins.

Mego

Posted 2018-07-20T22:21:14.050

Reputation: 32 998

Sandbox post (deleted) – Mego – 2018-07-20T22:21:37.450

2@LuisfelipeDejesusMunoz The input format is not important, as is the norm here on PPCG. – Mego – 2018-07-20T23:05:25.907

Is there any restriction to the upper bounds of y, m and d (e.g. could d be 2147483000?) – ErikF – 2018-07-21T03:44:10.003

@ErikF All inputs and outputs will be within the representable integer range of your language. – Mego – 2018-07-21T04:00:22.740

1What about output formats? Can we output a date object? Can we take a date object? – Asone Tuhid – 2018-07-21T09:00:26.693

Uh, excuse me, but I think step 3 makes step 4 obsolete. Do the "rollovers" in step 3 start from the month and not the day? Or are they only applied once, so that, for example, 2007-01-28 with d=33 becomes 2007-01-612007-02-30? – Erik the Outgolfer – 2018-07-21T12:19:57.200

Is it OK if I output e.g. Sat Mar 3 00:00:00 UTC 2018? – wastl – 2018-07-21T15:20:15.133

@wastl Yeah that's fine, since that's the default representation of a date as a datetime stream. – Mego – 2018-07-21T15:39:22.583

@EriktheOutgolfer The rollovers in step 3 are for the month, since you can't properly determine what the last day of the month is until you've normalized the month. You don't rollover the day of the month in step 4 - you just set it to the maximum of the calculated day and the last day of the month. – Mego – 2018-07-21T15:41:20.920

Answers

3

C (gcc), 291 bytes

This one was pretty fun to get returning the same values as the JS builtin.

z,m=0xEEFBB3;int*y;g(){z=28+(m>>y[1]*2&3)+!(y[1]-1)*(!(*y%4)&&(*y%100)||!(*y%400));}h(a){z=(a>g())?g():a;}j(){*y+=y[1]/12;y[1]%=12;y[2]=h(y[2]);}f(int*a){y=a+6;for(z=0;z<3;z++)y[z]=a[z];y[1]--;j();*y+=a[3];y[1]+=a[4];j();y[2]+=a[5];for(;y[2]>h(y[2]);(y[1]=++y[1]%12)||++*y)y[2]-=g();y[1]++;}

Try it online!

Un-golfed:

// De No Oc Se Au Jl Jn Ma Ap Mr Fe Ja
// 31 30 31 30 31 31 30 31 30 31 28 31 = Month length
// 11 10 11 10 11 11 10 11 10 11 00 11 = Offset (2-bit representation)
//   E     E     F     B     B     3   = Hex representation

int m=0xEEFBB3; // Month lengths-28 in reverse order, stored as 2 bits/month
int *y; // Pointer to the output date, shared as a global between calls

// Regenerate month length and add leap day
int days_month(void) { 
  return 28+(m>>y[1]*2&3)+!(y[1]-1)*(!(*y%4)&&(*y%100)||!(*y%400));
}

int calendar_day(int day) { return day>days_month()?days_month():day; }

void truncate_date(void) {
  *y+=y[1]/12; y[1]%=12;
  y[2]=calendar_day(y[2]);
}

void f(int *a) {
  int z;
  y=a+6;
  for(z=0;z<3;z++)y[z]=a[z];y[1]--; // Convert month to 0-based
  truncate_date();
  *y+=a[3]; y[1]+=a[4]; truncate_date();
  y[2]+=a[5];
  for(;y[2]>calendar_day(y[2]);(y[1]=++y[1]%12)||++*y)
    y[2]-=days_month();
  y[1]++; // Return month to 1-based
}

Try it online!

ErikF

Posted 2018-07-20T22:21:14.050

Reputation: 2 149

249 bytes – ceilingcat – 2018-07-30T14:01:08.580

1

perl -MDate::Calc=:all -E, 28 bytes

$,=$";say Add_Delta_YMD@ARGV

This takes 6 arguments: the input year, month and date (as separate arguments), and the number of years, months and days to add.

user73921

Posted 2018-07-20T22:21:14.050

Reputation:

2This doesn't deal with the quirky "rule 4" of the task, so fails some of the test cases - for eg., perl -MDate::Calc=:all -E '$,=$";say Add_Delta_YMD@ARGV' -- 2000 2 29 1 0 0 returns 2001 3 1 instead of 2001 2 28 as the OP expects (test case 6). – sundar - Reinstate Monica – 2018-07-21T10:34:09.027

1

R, 88 bytes

function(Y,M,D,y,m,d,o=M+m){while(is.na(x<-ISOdate(Y+y+o%/%12,o%%12,D)))D=D-1;x+864e2*d}

Try it online!

A function that takes 3 arguments (Y,M,D) for the date, and other 3 arguments (y,m,d) for the values to be added.

The output comes with prepended 12:00:00 GMT which is the default format for ISOdate's

digEmAll

Posted 2018-07-20T22:21:14.050

Reputation: 4 599

1

Perl 6,  60 50 45  44 bytes

{Date.new($^a).later(:$:year).later(:$:month).later(:$:day)}

Test it (60)
Input is ( "2000-02-29", year => 1, month => 0, day => 0 )


{$^a.later(:$:year).later(:$:month).later(:$:day)}

Test it (50)
Input is ( Date.new("2000-02-29"), year => 1, month => 0, day => 0 )


{$/=$^a;$/.=later(|$_) for |[R,] $^b.sort;$/}

Test it (45)
Input is ( Date.new("2000-02-29"), %( year => 1 ) )
(No need to include keys with a value of 0)


{$/=$^a;$/.=later(|$_) for |[R,] %_.sort;$/}

Test it (44)
Input is ( Date.new("2000-02-29"), year => 1 )

Expanded:

{  # bare block lambda

  $/ = $^a; # store only positional param into a modifiable scalar
            # (params are readonly by default)


  # do a loop over the data to add

  $/ .= later(    # add using Date.later()
    |$_           # turn current iterated Pair into a named parameter
  )

    for

      |           # flatten so that `for` will iterate

        [R,]      # shorter than `reverse` (year=>1, month=>0, day=>0)

          %_.sort # sort the named arguments (day=>0, month=>0, year=>1)
  ;

  # return new Date
  $/
}

Brad Gilbert b2gills

Posted 2018-07-20T22:21:14.050

Reputation: 12 713

You can remove the space before the for – Jo King – 2018-07-27T04:08:52.623

1

C# (.NET Core), 48 bytes

(a,y,m,d)=>a.AddYears(y).AddMonths(m).AddDays(d)

Try it online!

Charlie

Posted 2018-07-20T22:21:14.050

Reputation: 11 448

1

Java 8, 51 bytes

(s,y,m,d)->s.plusYears(y).plusMonths(m).plusDays(d)

Input (s) and output are both java.time.LocalDate.

Try it online.

Explanation:

(s,y,m,d)->        // Method with LocalDate and 3 int parameters and LocalDate return-type
  s.plusYears(y)   //  Add the years to the input start-Date
   .plusMonths(m)  //  Add the months as well
   .plusDays(d)    //  And add the days as well

Kevin Cruijssen

Posted 2018-07-20T22:21:14.050

Reputation: 67 575

1

R, 65 bytes

function(x,y){require(lubridate)
x%m+%period(y,c("ye","mo","d"))}

Uses the lubridate package. The %m+% infix operator is sugar for the add_with_rollback function which essentially implements what the question asks for.

TIO doesn't have lubridate so you can Try It Here Instead with f <- prepended to the function above along with test cases:

f(as.Date("2018-01-01"),c(0,0,1))
f(as.Date("2018-01-01"),c(0,1,0))
f(as.Date("2018-01-01"),c(1,0,0))
f(as.Date("2018-01-30"),c(0,1,0))
f(as.Date("2018-01-30"),c(0,2,0))
f(as.Date("2000-02-29"),c(1,0,0))
f(as.Date("2000-02-29"),c(4,0,0))
f(as.Date("2000-01-30"),c(0,0,2))
f(as.Date("2018-01-01"),c(2,3,4))
f(as.Date("2018-01-01"),c(5,15,40))

ngm

Posted 2018-07-20T22:21:14.050

Reputation: 3 974

You can save save two bytes with:function(x,y)x%m+%period(y,c("ye","mo","d")) require(lubridate) (require outisde of function) – JayCe – 2018-07-25T13:58:56.540

0

Bash, 150 149 bytes

a=$2+$5-1+b
y=$1+$4+a/12
m=1+a%12
d=date
$d -d@$[$($d +%s+$6*86400 -d$[y]-$[m]-$($d +$3%n%d -d@$[`b=1;$d +%s-86400 -d$[y]-$[m]-1`]|sort -n|head -1))]

Try it online!

Takes input via command line arguments in order: old year, old month, old day. year change, month change, day change. Outputs a string like Wed Feb 28 00:00:00 UTC 2018 to stdout.

wastl

Posted 2018-07-20T22:21:14.050

Reputation: 3 089

0

PHP, 203 bytes

<?list(,$a,$y,$m,$d)=$argv;$b=new DateTime($a);$j=$b->format('j');$b->modify("+$y year +$m month");$j==$b->format('j')?:$b->modify('last day of last month');$b->modify("+$d day");echo$b->format('Y-m-d');

To run it:

php -n <filename> <date> <y> <m> <d>

Example:

php -n date_occurrences.php 2018-01-01 5 15 40

Or Try it online!

Tests: Try it online!

Night2

Posted 2018-07-20T22:21:14.050

Reputation: 5 484

0

T-SQL, 53 Bytes

SELECT DATEADD(D,d,DATEADD(M,m,DATEADD(Y,y,a)))FROM t

I'm not sure that it matters, but I'm applying the Year adjustment, followed by the Month adjustment, followed by the Day. All test values check out.

Per our IO standards, input is taken from a pre-existing table t with date field a and integer fields y, m, and d.

Note interestingly that it isn't the capitalization that matters between the date type codes (D, M, and Y) and my input values (d, m, and y) its simply the order of parameters in the SQL DATEADD function.

BradC

Posted 2018-07-20T22:21:14.050

Reputation: 6 099

1Does this pass test case 6? Since it doesn't implement Rule 4, I think it'd give 2001 3 1 instead of 2001 2 28 for input 6. – sundar - Reinstate Monica – 2018-07-27T20:42:58.870

@sundar Looks like you are correct; I thought I had passed all the test cases. I'll see if it can be fixed... – BradC – 2018-07-30T15:20:51.447