Percentage of work days in a month

11

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

Joey

Posted 2011-02-23T23:03:11.360

Reputation: 12 260

2I'm a graduate student, so... echo 100 – Amory – 2014-11-24T20:24:48.580

Even grad students cannot escape the fundamental definitions in their line of work. And I defined work days differently ;-) – Joey – 2014-11-24T21:42:55.603

Answers

4

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)

J B

Posted 2011-02-23T23:03:11.360

Reputation: 9 638

1

Apparently it was abolished by "Siphous Hemes" during his rule. ref "A new history of the Holy Bible"

– Martin York – 2011-02-24T08:51:53.543

1Is every year after 2038 broken? Then switching t a 64-bit build might help due to some braindead-ness with date handling ;-) – Joey – 2011-02-26T10:51:11.557

@Joey that's exactly it. Thanks for the tip! – J B – 2011-02-28T13:50:37.057

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 ;-) – Joey – 2011-02-28T18:24:33.000

3

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.

zneak

Posted 2011-02-23T23:03:11.360

Reputation: 193

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.

– Titus – 2017-03-01T23:53:56.387

80 bytes: <?=.5+min(-8+$m=date(t,$t=strtotime($argn)),20+(5886>>date(w,$t)*2&3))/$m*100|0; – Titus – 2018-11-26T12:52:10.577

2

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)

fR0DDY

Posted 2011-02-23T23:03:11.360

Reputation: 4 337

2

Windows PowerShell, 80

$x=$args;1..31|%{"$x-$_"|date -u %u -ea 0}|%{$a++
$b+=!!($_%6)}
[int]($b*100/$a)

Joey

Posted 2011-02-23T23:03:11.360

Reputation: 12 260

Are you sure [int] really rounds? I'd tend to believe it floors. – zneak – 2011-02-28T04:08:37.647

@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. – Joey – 2011-02-28T18:23:01.410

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. – zneak – 2011-02-28T20:09:44.557

@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. – Joey – 2011-02-28T20:59:35.723

Necrobump but $input -> $args saves a byte. – Veskah – 2018-12-05T00:19:38.190

@Veskah Just curious... did you get here the same way I did? (PPCG Graduation Userscript Challenges of the Day -> Has my milk expired -> related challenges -> here) – Quintec – 2018-12-05T02:27:54.680

@Veskah: Usually on my challenges I'd get in the same arguments with you as with mazzy, but in this case I actually left it unspecified so it's an option here, indeed :-) – Joey – 2018-12-05T07:18:11.763

@Veskah: Apparently parameter validation has become more strict in the meantime and we need a bit more code to remove the error messages, sadly. – Joey – 2018-12-05T07:26:08.487

Bummer, guess I should've let sleeping dogs lie. – Veskah – 2018-12-05T21:21:36.400

2

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

Digital Trauma

Posted 2011-02-23T23:03:11.360

Reputation: 64 644

1

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".

Titus

Posted 2011-02-23T23:03:11.360

Reputation: 13 814

Well ... one byte: %7 instead of %28. – Titus – 2018-11-26T14:19:05.733

1

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);
}

Jonathan M Davis

Posted 2011-02-23T23:03:11.360

Reputation: 705

1

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.

Juan

Posted 2011-02-23T23:03:11.360

Reputation: 2 738

1

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.

Phrogz

Posted 2011-02-23T23:03:11.360

Reputation: 213

Why are you assigning Date to D? – Wile E. Coyote – 2011-02-28T11:48:37.380

@Dogbert Whoops! Holdover from a time when I had two Date.civils; thanks! – Phrogz – 2011-02-28T15:43:06.177

'-' could be written as ?- in Ruby 1.9 – Wile E. Coyote – 2011-02-28T16:03:56.350

@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. – Phrogz – 2011-02-28T16:22:17.590

e+1<<1 is three shorter than e-e.day+1 – steenslag – 2011-03-02T17:03:46.410

(d.wday+1)%7>1 can be replaced by d.cwday<6 – steenslag – 2011-03-02T19:33:39.447

@steenslag Those are both great, thank you. I didn't know about Date#<< previously; very cool. – Phrogz – 2011-03-02T20:18:05.737

0

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);};

adrianmp

Posted 2011-02-23T23:03:11.360

Reputation: 1 592

0

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!

Adám

Posted 2011-02-23T23:03:11.360

Reputation: 37 779

0

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)

Dr Y Wit

Posted 2011-02-23T23:03:11.360

Reputation: 511

0

Perl 6, 78 bytes

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

Try it online!

Jo King

Posted 2011-02-23T23:03:11.360

Reputation: 38 234

0

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

draegtun

Posted 2011-02-23T23:03:11.360

Reputation: 1 592