# Percentage of work days in a month

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

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

## 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)
• Apparently it was abolished by "Siphous Hemes" during his rule. ref "A new history of the Holy Bible" Commented Feb 24, 2011 at 8:51
• Is every year after 2038 broken? Then switching t a 64-bit build might help due to some braindead-ness with date handling ;-)
– Joey
Commented Feb 26, 2011 at 10:51
• @Joey that's exactly it. Thanks for the tip!
– J B
Commented Feb 28, 2011 at 13:50
• 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
Commented Feb 28, 2011 at 18:24

# Swift, 346 bytes

Short version:

func f(_ s:String)->Int{let c=Calendar.current,m=Int(s.suffix(2))!,y=Int(s.prefix(4))!,mR=c.range(of:.day,in:.month,for:DateComponents(calendar:c,year:y,month:m).date!)!,n=mR.reduce(0){p,d in let d=c.date(from: DateComponents(year:y,month:m,day:d))!,w=c.component(.weekday,from:d);return(2...6).contains(w) ?p+1:p};return (n*1000/mR.count+5)/10}


Slightly longer version:

func f2(_ s: String) -> Int {
let c=Calendar.current
let m=Int(s.suffix(2))!
let y=Int(s.prefix(4))!
let mR=c.range(of:.day,in:.month,for:DateComponents(calendar:c,year:y,month:m).date!)!
let n=mR.reduce(0){p,d in
let d=c.date(from: DateComponents(year:y,month:m,day:d))!
let w=c.component(.weekday,from:d)
return(2...6).contains(w) ?p+1:p
}
return (n*1000/mR.count+5)/10
}

• That's not the most golf-friendly of languages, is it? ;-)
– Joey
Commented Jan 10 at 8:35
• @Joey yes indeed - sometimes it feels like hoping one does not come in last place :-) Commented Jan 11 at 18:09

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

• 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. Commented Mar 1, 2017 at 23:53 • 80 bytes: <?=.5+min(-8+$m=date(t,$t=strtotime($argn)),20+(5886>>date(w,$t)*2&3))/$m*100|0; Commented Nov 26, 2018 at 12:52

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)


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


# 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!

$x=$args;1..31|%{"$x-$_"|date -u %u -ea 0}|%{$a++$b+=!!($_%6)} [int]($b*100/$a)  • Are you sure [int] really rounds? I'd tend to believe it floors. Commented Feb 28, 2011 at 4:08 • @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 Commented Feb 28, 2011 at 18:23 • 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. Commented Feb 28, 2011 at 20:09 • @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 Commented Feb 28, 2011 at 20:59 • Necrobump but $input -> $args saves a byte. Commented Dec 5, 2018 at 0:19 # R, 65 bytes \(a)round(mean(format(as.Date(paste0(a,-1:-31)),"%u")<6,n=T)*100)  Attempt This Online! • as.Date(paste0(a,-1:-31)) is producing days 1 to 31 of the month; for the shorter months the invalid dates are turned into NAs • format(...,"%u") conveniently turns the dates into their respective weekdays as the numbers 1 to 7; • <6: workdays 1-5 are TRUE, weekend is FALSE • mean over booleans gives the ratio N(TRUE values) / N(all values), with the argument n=T removing the NAs # GNU date+bc, 56 bytes ## Abstract This Answer demonstrates that the following 56-byte shell pipeline calculates the percentage of work days in a given month, rounded to the nearest whole percent: date -d$1-1month-4week +'70+%d-3*(%w+1<%d)-3*(%u<%d)'|bc


## Analysis

There are 28 possible monthly calendars, that can be identified according to the last day/date of the month:

Mon Tue Wed Thu Fri Sat Sun
28th 20/28 20/28 20/28 20/28 20/28 20/28 20/28
29th 21/29 21/29 21/29 21/29 21/29 20/29 20/29
30th 21/30 22/30 22/30 22/30 22/30 21/30 20/30
31st 21/31 22/31 23/31 23/31 23/31 22/31 21/31

There are nine distinct fractions here, that can be converted to percentages:

20/ 21/ 22/ 23/
/28 71
/29 69 72
/30 67 70 73
/31 68 71 74

Substituting these gives our conversion table:

Mon Tue Wed Thu Fri Sat Sun
28th 71 71 71 71 71 71 71
29th 72 72 72 72 72 69 69
30th 70 73 73 73 73 70 67
31st 68 71 74 74 74 71 68

The pattern becomes more clearly evident when we split this into a basic value for a month containing four weekends, less an amount for a fifth Saturday and for a fifth Sunday:

Basic Thu Fri Sat Sun Mon Tue Wed
28th 71 .       . .       . .       . .       . .       . .       . .       .
29th 72 .       . .       . .      -3 -3      . .       . .       . .       .
30th 73 .       . .       . .      -3 -3    -3 -3      . .       . .       .
31st 74 .       . .       . .      -3 -3    -3 -3    -3 -3      . .       .

