From 77d6bbb41bb0063053c3e8e6ae6c1dd11a1b441f Mon Sep 17 00:00:00 2001 From: Thomas Fussell Date: Tue, 6 Oct 2015 12:31:49 -0400 Subject: [PATCH] fix precision on time to number, fix type guessing, fix long long on osx --- include/xlnt/cell/cell.hpp | 6 +- include/xlnt/cell/cell_value.h | 98 ------------------ include/xlnt/cell/value.hpp | 8 ++ include/xlnt/common/datetime.hpp | 4 +- include/xlnt/styles/number_format.hpp | 5 +- source/cell.cpp | 21 +++- source/datetime.cpp | 18 ++-- source/number_format.cpp | 142 +++++++++++--------------- source/value.cpp | 33 +++++- tests/test_cell.hpp | 108 +++++++++++--------- 10 files changed, 191 insertions(+), 252 deletions(-) delete mode 100644 include/xlnt/cell/cell_value.h diff --git a/include/xlnt/cell/cell.hpp b/include/xlnt/cell/cell.hpp index 90a81785..1d3e4f98 100644 --- a/include/xlnt/cell/cell.hpp +++ b/include/xlnt/cell/cell.hpp @@ -123,11 +123,11 @@ public: void set_value(std::uint32_t i); void set_value(std::uint64_t i); #ifdef _WIN32 - void set_value(unsigned long i); + void set_value(unsigned long i); #endif #ifdef __linux__ - void set_value(long long i); - void set_value(unsigned long long i); + void set_value(long long i); + void set_value(unsigned long long i); #endif void set_value(float f); void set_value(double d); diff --git a/include/xlnt/cell/cell_value.h b/include/xlnt/cell/cell_value.h deleted file mode 100644 index 6bd4a9c9..00000000 --- a/include/xlnt/cell/cell_value.h +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) 2014 Thomas Fussell -// Copyright (c) 2010-2014 openpyxl -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE -// -// @license: http://www.opensource.org/licenses/mit-license.php -// @author: see AUTHORS file -#pragma once - -#include -#include -#include - -#include "../styles/style.hpp" -#include "../common/types.hpp" - -namespace xlnt { - -struct date; -struct datetime; -struct time; - -class cell_value -{ -public: - enum class type - { - null, - numeric, - string, - formula, - boolean, - error - }; - - std::string to_string() const; - - type get_data_type() const; - - void set_null(); - - cell_value &operator=(const cell_value &rhs); - cell_value &operator=(bool value); - cell_value &operator=(int value); - cell_value &operator=(double value); - cell_value &operator=(long int value); - cell_value &operator=(long long value); - cell_value &operator=(long double value); - cell_value &operator=(const std::string &value); - cell_value &operator=(const char *value); - cell_value &operator=(const date &value); - cell_value &operator=(const time &value); - cell_value &operator=(const datetime &value); - - bool operator==(const cell_value &comparand) const; - bool operator==(std::nullptr_t) const; - bool operator==(bool comparand) const; - bool operator==(int comparand) const; - bool operator==(double comparand) const; - bool operator==(const std::string &comparand) const; - bool operator==(const char *comparand) const; - bool operator==(const date &comparand) const; - bool operator==(const time &comparand) const; - bool operator==(const datetime &comparand) const; - - friend bool operator==(std::nullptr_t, const cell_value &cell); - friend bool operator==(bool comparand, const cell_value &cell); - friend bool operator==(int comparand, const cell_value &cell); - friend bool operator==(double comparand, const cell_value &cell); - friend bool operator==(const std::string &comparand, const cell_value &cell); - friend bool operator==(const char *comparand, const cell_value &cell); - friend bool operator==(const date &comparand, const cell_value &cell); - friend bool operator==(const time &comparand, const cell_value &cell); - friend bool operator==(const datetime &comparand, const cell_value &cell); -}; - -inline std::ostream &operator<<(std::ostream &stream, const xlnt::cell_value &value) -{ - return stream << value.to_string(); -} - -} // namespace xlnt diff --git a/include/xlnt/cell/value.hpp b/include/xlnt/cell/value.hpp index 696d4bcd..2e21cdcd 100644 --- a/include/xlnt/cell/value.hpp +++ b/include/xlnt/cell/value.hpp @@ -81,6 +81,10 @@ public: bool operator==(std::uint64_t comparand) const; #ifdef _WIN32 bool operator==(unsigned long comparand) const; +#endif +#ifdef __linux__ + bool operator==(long long comparand) const; + bool operator==(unsigned long long comparand) const; #endif bool operator==(float comparand) const; bool operator==(double comparand) const; @@ -103,6 +107,10 @@ public: friend bool operator==(std::uint64_t comparand, const value &v); #ifdef _WIN32 friend bool operator==(unsigned long comparand, const value &v); +#endif +#ifdef __linux__ + friend bool operator==(long long comparand, const value &v); + friend bool operator==(unsigned long long comparand, const value &v); #endif friend bool operator==(float comparand, const value &v); friend bool operator==(double comparand, const value &v); diff --git a/include/xlnt/common/datetime.hpp b/include/xlnt/common/datetime.hpp index f007d5df..55eda493 100644 --- a/include/xlnt/common/datetime.hpp +++ b/include/xlnt/common/datetime.hpp @@ -61,7 +61,7 @@ struct time } explicit time(const std::string &time_string); - double to_number() const; + long double to_number() const; bool operator==(const time &comparand) const; int hour; @@ -80,7 +80,7 @@ struct datetime { } - double to_number(calendar base_date) const; + long double to_number(calendar base_date) const; bool operator==(const datetime &comparand) const; int year; diff --git a/include/xlnt/styles/number_format.hpp b/include/xlnt/styles/number_format.hpp index ea77556e..f7ce64f4 100644 --- a/include/xlnt/styles/number_format.hpp +++ b/include/xlnt/styles/number_format.hpp @@ -94,8 +94,9 @@ public: format get_format_code() const { return format_code_; } void set_format_code(format format_code) { format_code_ = format_code; } - void set_format_code(const std::string &format_code) { custom_format_code_ = format_code; } - + void set_format_code_string(const std::string &format_code) { custom_format_code_ = format_code; } + std::string get_format_code_string(); + private: std::string custom_format_code_ = ""; format format_code_ = format::general; diff --git a/source/cell.cpp b/source/cell.cpp index 6e8ad090..296bc7f8 100644 --- a/source/cell.cpp +++ b/source/cell.cpp @@ -274,16 +274,26 @@ void cell::set_value(unsigned long long i) } #endif +void cell::set_value(float f) +{ + d_->value_ = value(f); +} + void cell::set_value(double d) { d_->value_ = value(d); } + +void cell::set_value(long double d) +{ + d_->value_ = value(d); +} void cell::set_value(const date &d) { d_->is_date_ = true; - auto date_format_code = xlnt::number_format::lookup_format(14); - auto number_format = xlnt::number_format(date_format_code); + auto code = xlnt::number_format::format::date_yyyymmdd2; + auto number_format = xlnt::number_format(code); get_style().set_number_format(number_format); auto base_date = get_parent().get_parent().get_properties().excel_base_date; set_value(d.to_number(base_date)); @@ -292,8 +302,8 @@ void cell::set_value(const date &d) void cell::set_value(const datetime &d) { d_->is_date_ = true; - auto date_format_code = xlnt::number_format::lookup_format(22); - auto number_format = xlnt::number_format(date_format_code); + auto code = xlnt::number_format::format::date_datetime; + auto number_format = xlnt::number_format(code); get_style().set_number_format(number_format); auto base_date = get_parent().get_parent().get_properties().excel_base_date; set_value(d.to_number(base_date)); @@ -302,6 +312,9 @@ void cell::set_value(const datetime &d) void cell::set_value(const time &t) { d_->is_date_ = true; + auto code = xlnt::number_format::format::date_time6; + auto number_format = xlnt::number_format(code); + get_style().set_number_format(number_format); set_value(t.to_number()); } diff --git a/source/datetime.cpp b/source/datetime.cpp index 7f5f5163..6035d5b1 100644 --- a/source/datetime.cpp +++ b/source/datetime.cpp @@ -121,16 +121,14 @@ time::time(const std::string &time_string) : hour(0), minute(0), second(0), micr } } -double time::to_number() const +long double time::to_number() const { - double number = microsecond; - number /= 1000000; - number += second; - number /= 60; - number += minute; - number /= 60; - number += hour; - number /= 24; + std::size_t microseconds = microsecond; + microseconds += second * 1e6; + microseconds += minute * 1e6 * 60; + microseconds += hour * 1e6 * 60 * 60; + auto number = microseconds / (24.0L * 60 * 60 * 1e6L); + number = std::floor(number * 1e11L + 0.5L) / 1e11L; return number; } @@ -159,7 +157,7 @@ int date::to_number(calendar base_date) const return days_since_1900; } -double datetime::to_number(calendar base_date) const +long double datetime::to_number(calendar base_date) const { return date(year, month, day).to_number(base_date) + time(hour, minute, second, microsecond).to_number(); diff --git a/source/number_format.cpp b/source/number_format.cpp index 5b30a122..db008b15 100644 --- a/source/number_format.cpp +++ b/source/number_format.cpp @@ -3,49 +3,6 @@ #include namespace xlnt { - -const std::unordered_map &number_format::format_strings() -{ - static const std::unordered_map strings = - { - {format::general, "General"}, - {format::text, "@"}, - {format::number, "0"}, - {format::number_00, "0.00"}, - {format::number_comma_separated1, "#,##0.00"}, - {format::number_comma_separated2, "#,##0.00_-"}, - {format::percentage, "0%"}, - {format::percentage_00, "0.00%"}, - {format::date_yyyymmdd2, "yyyy-mm-dd"}, - {format::date_yyyymmdd, "yy-mm-dd"}, - {format::date_ddmmyyyy, "dd/mm/yy"}, - {format::date_dmyslash, "d/m/y"}, - {format::date_dmyminus, "d-m-y"}, - {format::date_dmminus, "d-m"}, - {format::date_myminus, "m-y"}, - {format::date_xlsx14, "mm-dd-yy"}, - {format::date_xlsx15, "d-mmm-yy"}, - {format::date_xlsx16, "d-mmm"}, - {format::date_xlsx17, "mmm-yy"}, - {format::date_xlsx22, "m/d/yy h:mm"}, - {format::date_datetime, "d/m/y h:mm"}, - {format::date_time1, "h:mm AM/PM"}, - {format::date_time2, "h:mm:ss AM/PM"}, - {format::date_time3, "h:mm"}, - {format::date_time4, "h:mm:ss"}, - {format::date_time5, "mm:ss"}, - {format::date_time6, "h:mm:ss"}, - {format::date_time7, "i:s.S"}, - {format::date_time8, "h:mm:ss@"}, - {format::date_timedelta, "[hh]:mm:ss"}, - {format::date_yyyymmddslash, "yy/mm/dd@"}, - {format::currency_usd_simple, "\"$\"#,##0.00_-"}, - {format::currency_usd, "$#,##0_-"}, - {format::currency_eur_simple, "[$EUR ]#,##0.00_-"} - }; - - return strings; -} const std::unordered_map &number_format::builtin_formats() { @@ -105,51 +62,58 @@ const std::unordered_map &number_format::builtin_formats() return formats; } -const std::unordered_map &number_format::reversed_builtin_formats() +const std::unordered_map &number_format::format_strings() { - static const std::unordered_map formats = + static const std::unordered_map strings = { - {"General", 0}, - {"0", 1}, - {"0.00", 2}, - {"#,##0", 3}, - {"#,##0.00", 4}, - {"\"$\"#,##0_);(\"$\"#,##0)", 5}, - {"\"$\"#,##0_);[Red](\"$\"#,##0)", 6}, - {"\"$\"#,##0.00_);(\"$\"#,##0.00)", 7}, - {"\"$\"#,##0.00_);[Red](\"$\"#,##0.00)", 8}, - {"0%", 9}, - {"0.00%", 10}, - {"0.00E+00", 11}, - {"# ?/?", 12}, - {"# \?\?/??", 13}, //escape trigraph - {"mm-dd-yy", 14}, - {"d-mmm-yy", 15}, - {"d-mmm", 16}, - {"mmm-yy", 17}, - {"h:mm AM/PM", 18}, - {"h:mm:ss AM/PM", 19}, - {"h:mm", 20}, - {"h:mm:ss", 21}, - {"m/d/yy h:mm", 22}, - - {"#,##0_);(#,##0)", 37}, - {"#,##0_);[Red](#,##0)", 38}, - {"#,##0.00_);(#,##0.00)", 39}, - {"#,##0.00_);[Red](#,##0.00)", 40}, - - {"_(* #,##0_);_(* \\(#,##0\\);_(* \"-\"_);_(@_)", 41}, - {"_(\"$\"* #,##0_);_(\"$\"* \\(#,##0\\);_(\"$\"* \"-\"_);_(@_)", 42}, - {"_(* #,##0.00_);_(* \\(#,##0.00\\);_(* \"-\"??_);_(@_)", 43}, - - {"_(\"$\"* #,##0.00_)_(\"$\"* \\(#,##0.00\\)_(\"$\"* \"-\"??_)_(@_)", 44}, - {"mm:ss", 45}, - {"[h]:mm:ss", 46}, - {"mmss.0", 47}, - {"##0.0E+0", 48}, - {"@", 49} + {format::general, builtin_formats().at(0)}, + {format::text, builtin_formats().at(49)}, + {format::number, builtin_formats().at(1)}, + {format::number_00, builtin_formats().at(2)}, + {format::number_comma_separated1, builtin_formats().at(4)}, + {format::number_comma_separated2, "#,##0.00_-"}, + {format::percentage, builtin_formats().at(9)}, + {format::percentage_00, builtin_formats().at(10)}, + {format::date_yyyymmdd2, "yyyy-mm-dd"}, + {format::date_yyyymmdd, "yy-mm-dd"}, + {format::date_ddmmyyyy, "dd/mm/yy"}, + {format::date_dmyslash, "d/m/y"}, + {format::date_dmyminus, "d-m-y"}, + {format::date_dmminus, "d-m"}, + {format::date_myminus, "m-y"}, + {format::date_xlsx14, builtin_formats().at(14)}, + {format::date_xlsx15, builtin_formats().at(15)}, + {format::date_xlsx16, builtin_formats().at(16)}, + {format::date_xlsx17, builtin_formats().at(17)}, + {format::date_xlsx22, builtin_formats().at(22)}, + {format::date_datetime, "yyyy-mm-dd h:mm:ss"}, + {format::date_time1, builtin_formats().at(18)}, + {format::date_time2, builtin_formats().at(19)}, + {format::date_time3, builtin_formats().at(20)}, + {format::date_time4, builtin_formats().at(21)}, + {format::date_time5, builtin_formats().at(45)}, + {format::date_time6, builtin_formats().at(21)}, + {format::date_time7, "i:s.S"}, + {format::date_time8, "h:mm:ss@"}, + {format::date_timedelta, "[hh]:mm:ss"}, + {format::date_yyyymmddslash, "yy/mm/dd@"}, + {format::currency_usd_simple, "\"$\"#,##0.00_-"}, + {format::currency_usd, "$#,##0_-"}, + {format::currency_eur_simple, "[$EUR ]#,##0.00_-"} }; + return strings; +} + +const std::unordered_map &number_format::reversed_builtin_formats() +{ + static std::unordered_map formats; + + for(auto format_pair : builtin_formats()) + { + formats[format_pair.second] = format_pair.first; + } + return formats; } @@ -171,4 +135,14 @@ number_format::format number_format::lookup_format(int code) return match->first; } +std::string number_format::get_format_code_string() +{ + if(format_code_ == format::unknown) + { + return custom_format_code_; + } + + return format_strings().at(format_code_); +} + } // namespace xlnt diff --git a/source/value.cpp b/source/value.cpp index 3b7cbf99..d1919bae 100644 --- a/source/value.cpp +++ b/source/value.cpp @@ -100,6 +100,18 @@ value::value(const std::string &s) : type_(type::string), string_value_(s) { } +value::value(const date &d) : type_(type::numeric), numeric_value_(d.to_number(xlnt::calendar::windows_1900)) +{ +} + +value::value(const datetime &d) : type_(type::numeric), numeric_value_(d.to_number(xlnt::calendar::windows_1900)) +{ +} + +value::value(const time &t) : type_(type::numeric), numeric_value_(t.to_number()) +{ +} + value &value::operator=(value other) { swap(*this, other); @@ -129,7 +141,26 @@ double value::as() const { case type::boolean: case type::numeric: - return (double)numeric_value_; + return static_cast(numeric_value_); + case type::string: + return std::stod(string_value_); + case type::error: + throw std::runtime_error("invalid"); + case type::null: + return 0; + } + + return 0; +} + +template<> +long double value::as() const +{ + switch(type_) + { + case type::boolean: + case type::numeric: + return numeric_value_; case type::string: return std::stod(string_value_); case type::error: diff --git a/tests/test_cell.hpp b/tests/test_cell.hpp index 80dd489c..5ab19021 100644 --- a/tests/test_cell.hpp +++ b/tests/test_cell.hpp @@ -11,8 +11,10 @@ class test_cell : public CxxTest::TestSuite public: void test_infer_numeric() { - wb.set_guess_types(true); - xlnt::worksheet ws = wb.create_sheet(); + xlnt::workbook wb_guess_types; + wb_guess_types.set_guess_types(true); + + auto ws = wb_guess_types.create_sheet(); xlnt::cell cell(ws, "A1"); cell.set_value("4.2"); @@ -52,6 +54,21 @@ public: TS_ASSERT(cell.get_value() == xlnt::time(0, 30, 33, 865633)); } + + void test_ctor() + { + auto ws = wb.create_sheet(); + xlnt::cell cell(ws, "A1"); + + TS_ASSERT(cell.get_value().get_type() == xlnt::value::type::null); + TS_ASSERT(cell.get_column() == "A"); + TS_ASSERT(cell.get_row() == 1); + TS_ASSERT(cell.get_reference() == "A1"); + TS_ASSERT(cell.get_value() == xlnt::value::null()); + //TS_ASSERT(cell.get_xf_index() == 0); + TS_ASSERT(!cell.has_comment()); + } + void test_coordinates() { xlnt::cell_reference coord("ZF46"); @@ -214,70 +231,59 @@ public: } } - void test_insert_float() - { - xlnt::worksheet ws = wb.create_sheet(); - xlnt::cell cell(ws, "A1"); - cell.set_value(3.14); - TS_ASSERT(cell.get_value().is(xlnt::value::type::numeric)); - } - - void test_insert_percentage() - { - xlnt::worksheet ws = wb.create_sheet(); - xlnt::cell cell(ws, "A1"); - cell.set_value("3.14%"); - TS_ASSERT_DELTA(0.0314, cell.get_value().as(), 1e-7); - } - void test_insert_datetime() { + xlnt::datetime value(2010, 7, 13, 6, 37, 41); + auto internal = 40372.27616898148L; + auto number_format = "yyyy-mm-dd h:mm:ss"; + xlnt::worksheet ws = wb.create_sheet(); xlnt::cell cell(ws, "A1"); - cell.set_value(xlnt::date::today()); - TS_ASSERT(cell.get_value().is(xlnt::value::type::numeric)); + cell.set_value(value); + + TS_ASSERT(cell.get_value().get_type() == xlnt::value::type::numeric); + TS_ASSERT(cell.get_value().as() == internal); + TS_ASSERT(cell.is_date()); + TS_ASSERT(cell.get_style().get_number_format().get_format_code_string() == number_format); } void test_insert_date() { + xlnt::date value(2010, 7, 13); + auto internal = 40372; + auto number_format = "yyyy-mm-dd"; + xlnt::worksheet ws = wb.create_sheet(); xlnt::cell cell(ws, "A1"); - cell.set_value(xlnt::datetime::now()); - TS_ASSERT(cell.get_value().is(xlnt::value::type::numeric)); + cell.set_value(value); + + TS_ASSERT(cell.get_value().get_type() == xlnt::value::type::numeric); + TS_ASSERT(cell.get_value().as() == internal); + TS_ASSERT(cell.is_date()); + TS_ASSERT(cell.get_style().get_number_format().get_format_code_string() == number_format); } - void test_internal_date() + void test_insert_time() { - xlnt::worksheet ws = wb.create_sheet(); - xlnt::cell cell(ws, "A1"); - xlnt::datetime dt(2010, 7, 13, 6, 37, 41); - cell.set_value(dt); - TS_ASSERT_EQUALS(40372.27616898148, cell.get_value().as()); - } + xlnt::time value(1, 3); + auto internal = 0.04375L; + auto number_format = "h:mm:ss"; - void test_datetime_interpretation() - { xlnt::worksheet ws = wb.create_sheet(); xlnt::cell cell(ws, "A1"); - xlnt::datetime dt(2010, 7, 13, 6, 37, 41); - cell.set_value(dt); - TS_ASSERT_EQUALS(cell.get_value(), dt); - TS_ASSERT_DELTA(cell.get_value().as(), 40372.27616898148, 1e-7); - } + cell.set_value(value); - void test_date_interpretation() - { - xlnt::worksheet ws = wb.create_sheet(); - xlnt::cell cell(ws, "A1"); - xlnt::date dt(2010, 7, 13); - cell.set_value(dt); - TS_ASSERT_EQUALS(cell.get_value(), dt); - TS_ASSERT_EQUALS(cell.get_value().as(), 40372); + TS_ASSERT(cell.get_value().get_type() == xlnt::value::type::numeric); + TS_ASSERT(cell.get_value().as() == internal); + TS_ASSERT(cell.is_date()); + TS_ASSERT(cell.get_style().get_number_format().get_format_code_string() == number_format); } void test_number_format_style() { - xlnt::worksheet ws = wb.create_sheet(); + xlnt::workbook wb_guess_types; + wb_guess_types.set_guess_types(true); + xlnt::worksheet ws = wb_guess_types.create_sheet(); xlnt::cell cell(ws, "A1"); cell.set_value("12.6%"); TS_ASSERT_EQUALS(xlnt::number_format::format::percentage, cell.get_style().get_number_format().get_format_code()); @@ -285,7 +291,10 @@ public: void test_data_type_check() { - xlnt::worksheet ws = wb.create_sheet(); + xlnt::workbook wb_guess_types; + wb_guess_types.set_guess_types(true); + + xlnt::worksheet ws = wb_guess_types.create_sheet(); xlnt::cell cell(ws, "A1"); TS_ASSERT(cell.get_value().is(xlnt::value::type::null)); @@ -311,7 +320,10 @@ public: void test_time() { - xlnt::worksheet ws = wb.create_sheet(); + xlnt::workbook wb_guess_types; + wb_guess_types.set_guess_types(true); + + xlnt::worksheet ws = wb_guess_types.create_sheet(); xlnt::cell cell(ws, "A1"); cell.set_value("03:40:16"); @@ -382,7 +394,7 @@ public: xlnt::cell cell(ws, "A1"); cell.set_value(-13.5); - cell.get_style().get_number_format().set_format_code("0.00_);[Red]\\(0.00\\)"); + cell.get_style().get_number_format().set_format_code_string("0.00_);[Red]\\(0.00\\)"); TS_ASSERT(!cell.is_date()); }