From ea91b7fca5410e177ab5443b23dc8232124f3ae3 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Tue, 28 Jul 2020 17:57:32 +0000 Subject: [PATCH] fpspreadsheet: Add metadata writer for Excel XML. Fix usage of UTC in the Excel metadata. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7580 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../examples/other/metadata/demo_metadata.lpr | 5 +- .../fpspreadsheet/source/common/fpsutils.pas | 67 +++++++++-------- .../fpspreadsheet/source/common/xlsxml.pas | 72 +++++++++++++++---- .../fpspreadsheet/source/common/xlsxooxml.pas | 9 ++- 4 files changed, 106 insertions(+), 47 deletions(-) diff --git a/components/fpspreadsheet/examples/other/metadata/demo_metadata.lpr b/components/fpspreadsheet/examples/other/metadata/demo_metadata.lpr index f2af53a8b..61148cf8b 100644 --- a/components/fpspreadsheet/examples/other/metadata/demo_metadata.lpr +++ b/components/fpspreadsheet/examples/other/metadata/demo_metadata.lpr @@ -5,7 +5,7 @@ uses windows, {$ENDIF} SysUtils, - fpspreadsheet, fpstypes, xlsxooxml, fpsopendocument; + fpspreadsheet, fpstypes, xlsxooxml, fpsopendocument, xlsxml; function GetUserName: String; // http://forum.lazarus.freepascal.org/index.php/topic,23171.msg138057.html#msg138057 @@ -76,7 +76,8 @@ begin sheet.WriteText(2, 3, 'abc'); sheet.WriteBackgroundColor(2, 3, scYellow); book.WriteToFile('test.xlsx', true); - book.WritetoFile('test.ods', true); + book.WriteToFile('test.ods', true); + book.WriteToFile('test.xml', true) finally book.Free; end; diff --git a/components/fpspreadsheet/source/common/fpsutils.pas b/components/fpspreadsheet/source/common/fpsutils.pas index 5ea646cdf..bdaaf5fbb 100644 --- a/components/fpspreadsheet/source/common/fpsutils.pas +++ b/components/fpspreadsheet/source/common/fpsutils.pas @@ -41,6 +41,8 @@ const ISO8601Format='yyyymmdd"T"hhmmss'; {@@ Extended ISO 8601 date/time format, used in e.g. ODF/opendocument } ISO8601FormatExtended='yyyy"-"mm"-"dd"T"hh":"mm":"ss'; + {@@ Extended ISO 8601 date/time format, as UTC } + ISO8601FormatExtendedUTC='yyyy"-"mm"-"dd"T"hh":"mm":"ss"Z"'; {@@ ISO 8601 date-only format, used in ODF/opendocument } ISO8601FormatDateOnly='yyyy"-"mm"-"dd'; {@@ ISO 8601 time-only format, used in ODF/opendocument } @@ -3147,6 +3149,7 @@ end; {@@ ---------------------------------------------------------------------------- Converts an ISO8601-formatted date/time to TDateTime + Assumes UTC when the input string ends with 'Z' and converts to local time. -------------------------------------------------------------------------------} function ISO8601StrToDateTime(s: String): TDateTime; // example: 2020-07-28T11:07:36Z @@ -3157,6 +3160,7 @@ var hours, mins, days: integer; secs: Double; hrPos, minPos, secPos: integer; + ms: Double; begin Result := 0; @@ -3180,43 +3184,46 @@ begin s[p] := ' '; // Strip milliseconds? p := Pos('.', s); - if (p > 1) then - s := Copy(s, 1, p-1); - Result := StrToDateTime(s, fs); - exit; - end; - - p := pos('PT', s); - if p = 1 then + if (p > 1) then begin + ms := StrToFloat('0' + Copy(s, p, MaxInt), fs); + end else + ms := 0; + Result := StrToDateTime(s, fs) + ms; + end else begin - // Get hours - hrPos := pos('H', s); - if (hrPos > 0) then - hours := StrToInt(Copy(s, 3, hrPos-3)) - else - hours := 0; + p := pos('PT', s); + if p = 1 then + begin + // Get hours + hrPos := pos('H', s); + if (hrPos > 0) then + hours := StrToInt(Copy(s, 3, hrPos-3)) + else + hours := 0; - // Get minutes - minPos := pos('M', s); - if (p > 0) and (minPos > hrPos) then - mins := StrToInt(Copy(s, hrPos+1, minPos-hrPos-1)) - else - mins := 0; + // Get minutes + minPos := pos('M', s); + if (p > 0) and (minPos > hrPos) then + mins := StrToInt(Copy(s, hrPos+1, minPos-hrPos-1)) + else + mins := 0; - // Get seconds - secPos := pos('S', s); - if (secPos > 0) and (secPos > minPos) then - secs := StrToFloat(Copy(s, minPos+1, secPos-minPos-1), fs) - else - secs := 0; + // Get seconds + secPos := pos('S', s); + if (secPos > 0) and (secPos > minPos) then + secs := StrToFloat(Copy(s, minPos+1, secPos-minPos-1), fs) + else + secs := 0; - days := hours div 24; - hours := hours mod 24; - Result := days + (hours + (mins + secs/60) / 60) / 24; + days := hours div 24; + hours := hours mod 24; + Result := days + (hours + (mins + secs/60) / 60) / 24; + end else + exit; end; if isUTC then - Result := Result + GetLocalTimeOffset / (60*24); + Result := Result - GetLocalTimeOffset / MinsPerDay; end; diff --git a/components/fpspreadsheet/source/common/xlsxml.pas b/components/fpspreadsheet/source/common/xlsxml.pas index 73c1de8a1..966bf3b1f 100644 --- a/components/fpspreadsheet/source/common/xlsxml.pas +++ b/components/fpspreadsheet/source/common/xlsxml.pas @@ -2660,21 +2660,69 @@ begin end; procedure TsSpreadExcelXMLWriter.WriteDocumentProperties(AStream: TStream); +const + LE = LineEnding; +var + sTitle: String; + sAuthor: String; + sLastAuthor: String; + sDateCreated: String; + sDateLastSaved: String; + book: TsWorkbook; + dt: TDateTime; begin - AppendToStream(AStream, INDENT1 + - '' + LineEnding); + book := TsWorkbook(FWorkbook); - // replace by these when fpspreadsheet supports these meta data. - { - AppendToSstream(AStream, INDENT1 + - '' + LineEnding + INDENT2 + - '' + LineEnding + INDENT2 + - '' + LineEnding + INDENT2 + - '' + LineEnding + Indent2 + // Date in format YYYY-mm-ddThh:nn:ssZ - '16.00' + LineEnding + Indent1 + - '' + LineEnding + if (book.MetaData.Title = '') and + (book.MetaData.CreatedBy = '') and (book.MetaData.LastModifiedBy = '') and + (book.MetaData.DateCreated <= 0) and (book.MetaData.DateLastModified <= 0) then + begin + AppendToStream(AStream, INDENT1 + + '' + LE); + exit; + end; + + if book.MetaData.Title <> '' then + sTitle := '' + book.MetaData.Title + '' + LE + INDENT2 + else + sTitle := ''; + + if book.MetaData.CreatedBy <> '' then + sAuthor := '' + book.MetaData.CreatedBy + '' + LE + INDENT2 + else + sAuthor := ''; + + if book.MetaData.LastModifiedBy <> '' then + sLastAuthor := '' + book.MetaData.LastModifiedBy + '' + LE + INDENT2 + else + sLastAuthor := ''; + + // Dates are UTC and in format YYYY-mm-ddThh:nn:ssZ + if book.MetaData.DateCreated > 0 then begin + dt := book.MetaData.DateCreated + GetLocalTimeOffset / (24*60); + sDateCreated := FormatDateTime(ISO8601FormatExtendedUTC, dt); + sDateCreated := '' + sDateCreated + '' + LE + INDENT2; + end else + sDateCreated := ''; + + if book.MetaData.DateLastModified > 0 then + begin + dt := book.MetaData.DateLastModified + GetLocalTimeOffset / (24*60); + sDateLastSaved := FormatDateTime(ISO8601FormatExtendedUTC, dt); + sDateLastSaved := '' + sDateLastSaved + '' + LE + INDENT2; + end else + sDateLastSaved := ''; + + AppendToStream(AStream, INDENT1 + + '' + LE + INDENT2 + + sTitle + + sAuthor + + sLastAuthor + + sDateCreated + + sDateLastSaved + + '16.00' + LE + Indent1 + + '' + LE ); - } end; procedure TsSpreadExcelXMLWriter.WriteError(AStream: TStream; diff --git a/components/fpspreadsheet/source/common/xlsxooxml.pas b/components/fpspreadsheet/source/common/xlsxooxml.pas index faf82ea91..24fce1234 100644 --- a/components/fpspreadsheet/source/common/xlsxooxml.pas +++ b/components/fpspreadsheet/source/common/xlsxooxml.pas @@ -6101,6 +6101,7 @@ procedure TsSpreadOOXMLWriter.WriteMetaData(AStream: TStream); var book: TsWorkbook; s: String; + dt: TDateTime; begin book := TsWorkbook(FWorkbook); @@ -6152,15 +6153,17 @@ begin if book.MetaData.DateCreated > 0 then begin - s := FormatDateTime(ISO8601FormatExtended, book.MetaData.DateCreated) + 'Z'; + dt := book.MetaData.DateCreated + GetLocalTimeOffset / MinsPerDay; + s := FormatDateTime(ISO8601FormatExtendedUTC, dt); AppendToStream(AStream, Format( '%s', [s])); end; if book.MetaData.DateLastModified <= 0 then - s := FormatDateTime(ISO8601FormatExtended, book.MetaData.DateCreated) + 'Z' + dt := book.MetaData.DateCreated + GetLocalTimeOffset / MinsPerDay else - s := FormatDateTime(ISO8601FormatExtended, book.MetaData.DateLastModified) + 'Z'; + dt := book.MetaData.DateLastModified + GetLocalTimeOffset / MinsPerDay; + s := FormatDateTime(ISO8601FormatExtendedUTC, dt); AppendToStream(AStream, Format( '%s', [s]));