The basic value is clearly 71 plus one for each day more than 28 days.

And we must then subtract 3 if the last day is in a range beginning Saturday and having length equal to those excess days, less three for a similar range beginning Sunday.

## Implementation

Firstly, we append a day number to turn year and month into a full date: $1-01. Rather than finding the last day of the month, we select a date nearer the start of the month, by subtracting 28 days from the start of the following month. This gives us a day-of-week that's one later than used in the analysis section: date -d'$1-01 +1month-4week'


Using this date, we then format a calculation for bc to execute and print. The basic percentage is simple; it's just 70 more than the day number we landed on: 70+%d.

The adjustments for extra weekend days are then computed using a pair of inequalities, taking advantage of the fact that there are two representations for day-of-week, with Sunday represented as 0 or as 7. We subtract 3 points from the basic percentage for each of those inequalities.

## Demo

We can reproduce the results from the examples in the question:

And we can show that we get the correct results for all 28 shapes of month shown in the first table:

for i in $(printf '%s\n' 20{{00..03}-{03..12},{00..24}-02}-01+month-1day | date -f- +'%Y-%m%t%d %u %a' | sort -k2,3 -u | cut -f1) do ./1158$i
done | rs 0 7

71  71  71  71  71  71  71
72  72  72  72  72  69  69
70  73  73  73  73  70  67
68  71  74  74  74  71  68


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


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

## Ruby, 124119 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.

• Why are you assigning Date to D? Commented Feb 28, 2011 at 11:48
• @Dogbert Whoops! Holdover from a time when I had two Date.civils; thanks! Commented Feb 28, 2011 at 15:43
• '-' could be written as ?- in Ruby 1.9 Commented Feb 28, 2011 at 16:03
• @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. Commented Feb 28, 2011 at 16:22
• e+1<<1 is three shorter than e-e.day+1 Commented Mar 2, 2011 at 17:03

# Bash + coreutils, 82 bytes

f()(cal -NMd$1|sed -n "s/^$2.//p"|wc -w)
dc -ef $1 "[^S ]"df$1 S+r200*r/1+2/p


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

• Well ... one byte: %7 instead of %28. Commented Nov 26, 2018 at 14:19

# Japt, 36 34 bytes

HÆÐU+XnÃf@¤¤ÅÉ¶XÎÃme


Try it

Not pretty at all! Needs more work.

## 34 32 bytes

2 bytes can be saved by always rounding down as the challenge doesn't specify that we must round to the nearest integer:

HÆÐU+XnÃf@¤¤ÅÉ¶XÎÃme


Try it

# Go, 243 bytes

import(."time";."math")
func f(s string)int{t,_:=Parse("2006-01",s)
D,o:=Hour*24,0.0
y,m,_:=t.Date()
return int(Round(100*o/d))}


Attempt This Online!

Loops through the days of the month and checks each for being a weekday (between 0=Sunday and 6=Saturday).

# Bash + ncal, 75 70 bytes

