V1.0.1.8 -- 2025/12/20

* BMS History Function Improved
This commit is contained in:
2025-12-20 12:24:39 +09:00
parent ea26bb8214
commit 56e341a48a
12 changed files with 651 additions and 121 deletions

View File

@@ -1,16 +1,19 @@
using System;
using ClosedXML.Excel;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.OleDb;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Data;
using System.Windows.Forms;
using System.Data.OleDb;
namespace LFP_Manager.Function
{
public class csExcelFunction
{
#region EXCEL IMPORT / EXPORT
public static DataTable[] ExcelImport(string Ps_FileName, string[] sheetName)
{
DataTable[] result = null;
@@ -141,5 +144,209 @@ namespace LFP_Manager.Function
connection.Dispose();
}
}
#endregion
#region EXCEL CLOSED XML IMPORT / EXPORT
private static readonly string[] IsoDateFormats = new string[]
{
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-ddTHH:mm:ss",
"yyyy-MM-ddTHH:mm:ss.fff",
"yyyy-MM-ddTHH:mm:ssZ",
"yyyy-MM-ddTHH:mm:ss.fffZ",
"yyyy-MM-dd"
};
public static bool ExportDgvWithClosedXml(string filePath, DataTable[] dt, bool overwiteFile = true)
{
if (dt == null || dt.Length == 0) return false;
// .xls로 들어오면 .xlsx로 변환하여 저장 경로 결정
var outputPath = EnsureXlsxPath(filePath);
// 저장 경로 폴더 보장
var dir = Path.GetDirectoryName(outputPath);
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
if (File.Exists(outputPath))
{
if (overwiteFile) File.Delete(outputPath);
else throw new IOException("File already exists: " + outputPath);
}
try
{
using (var wb = new XLWorkbook()) // C# 7.3 호환
{
var nameCount = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < dt.Length; i++)
{
var table = dt[i];
if (table == null) continue;
string baseName = !string.IsNullOrWhiteSpace(table.TableName) ? table.TableName : ("Sheet" + (i + 1));
string sheetName = SanitizeSheetName(baseName);
// 중복 시트명 처리: _2, _3...
int dup;
if (nameCount.TryGetValue(sheetName, out dup))
{
dup++;
nameCount[sheetName] = dup;
string prefix = sheetName.Length > 28 ? sheetName.Substring(0, 28) : sheetName; // "_n" 자리 고려
sheetName = prefix + "_" + dup;
}
else
{
nameCount[sheetName] = 1;
}
// DataTable 전체를 시트로 추가 (헤더 포함)
var ws = wb.Worksheets.Add(table, sheetName);
// 자동 필터
var tbl = ws.Tables.FirstOrDefault();
if (tbl != null) tbl.ShowAutoFilter = true;
// ====== [추가] 문자열 날짜 → 진짜 DateTime으로 변환 ======
// 1) 날짜 컬럼 후보 탐지 (타입/컬럼명/샘플링)
bool[] isDateCol = new bool[table.Columns.Count];
for (int c = 0; c < table.Columns.Count; c++)
{
var colType = table.Columns[c].DataType;
if (colType == typeof(DateTime)) { isDateCol[c] = true; continue; }
if (colType == typeof(string))
{
string colName = table.Columns[c].ColumnName ?? "";
// 컬럼명 힌트
if (colName.IndexOf("date", StringComparison.OrdinalIgnoreCase) >= 0 ||
colName.IndexOf("time", StringComparison.OrdinalIgnoreCase) >= 0 ||
colName.EndsWith("_at", StringComparison.OrdinalIgnoreCase))
{
isDateCol[c] = true;
continue;
}
// 값 샘플링 (최대 20개)
int seen = 0, hits = 0;
for (int r = 0; r < table.Rows.Count && seen < 20; r++)
{
var obj = table.Rows[r][c];
if (obj == null || obj == DBNull.Value) continue;
var s = obj.ToString();
if (string.IsNullOrWhiteSpace(s)) continue;
seen++;
DateTime tmp;
if (TryParseIsoDate(s, out tmp)) hits++;
}
if (seen > 0 && (hits * 1.0 / seen) >= 0.6) isDateCol[c] = true;
}
}
// 2) 열 서식 지정 + 각 셀 값 DateTime으로 재설정
for (int c = 0; c < table.Columns.Count; c++)
{
var col = ws.Column(c + 1);
Type t = table.Columns[c].DataType;
if (isDateCol[c])
{
// 열 서식(표시): 날짜/시간
col.Style.DateFormat.Format = "yyyy-MM-dd HH:mm:ss";
// 데이터 행(2행부터) 변환
int rowCount = table.Rows.Count;
for (int r = 0; r < rowCount; r++)
{
var cell = ws.Cell(r + 2, c + 1);
// 이미 DateTime 셀은 스킵
if (cell.DataType == ClosedXML.Excel.XLDataType.DateTime)
continue;
var s = cell.GetString();
if (string.IsNullOrWhiteSpace(s)) continue;
DateTime dtv;
if (TryParseIsoDate(s, out dtv))
{
cell.Value = dtv; // ✅ 진짜 날짜로 저장 (엑셀 DateTime)
// cell.Style.DateFormat.Format = "yyyy-MM-dd HH:mm:ss"; // per-cell 필요시
}
}
}
else
{
// 기존 숫자/텍스트 서식 유지
if (t == typeof(DateTime))
col.Style.DateFormat.Format = "yyyy-MM-dd HH:mm:ss";
else if (t == typeof(int) || t == typeof(long) || t == typeof(short) || t == typeof(byte))
col.Style.NumberFormat.Format = "0"; // 정수
else if (t == typeof(decimal) || t == typeof(float) || t == typeof(double))
col.Style.NumberFormat.Format = "General"; // 일반 숫자
else
col.Style.NumberFormat.Format = "@"; // 텍스트
}
}
// ====== [추가 끝] ======
// 열 너비 자동 조정
ws.Columns().AdjustToContents();
}
wb.SaveAs(outputPath);
}
}
catch (Exception ex)
{
// 필요 시 로깅 추가
throw new Exception(ex.Message);
}
return true;
}
private static string EnsureXlsxPath(string filePath)
{
if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException(nameof(filePath));
var ext = Path.GetExtension(filePath);
if (!string.IsNullOrEmpty(ext) && ext.Equals(".xls", StringComparison.OrdinalIgnoreCase))
return Path.ChangeExtension(filePath, ".xlsx");
return filePath;
}
private static string SanitizeSheetName(string name)
{
if (string.IsNullOrEmpty(name)) return "Sheet1";
// Excel 금지문자: : \ / ? * [ ]
char[] invalid = { ':', '\\', '/', '?', '*', '[', ']' };
var arr = name.ToCharArray();
for (int i = 0; i < arr.Length; i++)
for (int j = 0; j < invalid.Length; j++)
if (arr[i] == invalid[j]) { arr[i] = '_'; break; }
var sanitized = new string(arr);
if (sanitized.Length > 31) sanitized = sanitized.Substring(0, 31);
if (string.IsNullOrWhiteSpace(sanitized)) sanitized = "Sheet1";
return sanitized;
}
private static bool TryParseIsoDate(string s, out DateTime dt)
{
// 로컬 시간 가정(오프셋 보정 없음). DB 문자열이 UTC라면 AdjustToUniversal로 변경하세요.
return DateTime.TryParseExact(
s, IsoDateFormats,
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeLocal,
out dt)
|| DateTime.TryParse(
s, CultureInfo.InvariantCulture,
DateTimeStyles.AssumeLocal,
out dt);
}
#endregion
}
}