12
\$\begingroup\$

Find the nearest Date to a TargetDate for a given Day of Week.

For example, given a date of 20161219 and a day of week of Friday (6), the answer is 20161216.

Another example, given a date of 20161219 and a day of week of Wednesday (4), the answer is 20161221.

A final example, given a date of 20161219 and a day of week of Monday (2), the answer is 20161219.

Rules:

  • The Date format for the input and output must match. In the examples, I used yyyymmdd, but you may use any format as long as the year (at least two digits), month, and day of month are "human readable".
  • Day of Week is input as an integer. In my example, Sunday is the first day of the week, therefore it is day of week number 1. You can have any Day of Week numbering, as long as you note it when it differs from the example.
  • Years 1970 through 2030 must be accommodated.
  • Common language date tools and libraries are allowed, but street cred is given to those that choose not to use them.
  • Least number of bytes wins.
\$\endgroup\$
4
  • 2
    \$\begingroup\$ Could answers take the date in a different form if convenient? Also the days of the week also seen inconvenient for languages that take Monday to be the first day of the week \$\endgroup\$
    – Blue
    Commented Dec 20, 2016 at 11:33
  • 4
    \$\begingroup\$ Yeah, the strict format requirements make this task more about parsing/converting/formatting, than about the actual date finding. Would have IMO been better to specify it along the lines of "You can accept the date and weekday in any reasonable format, as long as you use the same format for input and output.". But since a bunch of people have already submitted answers,I guess it has to be keep it as it is now. \$\endgroup\$
    – smls
    Commented Dec 20, 2016 at 23:01
  • \$\begingroup\$ @smls I agree. Lesson learned for next time. \$\endgroup\$ Commented Dec 21, 2016 at 2:29
  • 1
    \$\begingroup\$ @BrettVanderVeen I don't think it is too late to relax the requirements. You can just add a comment to each of the (currently 8) answers about the updated spec. I've done such on occasion. \$\endgroup\$
    – Adám
    Commented Dec 21, 2016 at 12:06

9 Answers 9

2
\$\begingroup\$

Perl 6, 83 bytes

