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
37#include "musx/dom/EnumClasses.h"
38
39namespace musx {
40namespace util {
41
47{
49
58 inline static std::time_t makeTimeT(int year, int month, int day)
59 {
60 std::tm tm = {};
61 tm.tm_year = year - 1900; // tm_year is years since 1900
62 tm.tm_mon = month - 1; // tm_mon is 0-based
63 tm.tm_mday = day;
64 tm.tm_hour = 0;
65 tm.tm_min = 0;
66 tm.tm_sec = 0;
67 tm.tm_isdst = -1; // Let system determine daylight saving time
68
69 return std::mktime(&tm);
70 }
71
72#ifdef MUSX_RUNNING_ON_WINDOWS
82 inline static std::string formatDate(std::time_t t, DateFormat style)
83 {
84 SYSTEMTIME st;
85 {
86 struct tm tm;
87 localtime_s(&tm, &t);
88 st.wYear = WORD(tm.tm_year + 1900);
89 st.wMonth = WORD(tm.tm_mon + 1);
90 st.wDay = WORD(tm.tm_mday);
91 st.wHour = WORD(tm.tm_hour);
92 st.wMinute = WORD(tm.tm_min);
93 st.wSecond = WORD(tm.tm_sec);
94 st.wMilliseconds = 0;
95 }
96
97 // Determine flags and custom format string
98 DWORD flags = 0;
99 const wchar_t* customFormat = nullptr;
100
101 switch (style)
102 {
103 case DateFormat::Short:
104 flags = DATE_SHORTDATE;
105 break;
106
107 case DateFormat::Long:
108 flags = DATE_LONGDATE;
109 break;
110
111 case DateFormat::Abbrev:
112 // Windows does not provide a built-in long abbreviated date, so use custom format string
113 customFormat = L"MMM d, yyyy";
114 break;
115 }
116
117 // First call to get required size
118 int requiredSize = GetDateFormatEx(
119 nullptr, flags, &st, customFormat, nullptr, 0, nullptr);
120
121 if (requiredSize == 0) {
122 throw std::system_error(GetLastError(), std::system_category());
123 }
124
125 // Allocate WCHAR buffer with exact required size
126 std::wstring wbuf(requiredSize, L'\0');
127
128 // Second call to actually format
129 int result = GetDateFormatEx(
130 nullptr, flags, &st, customFormat, wbuf.data(), requiredSize, nullptr);
131
132 if (result == 0) {
133 throw std::system_error(GetLastError(), std::system_category());
134 }
135
136 // Convert WCHAR (UTF-16) to UTF-8
137 int utf8len = WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), -1, nullptr, 0, nullptr, nullptr);
138 std::string output(utf8len, '\0');
139 WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), -1, &output[0], utf8len, nullptr, nullptr);
140 output.resize(utf8len - 1); // remove null terminator
141
142 return output;
143 }
144#endif // MUSX_RUNNING_ON_WINDOWS
145
146#ifndef MUSX_RUNNING_ON_WINDOWS
156 inline static std::string formatDate(std::time_t t, DateFormat style)
157 {
158 struct tm tm;
159
160 localtime_r(&t, &tm);
161
162 const char* fmt = nullptr;
163 switch (style) {
164 case DateFormat::Short:
165 fmt = "%x";
166 break;
167 case DateFormat::Long:
168 fmt = "%B %d, %Y";
169 break;
170 case DateFormat::Abbrev:
171 fmt = "%b %d, %Y";
172 break;
173 }
174
175 std::ostringstream oss;
176 try {
177 oss.imbue(std::locale("")); // Use user’s current locale
178 } catch (const std::runtime_error&) {
179 oss.imbue(std::locale::classic());
180 }
181
182 oss << std::put_time(&tm, fmt);
183 return oss.str();
184 }
185#endif // !MUSX_RUNNING_ON_WINDOWS
186
197 inline static std::string formatTime(std::time_t t, bool includeSeconds)
198 {
199#ifdef MUSX_RUNNING_ON_WINDOWS
200 SYSTEMTIME st;
201 {
202 struct tm tm;
203 localtime_s(&tm, &t);
204 st.wYear = WORD(tm.tm_year + 1900);
205 st.wMonth = WORD(tm.tm_mon + 1);
206 st.wDay = WORD(tm.tm_mday);
207 st.wHour = WORD(tm.tm_hour);
208 st.wMinute = WORD(tm.tm_min);
209 st.wSecond = WORD(tm.tm_sec);
210 st.wMilliseconds = 0;
211 }
212
213 DWORD flags = 0;
214 if (!includeSeconds) {
215 flags = TIME_NOSECONDS;
216 }
217
218 // First call to get required size
219 int requiredSize = GetTimeFormatEx(
220 nullptr, flags, &st, nullptr, nullptr, 0);
221
222 if (requiredSize == 0) {
223 throw std::system_error(GetLastError(), std::system_category());
224 }
225
226 // Allocate WCHAR buffer
227 std::wstring wbuf(requiredSize, L'\0');
228
229 // Second call to actually format
230 int result = GetTimeFormatEx(
231 nullptr, flags, &st, nullptr, wbuf.data(), requiredSize);
232
233 if (result == 0) {
234 throw std::system_error(GetLastError(), std::system_category());
235 }
236
237 // Convert WCHAR (UTF-16) to UTF-8
238 int utf8len = WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), -1, nullptr, 0, nullptr, nullptr);
239 std::string output(utf8len, '\0');
240 WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), -1, &output[0], utf8len, nullptr, nullptr);
241 output.resize(utf8len - 1); // remove null terminator
242
243 return output;
244
245#else // POSIX
246
247 struct tm tm;
248 localtime_r(&t, &tm);
249
250 const char* fmt = nullptr;
251
252 if (includeSeconds) {
253 fmt = "%X"; // Locale-specific full time with seconds
254 } else {
255 // Determine 24h vs 12h heuristic using nl_langinfo
256 bool is24h = false;
257 const char* tfmt = nl_langinfo(T_FMT);
258 if (tfmt && std::strchr(tfmt, 'H')) {
259 is24h = true;
260 }
261
262 fmt = is24h ? "%H:%M" : "%I:%M %p";
263 }
264
265 std::ostringstream oss;
266 try {
267 oss.imbue(std::locale("")); // Use user's current locale
268 } catch (const std::runtime_error&) {
269 oss.imbue(std::locale::classic());
270 }
271 oss << std::put_time(&tm, fmt);
272 return oss.str();
273
274#endif
275 }
276
277};
278
279} // namespace util
280} // namespace musx
DateFormat
Date format options. This value is coded into the Enigma date insert when the page title is created.
Definition EnumClasses.h:72
object model for musx file (enigmaxml)
Definition BaseClasses.h:36
Static class to provide utility functions for formatting date and time.
Definition DateTimeFormat.h:47
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:58
static std::string formatDate(std::time_t t, DateFormat style)
Formats a date according to the current locale on POSIX systems.
Definition DateTimeFormat.h:156
musx::dom::DateFormat DateFormat
Date format options.
Definition DateTimeFormat.h:48
static std::string formatTime(std::time_t t, bool includeSeconds)
Formats a time according to the current locale.
Definition DateTimeFormat.h:197