@@ -724,6 +724,7 @@ type formulaFuncs struct {
724
724
// WEEKNUM
725
725
// WEIBULL
726
726
// WEIBULL.DIST
727
+ // WORKDAY.INTL
727
728
// XIRR
728
729
// XLOOKUP
729
730
// XNPV
@@ -12552,6 +12553,197 @@ func (fn *formulaFuncs) MONTH(argsList *list.List) formulaArg {
12552
12553
return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Month()))
12553
12554
}
12554
12555
12556
+ // genWeekendMask generate weekend mask of a series of seven 0's and 1's which
12557
+ // represent the seven weekdays, starting from Monday.
12558
+ func genWeekendMask(weekend int) []byte {
12559
+ mask := make([]byte, 7)
12560
+ if masks, ok := map[int][]int{
12561
+ 1: {5, 6}, 2: {6, 0}, 3: {0, 1}, 4: {1, 2}, 5: {2, 3}, 6: {3, 4}, 7: {4, 5},
12562
+ 11: {6}, 12: {0}, 13: {1}, 14: {2}, 15: {3}, 16: {4}, 17: {5},
12563
+ }[weekend]; ok {
12564
+ for _, idx := range masks {
12565
+ mask[idx] = 1
12566
+ }
12567
+ }
12568
+ return mask
12569
+ }
12570
+
12571
+ // isWorkday check if the date is workday.
12572
+ func isWorkday(weekendMask []byte, date float64) bool {
12573
+ dateTime := timeFromExcelTime(date, false)
12574
+ weekday := dateTime.Weekday()
12575
+ if weekday == time.Sunday {
12576
+ weekday = 7
12577
+ }
12578
+ return weekendMask[weekday-1] == 0
12579
+ }
12580
+
12581
+ // prepareWorkday returns weekend mask and workdays pre week by given days
12582
+ // counted as weekend.
12583
+ func prepareWorkday(weekend formulaArg) ([]byte, int) {
12584
+ weekendArg := weekend.ToNumber()
12585
+ if weekendArg.Type != ArgNumber {
12586
+ return nil, 0
12587
+ }
12588
+ var weekendMask []byte
12589
+ var workdaysPerWeek int
12590
+ if len(weekend.Value()) == 7 {
12591
+ // possible string values for the weekend argument
12592
+ for _, mask := range weekend.Value() {
12593
+ if mask != '0' && mask != '1' {
12594
+ return nil, 0
12595
+ }
12596
+ weekendMask = append(weekendMask, byte(mask)-48)
12597
+ }
12598
+ } else {
12599
+ weekendMask = genWeekendMask(int(weekendArg.Number))
12600
+ }
12601
+ for _, mask := range weekendMask {
12602
+ if mask == 0 {
12603
+ workdaysPerWeek++
12604
+ }
12605
+ }
12606
+ return weekendMask, workdaysPerWeek
12607
+ }
12608
+
12609
+ // toExcelDateArg function converts a text representation of a time, into an
12610
+ // Excel date time number formula argument.
12611
+ func toExcelDateArg(arg formulaArg) formulaArg {
12612
+ num := arg.ToNumber()
12613
+ if num.Type != ArgNumber {
12614
+ dateString := strings.ToLower(arg.Value())
12615
+ if !isDateOnlyFmt(dateString) {
12616
+ if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError {
12617
+ return err
12618
+ }
12619
+ }
12620
+ y, m, d, _, err := strToDate(dateString)
12621
+ if err.Type == ArgError {
12622
+ return err
12623
+ }
12624
+ num.Number, _ = timeToExcelTime(time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.UTC), false)
12625
+ return newNumberFormulaArg(num.Number)
12626
+ }
12627
+ if arg.Number < 0 {
12628
+ return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
12629
+ }
12630
+ return num
12631
+ }
12632
+
12633
+ // prepareHolidays function converts array type formula arguments to into an
12634
+ // Excel date time number formula arguments list.
12635
+ func prepareHolidays(args formulaArg) []int {
12636
+ var holidays []int
12637
+ for _, arg := range args.ToList() {
12638
+ num := toExcelDateArg(arg)
12639
+ if num.Type != ArgNumber {
12640
+ continue
12641
+ }
12642
+ holidays = append(holidays, int(math.Ceil(num.Number)))
12643
+ }
12644
+ return holidays
12645
+ }
12646
+
12647
+ // workdayIntl is an implementation of the formula function WORKDAY.INTL.
12648
+ func workdayIntl(endDate, sign int, holidays []int, weekendMask []byte, startDate float64) int {
12649
+ for i := 0; i < len(holidays); i++ {
12650
+ holiday := holidays[i]
12651
+ if sign > 0 {
12652
+ if holiday > endDate {
12653
+ break
12654
+ }
12655
+ } else {
12656
+ if holiday < endDate {
12657
+ break
12658
+ }
12659
+ }
12660
+ if sign > 0 {
12661
+ if holiday > int(math.Ceil(startDate)) {
12662
+ if isWorkday(weekendMask, float64(holiday)) {
12663
+ endDate += sign
12664
+ for !isWorkday(weekendMask, float64(endDate)) {
12665
+ endDate += sign
12666
+ }
12667
+ }
12668
+ }
12669
+ } else {
12670
+ if holiday < int(math.Ceil(startDate)) {
12671
+ if isWorkday(weekendMask, float64(holiday)) {
12672
+ endDate += sign
12673
+ for !isWorkday(weekendMask, float64(endDate)) {
12674
+ endDate += sign
12675
+ }
12676
+ }
12677
+ }
12678
+ }
12679
+ }
12680
+ return endDate
12681
+ }
12682
+
12683
+ // WORKDAYdotINTL function returns a date that is a supplied number of working
12684
+ // days (excluding weekends and holidays) ahead of a given start date. The
12685
+ // function allows the user to specify which days of the week are counted as
12686
+ // weekends. The syntax of the function is:
12687
+ //
12688
+ // WORKDAY.INTL(start_date,days,[weekend],[holidays])
12689
+ //
12690
+ func (fn *formulaFuncs) WORKDAYdotINTL(argsList *list.List) formulaArg {
12691
+ if argsList.Len() < 2 {
12692
+ return newErrorFormulaArg(formulaErrorVALUE, "WORKDAY.INTL requires at least 2 arguments")
12693
+ }
12694
+ if argsList.Len() > 4 {
12695
+ return newErrorFormulaArg(formulaErrorVALUE, "WORKDAY.INTL requires at most 4 arguments")
12696
+ }
12697
+ startDate := toExcelDateArg(argsList.Front().Value.(formulaArg))
12698
+ if startDate.Type != ArgNumber {
12699
+ return startDate
12700
+ }
12701
+ days := argsList.Front().Next().Value.(formulaArg).ToNumber()
12702
+ if days.Type != ArgNumber {
12703
+ return days
12704
+ }
12705
+ weekend := newNumberFormulaArg(1)
12706
+ if argsList.Len() > 2 {
12707
+ weekend = argsList.Front().Next().Next().Value.(formulaArg)
12708
+ }
12709
+ var holidays []int
12710
+ if argsList.Len() == 4 {
12711
+ holidays = prepareHolidays(argsList.Back().Value.(formulaArg))
12712
+ sort.Ints(holidays)
12713
+ }
12714
+ if days.Number == 0 {
12715
+ return newNumberFormulaArg(math.Ceil(startDate.Number))
12716
+ }
12717
+ weekendMask, workdaysPerWeek := prepareWorkday(weekend)
12718
+ if workdaysPerWeek == 0 {
12719
+ return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
12720
+ }
12721
+ sign := 1
12722
+ if days.Number < 0 {
12723
+ sign = -1
12724
+ }
12725
+ offset := int(days.Number) / workdaysPerWeek
12726
+ daysMod := int(days.Number) % workdaysPerWeek
12727
+ endDate := int(math.Ceil(startDate.Number)) + offset*7
12728
+ if daysMod == 0 {
12729
+ for !isWorkday(weekendMask, float64(endDate)) {
12730
+ endDate -= sign
12731
+ }
12732
+ } else {
12733
+ for daysMod != 0 {
12734
+ endDate += sign
12735
+ if isWorkday(weekendMask, float64(endDate)) {
12736
+ if daysMod < 0 {
12737
+ daysMod++
12738
+ continue
12739
+ }
12740
+ daysMod--
12741
+ }
12742
+ }
12743
+ }
12744
+ return newNumberFormulaArg(float64(workdayIntl(endDate, sign, holidays, weekendMask, startDate.Number)))
12745
+ }
12746
+
12555
12747
// YEAR function returns an integer representing the year of a supplied date.
12556
12748
// The syntax of the function is:
12557
12749
//
0 commit comments