10
\$\begingroup\$

Given a year and a month, find out the percentage of work days in said month. Work days are Monday through Friday with no regard to holidays or other special things. The Gregorian calendar is used.

Input

A year and month in ISO 8601 format (YYYY-MM). The year always has four digits, the month always has two digits. The given year will not be before 1582.

Output

Output is the percentage of work days (according to above definition) in the given month, rounded to a whole number. No percent sign or fractional digits follow.

Sample 1

Input                Output

2010-05              68

Sample 2

Input                Output

2010-06              73

Sample 3

Input                Output

1920-10              68

Sample 4

Input                Output

2817-12              68

A week has passed, an answer has been accepted. For the curious, the sizes of the submissions we got in our contest:

129 – Z shell
174 – VB.NET
222 – C
233 – C
300 – C

As well as our own (unranked) solutions:

  75 – PowerShell
  93 – Ruby
112 – Bourne shell

\$\endgroup\$
  • 2
    \$\begingroup\$ I'm a graduate student, so... echo 100 \$\endgroup\$ – Amory Nov 24 '14 at 20:24
  • \$\begingroup\$ Even grad students cannot escape the fundamental definitions in their line of work. And I defined work days differently ;-) \$\endgroup\$ – Joey Nov 24 '14 at 21:42

14 Answers 14

4
\$\begingroup\$

64-bit Perl, 67 68

Perl 5.10 or later, run with perl -E 'code here' or perl -M5.010 filename

map{$d++,/^S/||$w++if$_=`date -d@ARGV-$_`}1..31;say int.5+100*$w/$d

Concessions to code size:

  • locale-sensitive: it counts as work days the days whose date output don't start with a capital S. Run under LC_ALL=C if in doubt.
  • output is pure and well-formatted, but there's "garbage" on stderr on months shorter than 31. 2> /dev/null if upset.
  • for some reason, my version of date considers 2817-12 an invalid month. Who knew, GNU new apocalypse is due! Requires a 64 bit build of date for dates after 2038. (Thanks Joey)
\$\endgroup\$
  • 1
    \$\begingroup\$ Apparently it was abolished by "Siphous Hemes" during his rule. ref "A new history of the Holy Bible" \$\endgroup\$ – Martin York Feb 24 '11 at 8:51
  • 1
    \$\begingroup\$ Is every year after 2038 broken? Then switching t a 64-bit build might help due to some braindead-ness with date handling ;-) \$\endgroup\$ – Joey Feb 26 '11 at 10:51
  • \$\begingroup\$ @Joey that's exactly it. Thanks for the tip! \$\endgroup\$ – J B Feb 28 '11 at 13:50
  • \$\begingroup\$ JB: Was just a guess and I actually didn't expect anything beyond C to still use solely 32-bit integers that count seconds since a weird epoch. Though, to be honest, I put the requirement about dates > 2038 in there for exactly this purpose ;-) \$\endgroup\$ – Joey Feb 28 '11 at 18:24
3
\$\begingroup\$

PHP - 135

I made it in PHP because I had a similar problem to treat a few days ago.

<?php $a=array(2,3,3,3,2,1,1);$t=strtotime($argv[1]);$m=date(t,$t);echo round((20+min($m-28,$a[date(w,strtotime('28day',$t))]))/$m*100)

(Somewhat) More legibly, and without notices about constants being used as strings:

<?php
date_default_timezone_set('America/New_York');
$additionalDays = array(2, 3, 3, 3, 2, 1, 1);
$timestamp = strtotime($argv[1]);
$daysInMonth = date('t', $timestamp);
$limit = $daysInMonth - 28;
$twentyNinthDayIndex = date('w', strtotime("+28 days", $timestamp));
$add = $additionalDays[$twentyNinthDayIndex];
$numberOfWorkDays = 20 + min($limit, $add);
echo round($numberOfWorkDays / $daysInMonth * 100);
?>

This is made possible by a very simple algorithm to compute the number of work days in a month: check for the weekdayness of the 29th, 30th and 31st (if those dates exist), and add 20.

\$\endgroup\$
  • \$\begingroup\$ Great algorithm, poor golfing. Using contemporary PHP 5.3.5 and -R, this approach can be golfed down to 86 bytes (63.7%): $a="2333211";echo.5+min(-8+$m=date(t,$t=strtotime($argn)),20+$a[date(w,$t)])/$m*100|0; See the golfing steps. \$\endgroup\$ – Titus Mar 1 '17 at 23:53
  • \$\begingroup\$ 80 bytes: <?=.5+min(-8+$m=date(t,$t=strtotime($argn)),20+(5886>>date(w,$t)*2&3))/$m*100|0; \$\endgroup\$ – Titus Nov 26 '18 at 12:52
2
\$\begingroup\$

Python 152 Characters

from calendar import*
y,m=map(int,raw_input().split('-'))
c=r=monthrange(y,m)[1]
for d in range(1,r+1):
 if weekday(y,m,d)>4:c-=1
print '%.f'%(c*100./r)
\$\endgroup\$
2
\$\begingroup\$

Windows PowerShell, 80

