MUSX Document Model
Loading...
Searching...
No Matches
DateTimeFormat.h
1/*
2 * Copyright (C) 2025, Robert Patterson
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22#pragma once
23
24#include <string>
25#include <cstring>
26#include <ctime>
27
28#ifdef MUSX_RUNNING_ON_WINDOWS
29#include <windows.h>
30#else
31#include <locale>
32#include <sstream>
33#include <iomanip>
34#include <langinfo.h>
35#endif
36
37namespace musx {
38namespace util {
39
45{
46
51 enum class DateFormatStyle : int
52 {
53 Short = 0,
54 Long = 1,
56 };
57
66 inline static std::time_t makeTimeT(int year, int month, int day)
67 {
68 std::tm tm = {};
69 tm.tm_year = year - 1900; // tm_year is years since 1900
70 tm.tm_mon = month - 1; // tm_mon is 0-based
71 tm.tm_mday = day;
72 tm.tm_hour = 0;
73 tm.tm_min = 0;
74 tm.tm_sec = 0;
75 tm.tm_isdst = -1; // Let system determine daylight saving time
76
77 return std::mktime(&tm);
78 }
79
80#ifdef MUSX_RUNNING_ON_WINDOWS
90 inline static std::string formatDate(std::time_t t, DateFormatStyle style)
91 {
92 SYSTEMTIME st;
93 {
94 struct tm tm;
95 localtime_s(&tm, &t);
96 st.wYear = WORD(tm.tm_year + 1900);
97 st.wMonth = WORD(tm.tm_mon + 1);
98 st.wDay = WORD(tm.tm_mday);
99 st.wHour = WORD(tm.tm_hour);
100 st.wMinute = WORD(tm.tm_min);
101 st.wSecond = WORD(tm.tm_sec);
102 st.wMilliseconds = 0;
103 }
104
105 // Determine flags and custom format string
106 DWORD flags = 0;
107 const wchar_t* customFormat = nullptr;
108
109 switch (style)
110 {
112 flags = DATE_SHORTDATE;
113 break;
114
116 flags = DATE_LONGDATE;
117 break;
118
120 // Windows does not provide a built-in long abbreviated date, so use custom format string
121 customFormat = L"MMM d, yyyy";
122 break;
123 }
124
125 // First call to get required size
126 int requiredSize = GetDateFormatEx(
127 nullptr, flags, &st, customFormat, nullptr, 0, nullptr);
128
129 if (requiredSize == 0) {
130 throw std::system_error(GetLastError(), std::system_category());
131 }
132
133 // Allocate WCHAR buffer with exact required size
134 std::wstring wbuf(requiredSize, L'\0');
135
136 // Second call to actually format
137 int result = GetDateFormatEx(
138 nullptr, flags, &st, customFormat, wbuf.data(), requiredSize, nullptr);
139
140 if (result == 0) {
141 throw std::system_error(GetLastError(), std::system_category());
142 }
143
144 // Convert WCHAR (UTF-16) to UTF-8
145 int utf8len = WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), -1, nullptr, 0, nullptr, nullptr);
146 std::string output(utf8len, '\0');
147 WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), -1, &output[0], utf8len, nullptr, nullptr);
148 output.resize(utf8len - 1); // remove null terminator
149
150 return output;
151 }
152#endif // MUSX_RUNNING_ON_WINDOWS
153
154#ifndef MUSX_RUNNING_ON_WINDOWS
164 inline static std::string formatDate(std::time_t t, DateFormatStyle style)
165 {
166 struct tm tm;
167
168 localtime_r(&t, &tm);
169
170 const char* fmt = nullptr;
171 switch (style) {
173 fmt = "%x";
174 break;
176 fmt = "%B %d, %Y";
177 break;
179 fmt = "%b %d, %Y";
180 break;
181 }
182
183 std::ostringstream oss;
184 oss.imbue(std::locale("")); // Use user’s current locale
185
186 oss << std::put_time(&tm, fmt);
187 return oss.str();
188 }
189#endif // !MUSX_RUNNING_ON_WINDOWS
190
201 inline static std::string formatTime(std::time_t t, bool includeSeconds)
202 {
203#ifdef MUSX_RUNNING_ON_WINDOWS
204 SYSTEMTIME st;
205 {
206 struct tm tm;
207 localtime_s(&tm, &t);
208 st.wYear = WORD(tm.tm_year + 1900);
209 st.wMonth = WORD(tm.tm_mon + 1);
210 st.wDay = WORD(tm.tm_mday);
211 st.wHour = WORD(tm.tm_hour);
212 st.wMinute = WORD(tm.tm_min);
213 st.wSecond = WORD(tm.tm_sec);
214 st.wMilliseconds = 0;
215 }
216
217 DWORD flags = 0;
218 if (!includeSeconds) {
219 flags = TIME_NOSECONDS;
220 }
221
222 // First call to get required size
223 int requiredSize = GetTimeFormatEx(
224 nullptr, flags, &st, nullptr, nullptr, 0);
225
226 if (requiredSize == 0) {
227 throw std::system_error(GetLastError(), std::system_category());
228 }
229
230 // Allocate WCHAR buffer
231 std::wstring wbuf(requiredSize, L'\0');
232
233 // Second call to actually format
234 int result = GetTimeFormatEx(
235 nullptr, flags, &st, nullptr, wbuf.data(), requiredSize);
236
237 if (result == 0) {
238 throw std::system_error(GetLastError(), std::system_category());
239 }
240
241 // Convert WCHAR (UTF-16) to UTF-8
242 int utf8len = WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), -1, nullptr, 0, nullptr, nullptr);
243 std::string output(utf8len, '\0');
244 WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), -1, &output[0], utf8len, nullptr, nullptr);
245 output.resize(utf8len - 1); // remove null terminator
246
247 return output;
248
249#else // POSIX
250
251 struct tm tm;
252 localtime_r(&t, &tm);
253
254 const char* fmt = nullptr;
255
256 if (includeSeconds) {
257 fmt = "%X"; // Locale-specific full time with seconds
258 } else {
259 // Determine 24h vs 12h heuristic using nl_langinfo
260 bool is24h = false;
261 const char* tfmt = nl_langinfo(T_FMT);
262 if (tfmt && std::strchr(tfmt, 'H')) {
263 is24h = true;
264 }
265
266 fmt = is24h ? "%H:%M" : "%I:%M %p";
267 }
268
269 std::ostringstream oss;
270 oss.imbue(std::locale("")); // Use user's current locale
271 oss << std::put_time(&tm, fmt);
272 return oss.str();
273
274#endif
275 }
276
277};
278
279} // namespace util
280} // namespace musx
object model for musx file (enigmaxml)
Definition BaseClasses.h:36
Static class to provide utility functions for formatting date and time.
Definition DateTimeFormat.h:45
static std::time_t makeTimeT(int year, int month, int day)
Creates a std::time_t from integer year, month, and day.
Definition DateTimeFormat.h:66
static std::string formatDate(std::time_t t, DateFormatStyle style)
Formats a date according to the current locale on POSIX systems.
Definition DateTimeFormat.h:164
static std::string formatTime(std::time_t t, bool includeSeconds)
Formats a time according to the current locale.
Definition DateTimeFormat.h:201
DateFormatStyle
Defines the date format styles supported by formatDate.
Definition DateTimeFormat.h:52
@ Short
Short date format (e.g., 7/10/25)
@ Long
Long date format (e.g., July 10, 2025)
@ LongAbbreviated
Long abbreviated date format (e.g., Jul 10, 2025)