m=ncal ${1#*-}${1%-*}
set $m wc -w<<<${m%%Sa*}|dc -e ?7-A0*$#\ 9-/p  We start by using ncal to print a calendar:  May 2010 Mo 3 10 17 24 31 Tu 4 11 18 25 We 5 12 19 26 Th 6 13 20 27 Fr 7 14 21 28 Sa 1 8 15 22 29 Su 2 9 16 23 30  The total number of days in the month is the word-count of this output, minus 9 (the seven weekday names and the month and year of the header). This word-count is obtained using set and $#.

The number of work days is a similar calculation, but we first eliminate everything from Sa onwards (we can't just match S because the month may be Sep), and subtract only 7 surplus words. We count this using wc.

We then use dc to compute a percentage (using A0 as short form of 100).

Note that this program rounds fractional percentages downwards - the question's examples appear to use a different choice for the rounding, but it didn't impose a specific direction. Rounding to nearest would cost an additional 7 bytes by executing $#\ 2/+ before we divide by the total: ?7-A0*$#\ 2/+$#\ 9-/p. # Python, 131113 104 bytes def f(y,m):d,r=monthrange(y,m);return round(sum(a%7<5for a in range(d,r+d))*100/r) from calendar import*  Attempt This Online! # 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  # 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)  # Perl 6, 78 bytes {round 100*@_.grep(6>*.day-of-week)/@_}o{Date.new("$_-01")...^*.month-*.month}


Try it online!

# Scala, 152 bytes

Golfed version. Try it online!

s=>{val d=java.time.YearMonth.parse(s);val t=d.lengthOfMonth;var w=0;for(i<-0 to t-1)if(d.atDay(i+1).getDayOfWeek.getValue<6)w+=1;Math.round(100.0*w/t)}


Ungolfed version. Try it online!

import java.time._
import java.time.temporal._

object Main {
def main(args: Array[String]): Unit = {
val f: String => Double = s => {
val d = YearMonth.parse(s)
val totalDays = d.lengthOfMonth
var workingDays = 0

for (i <- 0 until totalDays) {
val dayOfWeek = d.atDay(i + 1).getDayOfWeek
if (dayOfWeek.getValue >= DayOfWeek.MONDAY.getValue && dayOfWeek.getValue <= DayOfWeek.FRIDAY.getValue) {
workingDays += 1
}
}

Math.round(100.0 * workingDays / totalDays)
}

// test cases:
println(f("2010-05")) // 68
println(f("2010-06")) // 73
println(f("1920-10")) // 68
println(f("2817-12")) // 68
}
}


# Java, 203 187 bytes

Saved 16 bytes thanks to @ceilingcat

Golfed version. Attempt This Online!

static double f(String s){var d=YearMonth.parse(s);var e=d.lengthOfMonth();return Math.round(100.*IntStream.rangeClosed(1,e).filter(i->d.atDay(i).getDayOfWeek().getValue()<6).count()/e);}


Ungolfed version. Attempt This Online!

import java.time.*;
import java.time.temporal.*;

public class Main {
public static double f(String s) {
YearMonth d = YearMonth.parse(s);
int totalDays = d.lengthOfMonth();
int workingDays = 0;

for (int i = 0; i < totalDays; i++) {
DayOfWeek dayOfWeek = d.atDay(i + 1).getDayOfWeek();
if (dayOfWeek.getValue() >= DayOfWeek.MONDAY.getValue() && dayOfWeek.getValue() <= DayOfWeek.FRIDAY.getValue()) {
workingDays++;
}
}

return (double) Math.round(100.0 * workingDays / totalDays);
};

public static void main(String[] args) {

// Test cases:
System.out.println(f("2010-05")); // Should print 68
System.out.println(f("2010-06")); // Should print 73
System.out.println(f("1920-10")); // Should print 68
System.out.println(f("2817-12")); // Should print 68
}
}


# 05AB1E, 131 bytes

'-¡Dˆ1)RV0[YUÐ¯нÊ#3‹©12*+>₂*T÷®Xα©т%D4÷®т÷©4÷®·()ćsO7%2@+Yт‰0Kθ4ÖUD<i\28X+ë<7%É31α}‹iY¬>0ëY1¾ǝDÅsD12‹i>1ë\1Dǝ¤>2}}ǝDVˆ]r¯¨θн/т*ò


Explanation:

05AB1E lacks date builtins, so all calculations are done manually using Zeler's congruence:

$${\displaystyle h=\left(q+\left\lfloor{\frac{13(m+1)}{5}}\right\rfloor+K+\left\lfloor{\frac{K}{4}}\right\rfloor+\left\lfloor{\frac{J}{4}}\right\rfloor-2J\right){\bmod{7}}}$$

Where for the months March through December:

• $$\q\$$ is the $$\day\$$ of the month ([1, 31])
• $$\m\$$ is the 1-indexed $$\month\$$ ([3, 12])
• $$\K\$$ is the year of the century ($$\year \bmod 100\$$)
• $$\J\$$ is the 0-indexed century ($$\\left\lfloor {\frac {year}{100}}\right\rfloor\$$)

And for the months January and February:

• $$\q\$$ is the $$\day\$$ of the month ([1, 31])
• $$\m\$$ is the 1-indexed $$\month + 12\$$ ([13, 14])
• $$\K\$$ is the year of the century for the previous year ($$\(year - 1) \bmod 100\$$)
• $$\J\$$ is the 0-indexed century for the previous year ($$\\left\lfloor {\frac {year-1}{100}}\right\rfloor\$$)

Resulting in in the day of the week $$\h\$$, where 0 = Saturday, 1 = Sunday, ..., 6 = Friday.

The code modifications done compared to that answer:

'-¡          '# Split the (implicit) input-string on "-"
# Pop and push both separately to the stack
Dˆ        # Add a copy of the month to the global array
1       # Push a 1
)      # Wrap all three values into a triplet
R     # Reverse it
V    # Pop and store this 1st day of the input-month/year in variable Y
[            # Start an infinite loop:
YU         #  See linked answer (but does push year and month to the stack)
Ð        #  Triplicate instead of duplicate
¯       #  Push the global array
н      #  Pop and push its first item (the input-month)
Ê     #  Check that it's not equal to the current triplicated month
#    #  If they're not equal: stop the infinite loop
D             #  Duplicate this result
V            #  Pop and store this next day in variable Y
ˆ           #  Pop and add this next day to the global array
]          # After the infinite loop:
r         # Reverse all values on the stack, since we don't care about the top year
# nor month, but the workdayCount instead
¯        # Push the global array
¨       # Remove the last added day (1st of the next month)
θ      # Pop and push the last day (last day of the input-month)
н     # Pop and push its first item (the day)
/    # Divide the workdayCount by this day
т*  # Multiply it by 100
ò # Round it to the nearest integer
# (after which the result is output implicitly)