aboutsummaryrefslogtreecommitdiff
path: root/challenge-178/bob-lied/perl/ch-2.pl
blob: 3cca4d798f41123dbd736954e0ad13c90bf4c3ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#!/usr/bin/env perl
# vim:set ts=4 sw=4 sts=4 et ai wm=0 nu:
#=============================================================================
# ch-2.pl Perl Weekly Challenge Week 178 Task 2 Business Date
#=============================================================================
# Copyright (c) 2023, Bob Lied
#=============================================================================
# You are given $timestamp (date with time) and $duration in hours.
# Write a script to find the time that occurs $duration business hours after
# $timestamp. For the sake of this task, let us assume the working hours is
# 9am to 6pm, Monday to Friday. Please ignore timezone too.
# For example,
# Suppose the given timestamp is 2022-08-01 10:30 and the duration is 4 hours.
# Then the next business date would be 2022-08-01 14:30.
# If the given timestamp is 2022-08-01 17:00 and the duration is 3.5 hours,
# then the next business date would be 2022-08-02 11:30.
#=============================================================================

use v5.36;

use Time::Local;
use Time::Piece;
use Time::Seconds;

my $StartHour = "09:00";
my $EndHour   = "18:00";
my $StartTime = Time::Piece->strptime($StartHour, "%H:%M");
my $EndTime  = Time::Piece->strptime($EndHour, "%H:%M");
my $DayLength = $EndTime - $StartTime;

use Getopt::Long;
my $Verbose = 0;
my $DoTest  = 0;

GetOptions("test" => \$DoTest, "verbose" => \$Verbose);
exit(!runTest()) if $DoTest;

my ($ts, $duration) = @ARGV;

say businessDate($ts, $duration);

sub businessDate($timestamp, $duration)
{
    my $ts = Time::Piece->strptime($timestamp, "%F %R");

    my $startDOW = $ts->day_of_week;
    if ( $startDOW == 0 ) # Start day is Sunday, jump to Monday
    {
        $ts += ONE_DAY;
        $ts = Time::Piece->strptime($ts->ymd." $StartHour", "%F %R");
    }
    elsif ( $startDOW == 6 ) # Jump from Saturday to Monday morning
    {
        $ts += ONE_DAY * 2;
        $ts = Time::Piece->strptime($ts->ymd." $StartHour", "%F %R");
    }

    my $remainingTime = Time::Seconds->new($duration * ONE_HOUR);

    my $dayStart = Time::Piece->strptime( $ts->ymd." $StartHour", "%F %R");
    my $dayEnd   = Time::Piece->strptime( $ts->ymd." $EndHour",   "%F %R");

    if ( ($ts + $remainingTime) <= $dayEnd )
    {
        # All within the same day, this is easy.
        return ($ts + $remainingTime)->strftime("%F %R");
    }

    my $busDate = $ts;

    # Add weekends for each full week.
    my $workWeeks = int($remainingTime / ($DayLength*5));
    $busDate += ($workWeeks * 7) * ONE_DAY;
    $remainingTime -= $workWeeks * 5 * $DayLength;

    # Add full days.  Account for weekends as we wrap from one day to the
    # next.  day_of_week maps M:F to 1:5, 
    my $days = int($remainingTime / $DayLength);
    my $workDay = $busDate->day_of_week - 1; # 0-4, Monday=0

    # If we wrap around, add two days for the weekend
    if ( ( ($workDay + $days) % 5) < $workDay )
    {
        $busDate += ONE_DAY * 2;
    }
    $busDate += ONE_DAY * $days;

    $remainingTime -= $days * $DayLength;

    # Now we have part of a day left.
    $dayEnd = Time::Piece->strptime( $busDate->ymd." $EndHour", "%F %R");
    if ( ($busDate + $remainingTime) <= $dayEnd )
    {
        return ($busDate + $remainingTime)->strftime("%F %R");
    }

    # Wrap around to the next day.  Handle Friday afternoon.
    $remainingTime -= ($dayEnd - $busDate);
    if ( $busDate->day_of_week == 5 )
    {
        # Jump from Friday afternoon to Monday
        $busDate += ONE_DAY * 3;
    }
    else
    {
        $busDate += ONE_DAY;
    }
    # Back up to the start of the day and add the remaining time.
    $busDate = Time::Piece->strptime($busDate->ymd." $StartHour", "%F %R");
    $busDate += $remainingTime;
    return $busDate->strftime("%F %R");
}

sub runTest
{
    use Test2::V0;

    is( businessDate("2022-08-01 10:30", 4),   "2022-08-01 14:30", "Example 1");
    is( businessDate("2022-08-01 17:00", 3.5), "2022-08-02 11:30", "Example 2");
    is( businessDate("2022-08-01 17:00", 11),  "2022-08-03 10:00", "2 days");
    is( businessDate("2022-08-05 16:00",  3),  "2022-08-08 10:00", "Friday afternoon");
    is( businessDate("2022-08-01 09:00", 45),  "2022-08-08 09:00", "45-hour week");

    done_testing;
}