工作时长计算
流程图
设计程序部分代码
关节点一:获取2022年节假日
def get_holiday():
list_holiday=[]
year=2022
for month in range(1,13):
for day in range(1,32):
try:
dt=date(year,month,day)
except:
break
if is_holiday(dt):
#chinese_calendar库下的is_holiday()函数来判断是否为假期
list_holiday.append('{}-{:02d}-{:02d}'.format(year,month,day))
return list_holiday
关节点二:计算起止工作时长
根据不同的起止时间来对工作时长进行计算
def datediff_no_holiday(start,end,list_holiday):
"""Params:
start:开始时间
end:结束时间
'yyyy-mm-dd hh24:mi:ss'格式字符串"""
# print(end)
if start>=end or not start or not end:return 0
result=0
if end == 'Na ':
return 0
list_start=start.split(' ')
start=datetime.strptime(start, '%Y-%m-%d %H:%M:%S')
start_d=datetime.strptime(list_start[0],'%Y-%m-%d')
list_end=end.split(' ')
end=datetime.strptime(end, '%Y-%m-%d %H:%M:%S')
end_d=datetime.strptime(list_end[0],'%Y-%m-%d')
#首先判断结束时间是否在工作时间内,如果早于当天工作时间则转换为上班时间,如果大于则转换为后一天上班时间
if list_end[1]>'17:30:00':
end=end_d+timedelta(hours=17,minutes=30)
if list_end[1]<'08:30:00':
end=end_d+timedelta(hours=8, minutes=30)
list_end[1]='08:30:00'
#同理判断开始时间,如果大于则转换为后一天上班时间
if list_start[1]>'17:30:00':
start_d+=timedelta(days=1)
start=start_d+timedelta(hours=8, minutes=30)
list_start[1]='08:30:00'
#判断是否在节假日(包括不补班的周末)中,如果是则转入下一天进行循环判断
if start_d in list_holiday:
while start_d in list_holiday:
start_d+=timedelta(days=1)
start=start_d+timedelta(hours=8, minutes=30)
list_start[1]='08:30:00'
if end_d in list_holiday:
while end_d in list_holiday:
end_d+=timedelta(days=1)
end=end_d+timedelta(hours=8, minutes=30)
list_end[1]='08:30:00'
#判断开始时间,如果早于当天工作时间则转换为当天上班时间
if list_start[1]<'08:30:00':
start=start_d+timedelta(hours=8, minutes=30)
#剔除12点到13点的午休时间
if '12:00:00'<list_start[1]<'13:00:00':
start=start_d+timedelta(hours=13)
list_start[1]='13:00:00'
if start>=end:return 0
#如果开始与结束在同一日期里
if start_d==end_d:
result=(end-start).seconds
if list_start[1]<='12:00:00': #如果开始时间在上午
if list_end[1]>='13:00:00':
result-=3600
if '12:00:00'<list_end[1]<'13:00:00':
result-=(end-(end_d+timedelta(hours=12))).seconds
#如果开始与结束不在同一日期里,排除中间可能存在的节假日
else:
if list_start[1]<='12:00:00':
result-=3600
result+=(start_d+timedelta(hours=17,minutes=30)-start).seconds
start_d+=timedelta(days=1) #
while start_d<end_d:
if start_d not in list_holiday:
result+=28800 #每日工时8*3600秒
start_d+=timedelta(days=1)
result+=(end-(end_d+timedelta(hours=8, minutes=30))).seconds
if list_end[1]>='13:00:00':
result-=3600
elif '12:00:00'<list_end[1]<'13:00:00':
result-=(end-(end_d+timedelta(hours=12))).seconds
return result if result>0 else 0
"""返回相差的秒数"""
关节点三:对文件进行读取,并保存文件
从wxPython界面读取到导入文件的目录,对目录上的文件读取第一个sheet页,读取期中的‘审核开始时间‘,‘审核结束时长’,保存进data变量里,将每一行的开始时长和结束时长传给datediff_no_holiday()
函数进行工作时长计算,并返回计算结果,保存进一个list变量里,遍历完成之后,将此list列存储进excel表格中。
def col(path):
data = []
data1 = []
begin_time = []
end_time = []
hours = []
text = pd.read_excel(path,sheet_name = 0)
data.append(text['审核开始时间'].values)
data.append(text['审核结束时间'].values)
data1.append(str1(data[0]))
data1.append(str1(data[1]))
list_holiday=get_holiday()
list_holiday=list( map(lambda x : datetime.strptime(x,'%Y-%m-%d'), list_holiday) )
for i in range(len(data[0])):
hours.append((datediff_no_holiday(data1[0][i],data1[1][i],list_holiday))/3600)
text.insert(loc=len(text.columns), column='有效审核时长(小时)', value=hours)
path2 = path[0:path.rfind('.')] + '2' + path[path.rfind('.'):]
print(path2)
try:
text.to_excel(path2,engine='openpyxl')
except Exception as e:
print(e)
return(hours)
程序完整代码
# -*- coding: utf-8 -*-
from datetime import date
from datetime import datetime
from datetime import timedelta
import pandas as pd
from pathlib import Path
import wx
import openpyxl
import xlwt
from xlutils.copy import copy
import xlrd
import openpyxl
from xlwt import Style
import xlsxwriter
import arrow
import time
from chinese_calendar import is_holiday
import math
class MyFrame1(wx.Frame):
'''
设计wxPython图形化界面类,方便操作
'''
def __init__(self, parent):
#设计标题和窗口大小
wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=u"审核时长计算", pos=wx.DefaultPosition, size=wx.Size(400, 320),
style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL)
self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
bSizer1 = wx.BoxSizer(wx.VERTICAL)
self.m_statictext = wx.StaticText(self, wx.ID_ANY, u"请选择文件", wx.DefaultPosition, wx.DefaultSize, 0)
bSizer1.Add(self.m_statictext, 0, wx.ALL | wx.EXPAND, 5)
self.m_filePicker1 = wx.FilePickerCtrl(self, wx.ID_ANY, wx.EmptyString, u"选择名单",
u"EXCEL文件(*.xls;*.xlsx)|*.xls;*.xlsx|所有文件(*.*)|*.*", wx.DefaultPosition,
wx.DefaultSize, wx.FLP_DEFAULT_STYLE | wx.FLP_FILE_MUST_EXIST,
wx.DefaultValidator, u"选择文件")
bSizer1.Add(self.m_filePicker1, 0, wx.ALL | wx.EXPAND, 5)
#设计点击按钮
self.m_button1 = wx.Button(self, wx.ID_ANY, u"确认", wx.DefaultPosition, wx.DefaultSize, 0)
self.m_button1.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))
bSizer1.Add(self.m_button1, 0, wx.ALL | wx.ALIGN_CENTER, 5)
self.SetSizer(bSizer1)
self.Layout()
self.m_statusBar1 = self.CreateStatusBar(1, wx.STB_SIZEGRIP, wx.ID_ANY)
# print(self.m_filePicker4.GetPath())
self.m_button1.Bind(wx.EVT_BUTTON, self.m_button1OnButtonClick)
#设计点击事件
def m_button1OnButtonClick(self, event):
data = []
path = self.m_filePicker1.GetPath() # 获取当前选中文件的路径
#开始计算
col(path)
#设计提示框
box=wx.MessageDialog(None,'修改完毕','Message',wx.OK)
answer=box.ShowModal()
box.Destroy()
def get_holiday():
list_holiday=[]
year=2022
for month in range(1,13):
for day in range(1,32):
try:
dt=date(year,month,day)
except:
break
if is_holiday(dt):
list_holiday.append('{}-{:02d}-{:02d}'.format(year,month,day))
return list_holiday
def datediff_no_holiday(start,end,list_holiday):
"""Params:
start:开始时间
end:结束时间
'yyyy-mm-dd hh24:mi:ss'格式字符串"""
# print(end)
if start>=end or not start or not end:return 0
result=0
if end == 'Na ':
return 0
list_start=start.split(' ')
start=datetime.strptime(start, '%Y-%m-%d %H:%M:%S')
start_d=datetime.strptime(list_start[0],'%Y-%m-%d')
list_end=end.split(' ')
end=datetime.strptime(end, '%Y-%m-%d %H:%M:%S')
end_d=datetime.strptime(list_end[0],'%Y-%m-%d')
#首先判断结束时间是否在工作时间内,如果早于当天工作时间则转换为上班时间,如果大于则转换为后一天上班时间
if list_end[1]>'17:30:00':
end=end_d+timedelta(hours=17,minutes=30)
if list_end[1]<'08:30:00':
end=end_d+timedelta(hours=8, minutes=30)
list_end[1]='08:30:00'
#同理判断开始时间,如果大于则转换为后一天上班时间
if list_start[1]>'17:30:00':
start_d+=timedelta(days=1)
start=start_d+timedelta(hours=8, minutes=30)
list_start[1]='08:30:00'
#判断是否在节假日(包括不补班的周末)中,如果是则转入下一天进行循环判断
if start_d in list_holiday:
while start_d in list_holiday:
start_d+=timedelta(days=1)
start=start_d+timedelta(hours=8, minutes=30)
list_start[1]='08:30:00'
if end_d in list_holiday:
while end_d in list_holiday:
end_d+=timedelta(days=1)
end=end_d+timedelta(hours=8, minutes=30)
list_end[1]='08:30:00'
#判断开始时间,如果早于当天工作时间则转换为当天上班时间
if list_start[1]<'08:30:00':
start=start_d+timedelta(hours=8, minutes=30)
#剔除12点到13点的午休时间
if '12:00:00'<list_start[1]<'13:00:00':
start=start_d+timedelta(hours=13)
list_start[1]='13:00:00'
if start>=end:return 0
#如果开始与结束在同一日期里
if start_d==end_d:
result=(end-start).seconds
if list_start[1]<='12:00:00': #如果开始时间在上午
if list_end[1]>='13:00:00':
result-=3600
if '12:00:00'<list_end[1]<'13:00:00':
result-=(end-(end_d+timedelta(hours=12))).seconds
#如果开始与结束不在同一日期里,排除中间可能存在的节假日
else:
if list_start[1]<='12:00:00':
result-=3600
result+=(start_d+timedelta(hours=17,minutes=30)-start).seconds
start_d+=timedelta(days=1) #
while start_d<end_d:
if start_d not in list_holiday:
result+=28800 #每日工时8*3600秒
start_d+=timedelta(days=1)
result+=(end-(end_d+timedelta(hours=8, minutes=30))).seconds
if list_end[1]>='13:00:00':
result-=3600
elif '12:00:00'<list_end[1]<'13:00:00':
result-=(end-(end_d+timedelta(hours=12))).seconds
return result if result>0 else 0
"""返回相差的秒数"""
def col(path):
data = []
data1 = []
begin_time = []
end_time = []
hours = []
text = pd.read_excel(path,sheet_name = 0)
data.append(text['审核开始时间'].values)
data.append(text['审核结束时间'].values)
data1.append(str1(data[0]))
data1.append(str1(data[1]))
list_holiday=get_holiday()
list_holiday=list( map(lambda x : datetime.strptime(x,'%Y-%m-%d'), list_holiday) )
for i in range(len(data[0])):
hours.append((datediff_no_holiday(data1[0][i],data1[1][i],list_holiday))/3600)
text.insert(loc=len(text.columns), column='有效审核时长(小时)', value=hours)
path2 = path[0:path.rfind('.')] + '2' + path[path.rfind('.'):]
print(path2)
try:
text.to_excel(path2,engine='openpyxl')
except Exception as e:
print(e)
return(hours)
def str1(data):
data1 = []
for i in range(len(data)):
a = str(data[i])[0:19]
a =a.replace("T"," ")
data1.append(a)
# print(data1[0])
return data1
if __name__ == '__main__':
app = wx.App(False) # print输出是否重定向
frame = MyFrame1(None)
frame.Show()
app.MainLoop()
设计过程中出现的问题
对于假期时间获取
测试程序的时候是有一个当年的假期时间列,考虑到要获取第二个sheet页、减少人力成本、避免不必要的误差,改为采用通过库对当年的每一天是否为假期进行判断。提高了效率,减小误差。
xlrd只能读取65535行数据
测试文件时,文件数据只有2万行,并没有发现此问题,正式使用时,行数超过65536,导致导出的文件新增列为空,而且只有65536行,解决办法就是在写入文件时加上engine='openpyxl',代码如下:
text.to_excel(path2,engine='openpyxl')
细节问题处理
在读写文件时要加上异常处理,如下:
try:
text.to_excel(path2,engine='openpyxl')
except Exception as e:
print(e)
return(hours)
图形化界面
Python的图形化界面库有以下几种:
1、 PyQt5:我日常的主力GUI设计工具,几乎所有项目都会用它。PyQt5功能非常强大,可以用Qt开发出多漂亮的界面,就可以用PyQt5开发出多漂亮的界面;另外,它最赞的一点是支持可视化界面设计,对于Python小白设计GUI界面尤其有好!!!
2、Tkinter:又称“Tk接口”,优点是Python的默认标准GUI库,使用简单,缺点是设计的界面比较简陋,比如Python的默认IDLE!
3、Flexx:用于创建图形用户界面(GUI)的纯Python工具箱,该工具箱使用Web技术进行渲染,因此更适合于应用于Web应用中
4、wxPython:Python语言的一套优秀的GUI图形库,可以帮助开发人员轻松创建功能强悍的图形用户界面程序。同时它具有非常优秀的跨平台能力,可以在不修改程序的情况下在多种平台上运行,与PyQt5相比,唯一的缺点是设计的界面美观程度和灵活性欠缺。
5、Kivy:一款用于跨平台快速应用开发的开源框架,可以生成Android、iOS应用,但官方文档都是英文资料,配置使用会麻烦!
6、PySide:跨平台的应用程序框架Qt的Python绑定版本,可以使用Python语言和Qt进行界面开发。相对于PyQt,它支持的Qt版本比较老,最高支持到Qt 4.8版本,而且官方已经停止维护该库。
7、PyGTK:Python对GTK+GUI库的一系列封装,它最经常用于GNOME平台上,虽然也支持Windows系统,但表现不太好!
由于只做一个简单内部使用的小工具,wxPython是个不错的选择
打包程序
因为知识内部使用,不考虑其他花里胡哨的东西比如程序图标(-i),只使用-F -w
-F, –onefile 打包成一个exe文件。
-w, –windowed, –noconsole 使用窗口,无控制台
控制台使用命令如下:
pyinstaller -F -w 程序名.py
py32 or py64
测试py32位上进行打包的文件在win7、 win10、 win xp上都是可以运行的,py64 上打包的程序只能在win10上运行。
运行图片
点击browse进行文件选择,再点击确定,待程序运行完之后就会在原路径多一个文件名2的文件,里面会多处一列工作时长。