$x=$args;1..31|%{"$x-$_"|date -u %u -ea 0}|%{$a++
$b+=!!($_%6)}
[int]($b*100/$a)
\$\endgroup\$
  • \$\begingroup\$ Are you sure [int] really rounds? I'd tend to believe it floors. \$\endgroup\$ – zneak Feb 28 '11 at 4:08
  • \$\begingroup\$ @zneak: PowerShell is not C or a C-derived language. It uses the default rounding mode of .NET which is »round to nearest even integer«. Just try it out: Both [int]1.5 and [int]2.5 yield 2. This exact behaviour often causes problems in tasks where floored division is necessary (which then requires an extra [Math]::Floor()), but in this case it doesn't hurt and »round to even« only applies to numbers that end in .5 which cannot happen here. \$\endgroup\$ – Joey Feb 28 '11 at 18:23
  • \$\begingroup\$ If you're sure then I believe you. I just expected it to work like C# instead, and I don't have any Windows machine on which to test at home. \$\endgroup\$ – zneak Feb 28 '11 at 20:09
  • \$\begingroup\$ @zneak: No, definitely doesn't work like in C#. Something like [int] in PowerShell is usually more a conversion than a cast :-). Things like [int[]][char[]]'abc' also work which you can't get to work in many other languages. \$\endgroup\$ – Joey Feb 28 '11 at 20:59
  • \$\begingroup\$ Necrobump but $input -> $args saves a byte. \$\endgroup\$ – Veskah Dec 5 '18 at 0:19
1
\$\begingroup\$

D: 186 Characters

auto f(S)(S s){auto d=Date.fromISOExtendedString(s~"-28"),e=d.endOfMonth;int n=20;while(1){d+=dur!"days"(1);if(d>e)break;int w=d.dayOfWeek;if(w>0&&w<6)++n;}return rndtol(n*100.0/e.day);}

More Legibly:

auto f(S)(S s)
{
    auto d = Date.fromISOExtendedString(s ~ "-28"), e = d.endOfMonth;
    int n = 20;

    while(1)
    {
        d += dur!"days"(1);

        if(d > e)
            break;

        int w = d.dayOfWeek;

        if(w > 0 && w < 6)
            ++n;
    }

    return rndtol(n * 100.0 / e.day);
}
\$\endgroup\$
1
\$\begingroup\$

Python - 142

from calendar import*
y,m=map(int,raw_input().split('-'))
f,r=monthrange(y,m)
print'%.f'%((r-sum(weekday(y,m,d+1)>4for d in range(r)))*100./r)

Thanks to fR0DDY for the calendar bit.

\$\endgroup\$
1
\$\begingroup\$

Ruby, 124 119 111

require 'date'
e=Date.civil *$*[0].split(?-).map(&:to_i),-1
p ((e+1<<1..e).count{|d|d.cwday<6}*1e2/e.day).round

Requires Ruby 1.9 due to splatting the year and month before the -1 "day" argument and ?- for "-". For Ruby 1.8, we must add 2 characters:

require 'date'
e=Date.civil *$*[0].split('-').map(&:to_i)<<-1
p ((e+1<<1..e).count{|d|d.cwday<6}*1e2/e.day).round

Edit: Shave five characters based on @Dogbert's help.
Edit: Shave eight more characters based on @steenslag's help.

\$\endgroup\$
  • \$\begingroup\$ Why are you assigning Date to D? \$\endgroup\$ – Dogbert Feb 28 '11 at 11:48
  • \$\begingroup\$ @Dogbert Whoops! Holdover from a time when I had two Date.civils; thanks! \$\endgroup\$ – Phrogz Feb 28 '11 at 15:43
  • \$\begingroup\$ '-' could be written as ?- in Ruby 1.9 \$\endgroup\$ – Dogbert Feb 28 '11 at 16:03
  • \$\begingroup\$ @Dogbert Nice. I'll throw that in, too. I feel there must be a shorter way to pick the week days, but I haven't found it yet. \$\endgroup\$ – Phrogz Feb 28 '11 at 16:22
  • \$\begingroup\$ e+1<<1 is three shorter than e-e.day+1 \$\endgroup\$ – steenslag Mar 2 '11 at 17:03
1
\$\begingroup\$

Bash + coreutils, 82 bytes

f()(cal -NMd$1|sed -n "s/^$2.//p"|wc -w)
dc -e`f $1 "[^S ]"`d`f $1 S`+r200*r/1+2/p
\$\endgroup\$
1
\$\begingroup\$

PHP 5.2, 88 bytes

Although I already golfed zneak´s solution down to 85 bytes (I just found one more), here´s my own:
I doubt that I can squeeze another three bytes out here.

$a=_4444444255555236666304777411;echo$a[date(t,$t=strtotime($argn))%28*7+date(N,$t)]+67;

takes input from STDIN: Run with echo <yyyy>-<mm> | php -nR '<code>'.

The string $a maps days per month (date(t)) and week day of the first day of the month (date(N): Monday=1, Sunday=7) to the percentage of work days-67; strtotime converts the input to a UNIX timestamp; the rest of the code does the hashing.

