Group by with a break on date
This is probably the most straightforward way to do this (edited to handle gaps):
SELECT
  Worker
 ,WorkHours
 ,startofperiod
 ,LEAD(priorend,1,'2111-11-11') OVER (Partition By Worker ORDER BY startofperiod) AS endofperiod
FROM
  (
    SELECT
      Worker
     ,startofperiod
     ,endofperiod
     ,LAG(endofperiod) OVER (PARTITION BY Worker ORDER BY startofperiod) AS priorend --Get prior end date in case of gap
     ,WorkHours
     ,CASE
        WHEN WorkHours &lt;&gt; LAG(WorkHours,1,-1) OVER (PARTITION BY Worker ORDER BY startofperiod) THEN 'Y'  --If hours change
        WHEN LAG(endofperiod + Interval '1 DAY') OVER (PARTITION BY Worker ORDER BY startofperiod) &lt;&gt; startofperiod THEN 'Y'  --If gap
        ELSE 'N'
      END AS Change_Ind
    FROM
      workhours_over_time
  ) summary
WHERE
  Change_Ind = 'Y'

Returns:
Worker  |   WorkHours   |   startofperiod   |   endofperiod
W1      |   35          |   2020-01-01      |   2020-02-29
W1      |   39          |   2020-03-01      |   2020-04-15
W1      |   35          |   2020-04-16      |   2111-11-11

This will handle gaps as long as your intervals are correct (in your example they are not as 2020 is a leap year).  You could change the logic to LAG(endofperiod + Interval '2 DAYS') OVER (PARTITION BY Worker ORDER BY startofperiod) &lt; startofperiod or just fix the end dates.