My approach differs a bit from wje_lq's. My
workdays() takes the two dates as unix timestamps (
time_t), and a 7-bit mask describing which days of week are work days. The function returns the number of work days between the two dates in the local time zone, inclusive. My version is thread-safe, and does not modify the environment (or locale or time zone) during the calculation. It does expect the time zone not to change while it runs, though.
My
workdays() function makes the following assumptions:
- localtime_r() converts a time_t to struct tm in the local timezone, and sets the tm_wday field to 0 for Sunday, 1 for Monday, .., 6 for Saturday
(localtime_t() is also thread-safe, whereas localtime() is not.)
- mktime() converts a struct tm in local timezone to time_t
- difftime() returns the number of seconds between two time_t timestamps, and it is about 86400 seconds for each day
(In any interval, there should be less than 28800 seconds (eight hours) of drift, including DST and other adjustments)
The logic in the calculation is simple: Calculate the number of days in the initial fractional week (zero if the interval is full weeks), plus the number of weeks in the interval multiplied by the number of workdays in a week.
The initial fractional week is best described using a bit pattern, since the work week is also described using a bit pattern. The left side in the
& expression is a string of one bits the fractional week long, and the right side is the work week adjusted to start from the initial date in the interval. (Since the string of one bits is at most 6 bits long, it does not matter if there are a few extra bits in the right side.) The result is the correct bit pattern for the initial fractional week, and it will be empty (zero) if the interval is full weeks.
I've only tested the code lightly, so there may still be some bugs lurking in it.
Code:
#define _XOPEN_SOURCE
#include <time.h>
#include <stdint.h>
#include <stdio.h>
/* These are the bit masks for workdays().
*/
#define SUNDAY (1U)
#define MONDAY (2U)
#define TUESDAY (4U)
#define WEDNESDAY (8U)
#define THURSDAY (16U)
#define FRIDAY (32U)
#define SATURDAY (64U)
/* This is the constant for Monday to Friday work week.
*/
#define MON_FRI (62U)
/* Return the number of bits set in mask, AKA __builtin_popcount().
*/
static inline uint32_t popcount(uint32_t value)
{
uint32_t const mask1 = 0x55555555;
uint32_t const mask2 = 0xc30c30c3;
value -= (value >> 1U) & mask1;
value = (value & mask2)
+ ((value >> 2U) & mask2)
+ ((value >> 4U) & mask2);
value += (value >> 6U);
return (value + (value >> 12U) + (value >> 24U)) & 63U;
}
/* Return the number of workdays between the two dates, inclusive.
* This uses the current time zone settings.
*/
int workdays(time_t from_time, time_t to_time, unsigned int const work_week)
{
uint32_t const week = (uint32_t)(work_week & 127U);
uint32_t const fortnight = week | (week << 7U);
struct tm from_tm, to_tm;
int days;
if (from_time < to_time) {
localtime_r(&from_time, &from_tm);
localtime_r(&to_time, &to_tm);
} else {
localtime_r(&from_time, &to_tm);
localtime_r(&to_time, &from_tm);
}
/* Use 8 hours of buffer in any direction */
from_tm.tm_hour = 8; to_tm.tm_hour = 16;
from_tm.tm_min = 0; to_tm.tm_min = 0;
from_tm.tm_sec = 0; to_tm.tm_sec = 0;
from_tm.tm_isdst = 0; to_tm.tm_isdst = 0;
from_time = mktime(&from_tm);
to_time = mktime(&to_tm);
days = 1 + (int)(difftime(to_time, from_time) / 86400.0);
return (int)popcount( (127U >> (7 - (days % 7))) & (fortnight >> from_tm.tm_wday) )
+ (int)popcount( week ) * (int)(days / 7);
}
int local_noon(char const *date, time_t *const where)
{
struct tm tm;
if (!date) {
if (where) {
time_t now = time(NULL);
localtime_r(&now, &tm);
tm.tm_hour = 12;
tm.tm_min = 0;
tm.tm_sec = 0;
tm.tm_isdst = -1;
*where = mktime(&tm);
}
return 0;
}
if (strptime(date, "%Y-%m-%d", &tm) ||
strptime(date, "%Y%m%d", &tm) ||
strptime(date, "%m/%d/%Y", &tm) ||
strptime(date, "%d.%m.%Y", &tm)) {
if (where) {
tm.tm_hour = 12;
tm.tm_min = 0;
tm.tm_sec = 0;
tm.tm_isdst = -1;
*where = mktime(&tm);
}
return 0;
}
return -1;
}
int main(int argc, char *argv[])
{
time_t time1, time2;
if (argc < 2 || argc > 3) {
fprintf(stderr, "Usage: yyyy-mm-dd [ yyyy-mm-dd ]\n");
return (argc == 1) ? 0 : 1;
}
if (local_noon(argv[1], &time1)) {
fprintf(stderr, "%s: Invalid date.\n", argv[1]);
return 1;
}
if (argc > 2) {
if (local_noon(argv[2], &time2)) {
fprintf(stderr, "%s: Invalid date.\n", argv[2]);
return 1;
}
} else
local_noon(NULL, &time2);
printf("%d\n", workdays(time1, time2, MON_FRI));
return 0;
}