+1 byte for older PHP 5: Replace N with w and $a=_...; with $a="...".
another +3 bytes for PHP 4: insert .-1 after $argn.

-5 bytes for PHP 5.5 or later (postdates the challenge):
Remove everything before echo and replace $a with "4444444255555236666304777411".

\$\endgroup\$
  • \$\begingroup\$ Well ... one byte: %7 instead of %28. \$\endgroup\$ – Titus Nov 26 '18 at 14:19
0
\$\begingroup\$

Rebol - 118 113

w: b: 0 o: d: do join input"-01"while[d/2 = o/2][if d/7 < 6[++ w]++ b d: o + b]print to-integer round w / b * 100

Ungolfed:

w: b: 0 
o: d: do join input "-01"
while [d/2 = o/2] [
    if d/7 < 6 [++ w]
    ++ b
    d: o + b
]
print to-integer round w / b * 100
\$\endgroup\$
0
\$\begingroup\$

C#, 158 bytes

s=>{var d=DateTime.Parse(s);int i=0,t=DateTime.DaysInMonth(d.Year,d.Month),w=0;while(i<t)w-=-(int)d.AddDays(i++).DayOfWeek%6>>31;return Math.Round(1e2*w/t);};

Anonymous method which returns the required percentage.

Full program with ungolfed, commented method and test cases:

using System;

class WorkingDayPercentage
{
    static void Main()
    {
        Func <string, double> f =
        s =>
        {
            var d = DateTime.Parse(s);                      // extracts a DateTime object from the supplied string
            int i = 0,                                      // index variable
                t = DateTime.DaysInMonth(d.Year, d.Month),  // number of total number of days in the specified month
                w = 0;                                      // number of working days in the month

            while (i < t)                                   // iterates through the days of the month
                w -= -(int)d.AddDays(i++).DayOfWeek%6 >> 31;// d.AddDays(i) is the current day
                                                            // i++ increments the index variable to go to the next day
                                                            // .DayOfWeek is an enum which hold the weekdays
                                                            // (int)..DayOfWeek gets the days's index in the enum
                                                            // note that 6 is Saturday, 0 is Sunday, 1 is Monday etc.
                                                            // (int)DayOfWeek % 6 converts weekend days to 0
                                                            // while working days stay strictly positive
                                                            // - changes the sign of the positive numbers
                                                            // >> 31 extracts the signum
                                                            // which is -1 for negative numbers (working days)
                                                            // weekend days remain 0
                                                            // w -= substracts the negative numbers
                                                            // equivalent to adding their modulus

            return Math.Round(1e2 * w / t);                 // the Math.round function requires a double or a decimal
                                                            // working days and total number of days are integers
                                                            // also, a percentage must be returned
                                                            // multiplying with 100.0 converts the expression to a double
                                                            // however, 1e2 is used to shorten the code
        };

        // test cases:
        Console.WriteLine(f("2010-05")); // 68
        Console.WriteLine(f("2010-06")); // 73
        Console.WriteLine(f("1920-10")); // 68
        Console.WriteLine(f("2817-12")); // 68
    }
}

Alternative function, which adds negative values to the number of working days, changing the sign in the return with no extra byte cost:

s=>{var d=DateTime.Parse(s);int i=0,t=DateTime.DaysInMonth(d.Year,d.Month),w=0;while(i<t)w+=-(int)d.AddDays(i++).DayOfWeek%6>>31;return-Math.Round(1e2*w/t);};
\$\endgroup\$
0
\$\begingroup\$

Oracle SQL, 110 bytes

select round(100*sum(1-trunc(to_char(x+level-1,'d')/6))/sum(1))from dual,t connect by level<=add_months(x,1)-x

It works with an assumption that input data is stored in a table in using date data type, e.g.

with t as (select to_date('2010-06','yyyy-mm') x from dual)
\$\endgroup\$
0
\$\begingroup\$

APL (Dyalog Unicode), 55 bytesSBCS

Anonymous tacit prefix function.

⌊.5+100×2÷/(2 5)2{≢⍎⍕↓⍺↓¯2⌽' ',cal⍎' '@5⊢⍵⊣⎕CY'dfns'}¨⊂

 enclose date to treat it as a whole

(2 5)2{ apply the following function on that, but with the left arguments [2,5] and 2:

⎕CY'dfns' copy the "dfns" library

⍵⊣ discard the report in favour of the date

' '@5⊢ replace the 5th character (-) with a space

 execute that to get two-element list

cal call the calendar function on that

' ', prepend a column of spaces to that

¯2⌽ rotate the last two columns (Saturday) to the front

⍺↓ drop left-argument number of rows (2, headers) and columns (if specified; 5=Sat+Sun)

 split matrix into list of lines

 format (flattens with insertion of double-spacing)

 execute (turns remaining day numbers into a flat numeric list)

 tally those

2÷/ divide each pair (there's only one)

100× multiply by a hundred

.5+ add a half

 floor

Try it online!

\$\endgroup\$
0
\$\begingroup\$

Perl 6, 78 bytes

{round 100*@_.grep(6>*.day-of-week)/@_}o{Date.new("$_-01")...^*.month-*.month}

Try it online!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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