->$_,\w{~(Date.new(|m/(....)(..)(..)/)-3...*.day-of-week%7+1==w)[*-1]~~{S:g/"-"//}}

Try it

Expanded

->     # pointy block lambda
  $_,
  \w
{
  ~(  # turn into a Str

    Date.new( |m/(....)(..)(..)/ ) - 3 # three days earlier
    ...                                # generate a sequence
    *.day-of-week % 7 + 1 == w         # stop when the right day is found

  )[*-1]  # the last one

  ~~      # apply the following block

  {
    S:g/"-"//  # remove 「-」
  }
}
\$\endgroup\$
1
  • \$\begingroup\$ FYI, I eased up on the date and day of week format rules. Feel free to resubmit. \$\endgroup\$ Commented Dec 26, 2016 at 23:09
2
\$\begingroup\$

Bash + coreutils, 50

date -d$1+$[($2-`date -d$1 +%u`+9)%7-3]day +%Y%m%d

Try it online.

  • date -d$1 +%u gives the day of week (1..7); 1 is Monday
  • This is subtracted from the input day of week to give a difference of day indexes. We want to add a factor between in the range [-3, 3] to the input date to get the closest date, so we add a magic factor 9, take the modulo 7, then subtract 3. The magic factor 9 is 3 + 7 - 1. The 3 adjusts the input range for range [-3, 3], and is subtracted again after the modulo. The 7 is required because bash modulo is the same as CPU modulo and can give -ve results - adding 7 adjusts into the right range. The -1 is because in the input, 1 is Sunday, but with the %u output, 1 is Monday.
  • The outer date then parses <input date> + <magic factor>days and presents it in the YYYYMMDD format.
\$\endgroup\$
1
  • \$\begingroup\$ FYI, I eased up on the date and day of week format rules. Feel free to resubmit. \$\endgroup\$ Commented Dec 26, 2016 at 23:09
2
\$\begingroup\$

JavaScript (ES6), 93 bytes

(s,n,d=new Date(s))=>d.setDate(d.getDate()+(n+9-d.getDay())%7-3)&&d.toISOString().slice(0,10)

Uses %Y-%m-%d date format. If %a %b %d %Y is an acceptable date format, then for 82 bytes:

(s,n,d=new Date(s))=>d.setDate(d.getDate()+(n+9-d.getDay())%7-3)&&d.toDateString()

I could save a further 2 bytes by requiring Sunday = 10 to Saturday = 16, but that's a silly day of week mapping. Previous 142 141 bytes (thanks to @Guedes) version that used the specific YYYYMMDD date format, with its explanation:

(s,n,d=new Date(s.replace(/(?=..(..)?$)/g,'-')))=>d.setDate(d.getDate()+(n+9-d.getDay())%7-3)&&d.toISOString().replace(/-(..)-(..).*/,'$1$2')

This feels far too long. Explanation: First, inserts -s into the date to get it into ISO format which new Date can then parse. Then, compares the desired day with the actual day to get a value between -3 and 3 for the actual offset. The magic 9 comes from 7 (because % is CPU modulo, not true modulo) plus 3 (for the -3 offset) minus 1 (because n is 1-indexed and getDay is 0-indexed). The date is then converted back into ISO format and the unwanted characters removed.

\$\endgroup\$
2
  • \$\begingroup\$ You can save one byte changing the first replace to .replace(/(?=..(..)?$)/g,'-') \$\endgroup\$ Commented Dec 23, 2016 at 18:59
  • \$\begingroup\$ FYI, I eased up on the date and day of week format rules. Feel free to resubmit. \$\endgroup\$ Commented Dec 26, 2016 at 23:09
1
\$\begingroup\$

Perl 6, 46 bytes

{~first *.day-of-week==$^b,Date.new($^a)-3..*}

A lambda that accepts two arguments: A date string in yyyy-mm-dd format, and a weekday number between 1=Monday and 7=Sunday.

Explanation:

                           Date.new($^a)       # Construct Date object from first argument.
                                        -3     # Subtract 3 days.
                                          ..*  # Construct a Range from that, to infinity.
  first                   ,                    # Iterate the range, and return first date...
        *.day-of-week==$^b                     # whose weekday number equals the second arg.

Using the date and weekday format used in the examples, it would be 88 bytes:

{S:g/\-//with ~first *.day-of-week==($^b-2)%7+1,Date.new(|($^a~~/(....)(..)(..)/))-3..*}
\$\endgroup\$
2
  • \$\begingroup\$ FYI, I eased up on the date and day of week format rules. Feel free to resubmit. \$\endgroup\$ Commented Dec 26, 2016 at 23:09
  • \$\begingroup\$ @BrettVanderVeen: Updated. \$\endgroup\$
    – smls
    Commented Dec 27, 2016 at 17:19
1
\$\begingroup\$

Python, 150 149 bytes

from datetime import*
lambda s,n,t='%Y%m%d':[d for d in[datetime.strptime(s,t)+timedelta(o-3)for o in range(7)]if(n-2)%7==d.weekday()][0].strftime(t)

Oouuf, 149. repl.it

Unnamed function that takes a string s, the date as specified (using format string t='%Y%m%d' for both input and output) and a number in [1,7], n.
Inspects the seven days from three days prior to three days after that given for the day of the week requested.
The week in datetime starts on Monday and is zero based, so the test is (n-2)%7==d.weekday().

\$\endgroup\$
1
  • \$\begingroup\$ FYI, I eased up on the date and day of week format rules. Feel free to resubmit. \$\endgroup\$ Commented Dec 26, 2016 at 23:10
1
\$\begingroup\$

SAS Macro Language, 110 Bytes

%macro s(d,a);%put %sysfunc(putn(%sysfunc(intnx(week.&a.,%eval(%sysfunc(putn(&d,8.))-4),1)),yymmddn8.));%mend;

A terrible golfing language but here's the explanation (from the inside out):

Create a macro s that accepts a date input d and a numeric weekday a. Then %eval(%sysfunc(putn(&d,8.))-4) converts the date to a SAS numeric date and decrements it by 4 days. The intnx function then returns the next occurrence of the specified weekday. putn then formats the date back to yyyymmdd and %put prints the output to the log.

\$\endgroup\$
1
  • \$\begingroup\$ FYI, I eased up on the date and day of week format rules. Feel free to resubmit. \$\endgroup\$ Commented Dec 26, 2016 at 23:09
1
\$\begingroup\$

Mathematica, 85 49 bytes

#~DatePlus~Mod[#2-#~DateValue~"ISOWeekDay",7,-3]&

The input format for dates is either a list like {2016, 12, 16} or an actual DateObject (I think a few others work as well, but I've tested these). In either case, the output format will match automatically. The day numbering starts from 1 on Mondays.

The idea is to compute the difference between the input weekday and the target weekday with #2-#~DateValue~"ISOWeekDay" and then to map it into [-3, 3] using a modulo with offset (i.e. Mod[...,7,-3]). The result is simply added to the input date (the default unit for date object addition is days).

\$\endgroup\$
2
  • \$\begingroup\$ FYI, I eased up on the date and day of week format rules. Feel free to resubmit. \$\endgroup\$ Commented Dec 26, 2016 at 23:10
  • \$\begingroup\$ @BrettVanderVeen thanks, edited. \$\endgroup\$ Commented Dec 26, 2016 at 23:18
1
\$\begingroup\$

R, 119 77 56 bytes

Unnamed function that takes two inputs, the date D (string) and the day d (numeric).

function(D,d,s=-3:3+as.Date(D))s[lubridate::wday(s)==d]

Uses the function wday() from the lubridate package. It can conveniently convert Date objects into week days with 1 being treated as Sunday by default.

Edit: I/O is now the default of the as.Date() function: "YYYY-mm-dd"

Ungolfed and Explained

function(D,d){
    D=as.Date(D);            # Format input date
    s=-3:3+D;                # Generate sequence of +- 3 days
    s[lubridate::wday(s)==d] # Match the weekday that equals to target day                                    
}

Edit: Now creates a sequence of +-3 days for which any given weekday is guaranteed to be found in. Subsequently we only have to match one day which made the problem much easier.

\$\endgroup\$
2
  • \$\begingroup\$ FYI, I eased up on the date and day of week format rules. Feel free to resubmit. \$\endgroup\$ Commented Dec 26, 2016 at 23:09
  • \$\begingroup\$ @BrettVanderVeen Thanks, updated. \$\endgroup\$
    – Billywob
    Commented Dec 27, 2016 at 10:23
0
\$\begingroup\$

Shell + GNU date, 43 bytes

date +%F -d$1-3day`date -d$1+$2day +%w`day

I wish I could explain how this works, but there was so much trial-and-error tweaking it no longer makes sense even to me! We start by subtracting three days so that adding a number in range 0-6 gives the nearest. But why adding the desired weekday to the target date gives the necessary offset has slipped my grasp.

Input as in the question; output format is YYYY-MM-DD.

Online test

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.