Current File : //usr/local/lib/python3.6/site-packages/xlsxwriter/utility.py |
###############################################################################
#
# Worksheet - A class for writing Excel Worksheets.
#
# SPDX-License-Identifier: BSD-2-Clause
# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org
#
import re
import datetime
from warnings import warn
COL_NAMES = {}
CHAR_WIDTHS = {
" ": 3,
"!": 5,
'"': 6,
"#": 7,
"$": 7,
"%": 11,
"&": 10,
"'": 3,
"(": 5,
")": 5,
"*": 7,
"+": 7,
",": 4,
"-": 5,
".": 4,
"/": 6,
"0": 7,
"1": 7,
"2": 7,
"3": 7,
"4": 7,
"5": 7,
"6": 7,
"7": 7,
"8": 7,
"9": 7,
":": 4,
";": 4,
"<": 7,
"=": 7,
">": 7,
"?": 7,
"@": 13,
"A": 9,
"B": 8,
"C": 8,
"D": 9,
"E": 7,
"F": 7,
"G": 9,
"H": 9,
"I": 4,
"J": 5,
"K": 8,
"L": 6,
"M": 12,
"N": 10,
"O": 10,
"P": 8,
"Q": 10,
"R": 8,
"S": 7,
"T": 7,
"U": 9,
"V": 9,
"W": 13,
"X": 8,
"Y": 7,
"Z": 7,
"[": 5,
"\\": 6,
"]": 5,
"^": 7,
"_": 7,
"`": 4,
"a": 7,
"b": 8,
"c": 6,
"d": 8,
"e": 8,
"f": 5,
"g": 7,
"h": 8,
"i": 4,
"j": 4,
"k": 7,
"l": 4,
"m": 12,
"n": 8,
"o": 8,
"p": 8,
"q": 8,
"r": 5,
"s": 6,
"t": 5,
"u": 8,
"v": 7,
"w": 11,
"x": 7,
"y": 7,
"z": 6,
"{": 5,
"|": 7,
"}": 5,
"~": 7,
}
# Compile performance critical regular expressions.
re_leading = re.compile(r"^\s")
re_trailing = re.compile(r"\s$")
re_range_parts = re.compile(r"(\$?)([A-Z]{1,3})(\$?)(\d+)")
def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
"""
Convert a zero indexed row and column cell reference to a A1 style string.
Args:
row: The cell row. Int.
col: The cell column. Int.
row_abs: Optional flag to make the row absolute. Bool.
col_abs: Optional flag to make the column absolute. Bool.
Returns:
A1 style string.
"""
if row < 0:
warn("Row number %d must be >= 0" % row)
return None
if col < 0:
warn("Col number %d must be >= 0" % col)
return None
row += 1 # Change to 1-index.
row_abs = "$" if row_abs else ""
col_str = xl_col_to_name(col, col_abs)
return col_str + row_abs + str(row)
def xl_rowcol_to_cell_fast(row, col):
"""
Optimized version of the xl_rowcol_to_cell function. Only used internally.
Args:
row: The cell row. Int.
col: The cell column. Int.
Returns:
A1 style string.
"""
if col in COL_NAMES:
col_str = COL_NAMES[col]
else:
col_str = xl_col_to_name(col)
COL_NAMES[col] = col_str
return col_str + str(row + 1)
def xl_col_to_name(col, col_abs=False):
"""
Convert a zero indexed column cell reference to a string.
Args:
col: The cell column. Int.
col_abs: Optional flag to make the column absolute. Bool.
Returns:
Column style string.
"""
col_num = col
if col_num < 0:
warn("Col number %d must be >= 0" % col_num)
return None
col_num += 1 # Change to 1-index.
col_str = ""
col_abs = "$" if col_abs else ""
while col_num:
# Set remainder from 1 .. 26
remainder = col_num % 26
if remainder == 0:
remainder = 26
# Convert the remainder to a character.
col_letter = chr(ord("A") + remainder - 1)
# Accumulate the column letters, right to left.
col_str = col_letter + col_str
# Get the next order of magnitude.
col_num = int((col_num - 1) / 26)
return col_abs + col_str
def xl_cell_to_rowcol(cell_str):
"""
Convert a cell reference in A1 notation to a zero indexed row and column.
Args:
cell_str: A1 style string.
Returns:
row, col: Zero indexed cell row and column indices.
"""
if not cell_str:
return 0, 0
match = re_range_parts.match(cell_str)
col_str = match.group(2)
row_str = match.group(4)
# Convert base26 column string to number.
expn = 0
col = 0
for char in reversed(col_str):
col += (ord(char) - ord("A") + 1) * (26**expn)
expn += 1
# Convert 1-index to zero-index
row = int(row_str) - 1
col -= 1
return row, col
def xl_cell_to_rowcol_abs(cell_str):
"""
Convert an absolute cell reference in A1 notation to a zero indexed
row and column, with True/False values for absolute rows or columns.
Args:
cell_str: A1 style string.
Returns:
row, col, row_abs, col_abs: Zero indexed cell row and column indices.
"""
if not cell_str:
return 0, 0, False, False
match = re_range_parts.match(cell_str)
col_abs = match.group(1)
col_str = match.group(2)
row_abs = match.group(3)
row_str = match.group(4)
if col_abs:
col_abs = True
else:
col_abs = False
if row_abs:
row_abs = True
else:
row_abs = False
# Convert base26 column string to number.
expn = 0
col = 0
for char in reversed(col_str):
col += (ord(char) - ord("A") + 1) * (26**expn)
expn += 1
# Convert 1-index to zero-index
row = int(row_str) - 1
col -= 1
return row, col, row_abs, col_abs
def xl_range(first_row, first_col, last_row, last_col):
"""
Convert zero indexed row and col cell references to a A1:B1 range string.
Args:
first_row: The first cell row. Int.
first_col: The first cell column. Int.
last_row: The last cell row. Int.
last_col: The last cell column. Int.
Returns:
A1:B1 style range string.
"""
range1 = xl_rowcol_to_cell(first_row, first_col)
range2 = xl_rowcol_to_cell(last_row, last_col)
if range1 is None or range2 is None:
warn("Row and column numbers must be >= 0")
return None
if range1 == range2:
return range1
else:
return range1 + ":" + range2
def xl_range_abs(first_row, first_col, last_row, last_col):
"""
Convert zero indexed row and col cell references to a $A$1:$B$1 absolute
range string.
Args:
first_row: The first cell row. Int.
first_col: The first cell column. Int.
last_row: The last cell row. Int.
last_col: The last cell column. Int.
Returns:
$A$1:$B$1 style range string.
"""
range1 = xl_rowcol_to_cell(first_row, first_col, True, True)
range2 = xl_rowcol_to_cell(last_row, last_col, True, True)
if range1 is None or range2 is None:
warn("Row and column numbers must be >= 0")
return None
if range1 == range2:
return range1
else:
return range1 + ":" + range2
def xl_range_formula(sheetname, first_row, first_col, last_row, last_col):
"""
Convert worksheet name and zero indexed row and col cell references to
a Sheet1!A1:B1 range formula string.
Args:
sheetname: The worksheet name. String.
first_row: The first cell row. Int.
first_col: The first cell column. Int.
last_row: The last cell row. Int.
last_col: The last cell column. Int.
Returns:
A1:B1 style range string.
"""
cell_range = xl_range_abs(first_row, first_col, last_row, last_col)
sheetname = quote_sheetname(sheetname)
return sheetname + "!" + cell_range
def quote_sheetname(sheetname):
"""
Convert a worksheet name to a quoted name if it contains spaces or
special characters.
Args:
sheetname: The worksheet name. String.
Returns:
A quoted worksheet string.
"""
if not sheetname.isalnum() and not sheetname.startswith("'"):
# Double quote any single quotes.
sheetname = sheetname.replace("'", "''")
# Single quote the sheet name.
sheetname = "'%s'" % sheetname
return sheetname
def xl_pixel_width(string):
"""
Get the pixel width of a string based on individual character widths taken
from Excel. UTF8 characters, and other unhandled characters, are given a
default width of 8.
Args:
string: The string to calculate the width for. String.
Returns:
The string width in pixels. Note, Excel adds an additional 7 pixels of
padding in the cell.
"""
length = 0
for char in string:
length += CHAR_WIDTHS.get(char, 8)
return length
def xl_color(color):
# Used in conjunction with the XlsxWriter *color() methods to convert
# a color name into an RGB formatted string. These colors are for
# backward compatibility with older versions of Excel.
named_colors = {
"black": "#000000",
"blue": "#0000FF",
"brown": "#800000",
"cyan": "#00FFFF",
"gray": "#808080",
"green": "#008000",
"lime": "#00FF00",
"magenta": "#FF00FF",
"navy": "#000080",
"orange": "#FF6600",
"pink": "#FF00FF",
"purple": "#800080",
"red": "#FF0000",
"silver": "#C0C0C0",
"white": "#FFFFFF",
"yellow": "#FFFF00",
}
if color in named_colors:
color = named_colors[color]
if not re.match("#[0-9a-fA-F]{6}", color):
warn("Color '%s' isn't a valid Excel color" % color)
# Convert the RGB color to the Excel ARGB format.
return "FF" + color.lstrip("#").upper()
def get_rgb_color(color):
# Convert the user specified color to an RGB color.
rgb_color = xl_color(color)
# Remove leading FF from RGB color for charts.
rgb_color = re.sub(r"^FF", "", rgb_color)
return rgb_color
def get_sparkline_style(style_id):
styles = [
{
"series": {"theme": "4", "tint": "-0.499984740745262"},
"negative": {"theme": "5"},
"markers": {"theme": "4", "tint": "-0.499984740745262"},
"first": {"theme": "4", "tint": "0.39997558519241921"},
"last": {"theme": "4", "tint": "0.39997558519241921"},
"high": {"theme": "4"},
"low": {"theme": "4"},
}, # 0
{
"series": {"theme": "4", "tint": "-0.499984740745262"},
"negative": {"theme": "5"},
"markers": {"theme": "4", "tint": "-0.499984740745262"},
"first": {"theme": "4", "tint": "0.39997558519241921"},
"last": {"theme": "4", "tint": "0.39997558519241921"},
"high": {"theme": "4"},
"low": {"theme": "4"},
}, # 1
{
"series": {"theme": "5", "tint": "-0.499984740745262"},
"negative": {"theme": "6"},
"markers": {"theme": "5", "tint": "-0.499984740745262"},
"first": {"theme": "5", "tint": "0.39997558519241921"},
"last": {"theme": "5", "tint": "0.39997558519241921"},
"high": {"theme": "5"},
"low": {"theme": "5"},
}, # 2
{
"series": {"theme": "6", "tint": "-0.499984740745262"},
"negative": {"theme": "7"},
"markers": {"theme": "6", "tint": "-0.499984740745262"},
"first": {"theme": "6", "tint": "0.39997558519241921"},
"last": {"theme": "6", "tint": "0.39997558519241921"},
"high": {"theme": "6"},
"low": {"theme": "6"},
}, # 3
{
"series": {"theme": "7", "tint": "-0.499984740745262"},
"negative": {"theme": "8"},
"markers": {"theme": "7", "tint": "-0.499984740745262"},
"first": {"theme": "7", "tint": "0.39997558519241921"},
"last": {"theme": "7", "tint": "0.39997558519241921"},
"high": {"theme": "7"},
"low": {"theme": "7"},
}, # 4
{
"series": {"theme": "8", "tint": "-0.499984740745262"},
"negative": {"theme": "9"},
"markers": {"theme": "8", "tint": "-0.499984740745262"},
"first": {"theme": "8", "tint": "0.39997558519241921"},
"last": {"theme": "8", "tint": "0.39997558519241921"},
"high": {"theme": "8"},
"low": {"theme": "8"},
}, # 5
{
"series": {"theme": "9", "tint": "-0.499984740745262"},
"negative": {"theme": "4"},
"markers": {"theme": "9", "tint": "-0.499984740745262"},
"first": {"theme": "9", "tint": "0.39997558519241921"},
"last": {"theme": "9", "tint": "0.39997558519241921"},
"high": {"theme": "9"},
"low": {"theme": "9"},
}, # 6
{
"series": {"theme": "4", "tint": "-0.249977111117893"},
"negative": {"theme": "5"},
"markers": {"theme": "5", "tint": "-0.249977111117893"},
"first": {"theme": "5", "tint": "-0.249977111117893"},
"last": {"theme": "5", "tint": "-0.249977111117893"},
"high": {"theme": "5", "tint": "-0.249977111117893"},
"low": {"theme": "5", "tint": "-0.249977111117893"},
}, # 7
{
"series": {"theme": "5", "tint": "-0.249977111117893"},
"negative": {"theme": "6"},
"markers": {"theme": "6", "tint": "-0.249977111117893"},
"first": {"theme": "6", "tint": "-0.249977111117893"},
"last": {"theme": "6", "tint": "-0.249977111117893"},
"high": {"theme": "6", "tint": "-0.249977111117893"},
"low": {"theme": "6", "tint": "-0.249977111117893"},
}, # 8
{
"series": {"theme": "6", "tint": "-0.249977111117893"},
"negative": {"theme": "7"},
"markers": {"theme": "7", "tint": "-0.249977111117893"},
"first": {"theme": "7", "tint": "-0.249977111117893"},
"last": {"theme": "7", "tint": "-0.249977111117893"},
"high": {"theme": "7", "tint": "-0.249977111117893"},
"low": {"theme": "7", "tint": "-0.249977111117893"},
}, # 9
{
"series": {"theme": "7", "tint": "-0.249977111117893"},
"negative": {"theme": "8"},
"markers": {"theme": "8", "tint": "-0.249977111117893"},
"first": {"theme": "8", "tint": "-0.249977111117893"},
"last": {"theme": "8", "tint": "-0.249977111117893"},
"high": {"theme": "8", "tint": "-0.249977111117893"},
"low": {"theme": "8", "tint": "-0.249977111117893"},
}, # 10
{
"series": {"theme": "8", "tint": "-0.249977111117893"},
"negative": {"theme": "9"},
"markers": {"theme": "9", "tint": "-0.249977111117893"},
"first": {"theme": "9", "tint": "-0.249977111117893"},
"last": {"theme": "9", "tint": "-0.249977111117893"},
"high": {"theme": "9", "tint": "-0.249977111117893"},
"low": {"theme": "9", "tint": "-0.249977111117893"},
}, # 11
{
"series": {"theme": "9", "tint": "-0.249977111117893"},
"negative": {"theme": "4"},
"markers": {"theme": "4", "tint": "-0.249977111117893"},
"first": {"theme": "4", "tint": "-0.249977111117893"},
"last": {"theme": "4", "tint": "-0.249977111117893"},
"high": {"theme": "4", "tint": "-0.249977111117893"},
"low": {"theme": "4", "tint": "-0.249977111117893"},
}, # 12
{
"series": {"theme": "4"},
"negative": {"theme": "5"},
"markers": {"theme": "4", "tint": "-0.249977111117893"},
"first": {"theme": "4", "tint": "-0.249977111117893"},
"last": {"theme": "4", "tint": "-0.249977111117893"},
"high": {"theme": "4", "tint": "-0.249977111117893"},
"low": {"theme": "4", "tint": "-0.249977111117893"},
}, # 13
{
"series": {"theme": "5"},
"negative": {"theme": "6"},
"markers": {"theme": "5", "tint": "-0.249977111117893"},
"first": {"theme": "5", "tint": "-0.249977111117893"},
"last": {"theme": "5", "tint": "-0.249977111117893"},
"high": {"theme": "5", "tint": "-0.249977111117893"},
"low": {"theme": "5", "tint": "-0.249977111117893"},
}, # 14
{
"series": {"theme": "6"},
"negative": {"theme": "7"},
"markers": {"theme": "6", "tint": "-0.249977111117893"},
"first": {"theme": "6", "tint": "-0.249977111117893"},
"last": {"theme": "6", "tint": "-0.249977111117893"},
"high": {"theme": "6", "tint": "-0.249977111117893"},
"low": {"theme": "6", "tint": "-0.249977111117893"},
}, # 15
{
"series": {"theme": "7"},
"negative": {"theme": "8"},
"markers": {"theme": "7", "tint": "-0.249977111117893"},
"first": {"theme": "7", "tint": "-0.249977111117893"},
"last": {"theme": "7", "tint": "-0.249977111117893"},
"high": {"theme": "7", "tint": "-0.249977111117893"},
"low": {"theme": "7", "tint": "-0.249977111117893"},
}, # 16
{
"series": {"theme": "8"},
"negative": {"theme": "9"},
"markers": {"theme": "8", "tint": "-0.249977111117893"},
"first": {"theme": "8", "tint": "-0.249977111117893"},
"last": {"theme": "8", "tint": "-0.249977111117893"},
"high": {"theme": "8", "tint": "-0.249977111117893"},
"low": {"theme": "8", "tint": "-0.249977111117893"},
}, # 17
{
"series": {"theme": "9"},
"negative": {"theme": "4"},
"markers": {"theme": "9", "tint": "-0.249977111117893"},
"first": {"theme": "9", "tint": "-0.249977111117893"},
"last": {"theme": "9", "tint": "-0.249977111117893"},
"high": {"theme": "9", "tint": "-0.249977111117893"},
"low": {"theme": "9", "tint": "-0.249977111117893"},
}, # 18
{
"series": {"theme": "4", "tint": "0.39997558519241921"},
"negative": {"theme": "0", "tint": "-0.499984740745262"},
"markers": {"theme": "4", "tint": "0.79998168889431442"},
"first": {"theme": "4", "tint": "-0.249977111117893"},
"last": {"theme": "4", "tint": "-0.249977111117893"},
"high": {"theme": "4", "tint": "-0.499984740745262"},
"low": {"theme": "4", "tint": "-0.499984740745262"},
}, # 19
{
"series": {"theme": "5", "tint": "0.39997558519241921"},
"negative": {"theme": "0", "tint": "-0.499984740745262"},
"markers": {"theme": "5", "tint": "0.79998168889431442"},
"first": {"theme": "5", "tint": "-0.249977111117893"},
"last": {"theme": "5", "tint": "-0.249977111117893"},
"high": {"theme": "5", "tint": "-0.499984740745262"},
"low": {"theme": "5", "tint": "-0.499984740745262"},
}, # 20
{
"series": {"theme": "6", "tint": "0.39997558519241921"},
"negative": {"theme": "0", "tint": "-0.499984740745262"},
"markers": {"theme": "6", "tint": "0.79998168889431442"},
"first": {"theme": "6", "tint": "-0.249977111117893"},
"last": {"theme": "6", "tint": "-0.249977111117893"},
"high": {"theme": "6", "tint": "-0.499984740745262"},
"low": {"theme": "6", "tint": "-0.499984740745262"},
}, # 21
{
"series": {"theme": "7", "tint": "0.39997558519241921"},
"negative": {"theme": "0", "tint": "-0.499984740745262"},
"markers": {"theme": "7", "tint": "0.79998168889431442"},
"first": {"theme": "7", "tint": "-0.249977111117893"},
"last": {"theme": "7", "tint": "-0.249977111117893"},
"high": {"theme": "7", "tint": "-0.499984740745262"},
"low": {"theme": "7", "tint": "-0.499984740745262"},
}, # 22
{
"series": {"theme": "8", "tint": "0.39997558519241921"},
"negative": {"theme": "0", "tint": "-0.499984740745262"},
"markers": {"theme": "8", "tint": "0.79998168889431442"},
"first": {"theme": "8", "tint": "-0.249977111117893"},
"last": {"theme": "8", "tint": "-0.249977111117893"},
"high": {"theme": "8", "tint": "-0.499984740745262"},
"low": {"theme": "8", "tint": "-0.499984740745262"},
}, # 23
{
"series": {"theme": "9", "tint": "0.39997558519241921"},
"negative": {"theme": "0", "tint": "-0.499984740745262"},
"markers": {"theme": "9", "tint": "0.79998168889431442"},
"first": {"theme": "9", "tint": "-0.249977111117893"},
"last": {"theme": "9", "tint": "-0.249977111117893"},
"high": {"theme": "9", "tint": "-0.499984740745262"},
"low": {"theme": "9", "tint": "-0.499984740745262"},
}, # 24
{
"series": {"theme": "1", "tint": "0.499984740745262"},
"negative": {"theme": "1", "tint": "0.249977111117893"},
"markers": {"theme": "1", "tint": "0.249977111117893"},
"first": {"theme": "1", "tint": "0.249977111117893"},
"last": {"theme": "1", "tint": "0.249977111117893"},
"high": {"theme": "1", "tint": "0.249977111117893"},
"low": {"theme": "1", "tint": "0.249977111117893"},
}, # 25
{
"series": {"theme": "1", "tint": "0.34998626667073579"},
"negative": {"theme": "0", "tint": "-0.249977111117893"},
"markers": {"theme": "0", "tint": "-0.249977111117893"},
"first": {"theme": "0", "tint": "-0.249977111117893"},
"last": {"theme": "0", "tint": "-0.249977111117893"},
"high": {"theme": "0", "tint": "-0.249977111117893"},
"low": {"theme": "0", "tint": "-0.249977111117893"},
}, # 26
{
"series": {"rgb": "FF323232"},
"negative": {"rgb": "FFD00000"},
"markers": {"rgb": "FFD00000"},
"first": {"rgb": "FFD00000"},
"last": {"rgb": "FFD00000"},
"high": {"rgb": "FFD00000"},
"low": {"rgb": "FFD00000"},
}, # 27
{
"series": {"rgb": "FF000000"},
"negative": {"rgb": "FF0070C0"},
"markers": {"rgb": "FF0070C0"},
"first": {"rgb": "FF0070C0"},
"last": {"rgb": "FF0070C0"},
"high": {"rgb": "FF0070C0"},
"low": {"rgb": "FF0070C0"},
}, # 28
{
"series": {"rgb": "FF376092"},
"negative": {"rgb": "FFD00000"},
"markers": {"rgb": "FFD00000"},
"first": {"rgb": "FFD00000"},
"last": {"rgb": "FFD00000"},
"high": {"rgb": "FFD00000"},
"low": {"rgb": "FFD00000"},
}, # 29
{
"series": {"rgb": "FF0070C0"},
"negative": {"rgb": "FF000000"},
"markers": {"rgb": "FF000000"},
"first": {"rgb": "FF000000"},
"last": {"rgb": "FF000000"},
"high": {"rgb": "FF000000"},
"low": {"rgb": "FF000000"},
}, # 30
{
"series": {"rgb": "FF5F5F5F"},
"negative": {"rgb": "FFFFB620"},
"markers": {"rgb": "FFD70077"},
"first": {"rgb": "FF5687C2"},
"last": {"rgb": "FF359CEB"},
"high": {"rgb": "FF56BE79"},
"low": {"rgb": "FFFF5055"},
}, # 31
{
"series": {"rgb": "FF5687C2"},
"negative": {"rgb": "FFFFB620"},
"markers": {"rgb": "FFD70077"},
"first": {"rgb": "FF777777"},
"last": {"rgb": "FF359CEB"},
"high": {"rgb": "FF56BE79"},
"low": {"rgb": "FFFF5055"},
}, # 32
{
"series": {"rgb": "FFC6EFCE"},
"negative": {"rgb": "FFFFC7CE"},
"markers": {"rgb": "FF8CADD6"},
"first": {"rgb": "FFFFDC47"},
"last": {"rgb": "FFFFEB9C"},
"high": {"rgb": "FF60D276"},
"low": {"rgb": "FFFF5367"},
}, # 33
{
"series": {"rgb": "FF00B050"},
"negative": {"rgb": "FFFF0000"},
"markers": {"rgb": "FF0070C0"},
"first": {"rgb": "FFFFC000"},
"last": {"rgb": "FFFFC000"},
"high": {"rgb": "FF00B050"},
"low": {"rgb": "FFFF0000"},
}, # 34
{
"series": {"theme": "3"},
"negative": {"theme": "9"},
"markers": {"theme": "8"},
"first": {"theme": "4"},
"last": {"theme": "5"},
"high": {"theme": "6"},
"low": {"theme": "7"},
}, # 35
{
"series": {"theme": "1"},
"negative": {"theme": "9"},
"markers": {"theme": "8"},
"first": {"theme": "4"},
"last": {"theme": "5"},
"high": {"theme": "6"},
"low": {"theme": "7"},
}, # 36
]
return styles[style_id]
def supported_datetime(dt):
# Determine is an argument is a supported datetime object.
return isinstance(
dt, (datetime.datetime, datetime.date, datetime.time, datetime.timedelta)
)
def remove_datetime_timezone(dt_obj, remove_timezone):
# Excel doesn't support timezones in datetimes/times so we remove the
# tzinfo from the object if the user has specified that option in the
# constructor.
if remove_timezone:
dt_obj = dt_obj.replace(tzinfo=None)
else:
if dt_obj.tzinfo:
raise TypeError(
"Excel doesn't support timezones in datetimes. "
"Set the tzinfo in the datetime/time object to None or "
"use the 'remove_timezone' Workbook() option"
)
return dt_obj
def datetime_to_excel_datetime(dt_obj, date_1904, remove_timezone):
# Convert a datetime object to an Excel serial date and time. The integer
# part of the number stores the number of days since the epoch and the
# fractional part stores the percentage of the day.
date_type = dt_obj
is_timedelta = False
if date_1904:
# Excel for Mac date epoch.
epoch = datetime.datetime(1904, 1, 1)
else:
# Default Excel epoch.
epoch = datetime.datetime(1899, 12, 31)
# We handle datetime .datetime, .date and .time objects but convert
# them to datetime.datetime objects and process them in the same way.
if isinstance(dt_obj, datetime.datetime):
dt_obj = remove_datetime_timezone(dt_obj, remove_timezone)
delta = dt_obj - epoch
elif isinstance(dt_obj, datetime.date):
dt_obj = datetime.datetime.fromordinal(dt_obj.toordinal())
delta = dt_obj - epoch
elif isinstance(dt_obj, datetime.time):
dt_obj = datetime.datetime.combine(epoch, dt_obj)
dt_obj = remove_datetime_timezone(dt_obj, remove_timezone)
delta = dt_obj - epoch
elif isinstance(dt_obj, datetime.timedelta):
is_timedelta = True
delta = dt_obj
else:
raise TypeError("Unknown or unsupported datetime type")
# Convert a Python datetime.datetime value to an Excel date number.
excel_time = delta.days + (
float(delta.seconds) + float(delta.microseconds) / 1e6
) / (60 * 60 * 24)
# The following is a workaround for the fact that in Excel a time only
# value is represented as 1899-12-31+time whereas in datetime.datetime()
# it is 1900-1-1+time so we need to subtract the 1 day difference.
if isinstance(date_type, datetime.datetime) and dt_obj.isocalendar() == (
1900,
1,
1,
):
excel_time -= 1
# Account for Excel erroneously treating 1900 as a leap year.
if not date_1904 and not is_timedelta and excel_time > 59:
excel_time += 1
return excel_time
def preserve_whitespace(string):
# Check if a string has leading or trailing whitespace that requires a
# "preserve" attribute.
if re_leading.search(string) or re_trailing.search(string):
return True
else:
return False