Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions TeXmacs/progs/texmacs/texmacs/tm-files-test.scm
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
(:use (texmacs texmacs tm-files))
) ;texmacs-module

(import (only (liii path) path-join))
(import (only (liii path) path-join path->string))

(define (regtest-auto-backup-official-url)
(regression-test-group "auto-backup"
Expand All @@ -40,7 +40,8 @@
(lambda (case
) ;case
(and (== case "inside")
(auto-backup-texmacs-path-buffer? (system->url (path-join (url->system (get-texmacs-path)) "progs" "test.tmu"))
(auto-backup-texmacs-path-buffer? (system->url (path->string (path-join (url->system (get-texmacs-path)) "progs" "test.tmu"))
) ;system->url
) ;auto-backup-texmacs-path-buffer?
) ;and
) ;lambda
Expand Down
74 changes: 54 additions & 20 deletions devel/1112.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,82 @@
- [dddd.md](dddd.md) - 任务文档模板

## 2 任务相关的代码文件
- `lolly/System/Language/locale.hpp`
- `lolly/System/Language/locale.cpp`
- `src/System/Language/tm_locale.hpp`
- `src/System/Language/tm_locale.cpp`
- `src/Plugins/Qt/qt_utilities.hpp`
- `src/Plugins/Qt/qt_utilities.cpp`
- `tests/System/Language/tm_locale_test.cpp`
- `lolly/tests/lolly/system/locale_test.cpp`

## 3 如何测试

### 3.1 确定性测试(单元测试
### 3.1 lolly 单元测试
```bash
xmake b tm_locale_test
xmake r tm_locale_test
cd lolly
xmake f --enable_tests=y
xmake b locale_test
xmake r locale_test
```

### 3.2 非确定性测试(文档验证)
无。
### 3.2 主项目构建验证
```bash
xmake b stem
```

### 3.3 提交前最少步骤
```bash
gf fmt --changed-since=main
xmake b stem
cd lolly
xmake f --enable_tests=y
xmake b locale_test
xmake r locale_test
```

## 4 如何提交

提交前执行以下最少步骤:

```bash
gf fmt --changed-since=main
xmake b tm_locale_test
xmake r tm_locale_test
xmake b stem
cd lolly
xmake f --enable_tests=y
xmake b locale_test
xmake r locale_test
```

## 5 What
为 `tm_locale.hpp` 中的 `get_date` 函数扩展可测试性,并补充完善的单元测试,作为后续将 `tm_locale` 迁移到 lolly 的基础。

1. 扩展 `get_date` 签名,支持传入固定的 `year/month/day`(全为 `-1` 时使用当前时间)
2. 在 `qt_get_date` 中新增 `QDateTime` 重载及 `year/month/day` 重载,保持原有行为不变
3. 新增 `tests/System/Language/tm_locale_test.cpp`
4. 覆盖 `strftime` 格式:`%Y`、`%m`、`%d`、`%B`、`%A`、`%H:%M`、`%H:%M:%S`、`%Y-%m-%d`
5. 覆盖 Qt 日期格式:`yyyy`、`M`、`d`、`MMMM`、`dddd`
6. 覆盖各语言默认格式:english、british、american、german、chinese、japanese、korean 及未知语言
将 `tm_locale` 中与平台无关的日期格式化逻辑迁移到 lolly,解除 `get_date` 对 Qt 的依赖:

1. 在 `lolly/System/Language/locale.hpp/cpp` 中新增 `lolly::locale::get_date`,使用 C 标准库 time/locale 与自定义 Qt 日期格式解析器实现,不依赖 Qt。
2. 主项目 `src/System/Language/tm_locale.cpp` 改为调用 `lolly::locale::get_date`,移除对 `qt_get_date` 的依赖。
3. 从 `src/Plugins/Qt/qt_utilities.hpp/cpp` 中移除不再使用的 `qt_get_date` 系列函数。
4. 将 `get_date` 的单元测试从主项目迁移到 `lolly/tests/lolly/system/locale_test.cpp`,使用 doctest 框架重写。
5. 覆盖原有行为:
- `strftime` 格式:`%Y`、`%m`、`%d`、`%B`、`%A`、`%H:%M`、`%H:%M:%S`、`%Y-%m-%d`
- Qt 日期格式:`yyyy`、`M`、`d`、`MMMM`、`dddd`
- 各语言默认格式:`english`、`british`、`american`、`german`、`chinese`、`japanese`、`korean` 及未知语言
- 全为 `-1` 时使用当前时间

## 6 Why
`tm_locale` 目前依赖 Qt 实现,目标是将其中与平台无关的日期/本地化逻辑下沉到 lolly。先补充测试用例可以:
- 固化现有行为,防止迁移过程中出现回归
- 明确 `get_date` 在不同语言和格式下的输出形式
- 通过固定日期让测试输出可直接与字符串比较,结果更明确

`tm_locale` 目前依赖 Qt 实现日期格式化,目标是将其中与平台无关的日期/本地化逻辑下沉到 lolly:

- 让 lolly 成为可独立复用的基础设施库
- 降低主项目对 Qt 的依赖
- 在迁移过程中通过 lolly 侧的单元测试固化行为,防止回归

## 7 How
测试使用 QtTest 框架,调用 `get_date(lan, fm, year, month, day)` 并传入固定日期,直接与预期字符串比较。CJK 默认格式测试中,使用 `herk_to_utf8` 将 Cork 编码结果转回 UTF-8,再与可读的中日韩文字面量比较。保留一个回退用例验证全 `-1` 时使用当前时间。

`lolly::locale::get_date` 的实现要点:

- 使用 `<ctime>` 与 `<clocale>` 提供的 `strftime`、`time`、`localtime`、`mktime`、`setlocale` 处理 `strftime` 格式与当前时间回退。
- 使用自定义解析器处理 Qt 日期格式字符 `y/M/d`,支持 `yyyy`、`M`、`MM`、`MMM`、`MMMM`、`d`、`dd`、`ddd`、`dddd`。
- 内置常用语言(English、German)的月份名与星期名表;未知语言默认使用 English。
- CJK 默认格式直接按 Qt 原有行为拼接 Cork 编码字符(`<#5e74>`/`<#6708>`/`<#65e5>`、`<#b144>`/`<#c6d4>`/`<#c77c>`)。
- 测试使用 doctest 框架,固定日期与预期字符串直接比较;CJK 测试使用 `lolly::data::encode_as_utf8` 构造 UTF-8 预期值。

保留一个回退用例验证全 `-1` 时使用当前时间。
218 changes: 218 additions & 0 deletions lolly/System/Language/locale.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@

#include "locale.hpp"

#include <clocale>
#include <cstring>
#include <ctime>

#if !defined(OS_MINGW) && !defined(OS_WIN)
#include <langinfo.h>
#ifndef X11TEXMACS
Expand Down Expand Up @@ -269,3 +273,217 @@ string
get_locale_charset () {
return "UTF-8";
}

namespace lolly {
namespace locale {

/******************************************************************************
* Date helpers
******************************************************************************/

static int
days_in_month (int year, int month) {
static int days_per_month[12]= {31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31};
if (month == 2) {
bool leap= (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
return leap ? 29 : 28;
}
return days_per_month[month - 1];
}

static int
weekday (int year, int month, int day) {
struct tm tm;
memset (&tm, 0, sizeof (tm));
tm.tm_year = year - 1900;
tm.tm_mon = month - 1;
tm.tm_mday = day;
tm.tm_isdst= -1;
mktime (&tm);
return tm.tm_wday;
}

/******************************************************************************
* Month and weekday names
******************************************************************************/

static const char* en_months_full[12]= {
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"};
static const char* en_months_abbr[12] = {"Jan", "Feb", "Mar", "Apr",
"May", "Jun", "Jul", "Aug",
"Sep", "Oct", "Nov", "Dec"};
static const char* en_weekdays_full[7]= {"Sunday", "Monday", "Tuesday",
"Wednesday", "Thursday", "Friday",
"Saturday"};
static const char* en_weekdays_abbr[7]= {"Sun", "Mon", "Tue", "Wed",
"Thu", "Fri", "Sat"};

static const char* de_months_full[12]= {
"Januar", "Februar", "M<#e4>rz", "April", "Mai", "Juni",
"Juli", "August", "September", "Oktober", "November", "Dezember"};
static const char* de_months_abbr[12] = {"Jan", "Feb", "M<#e4>r", "Apr",
"Mai", "Jun", "Jul", "Aug",
"Sep", "Okt", "Nov", "Dez"};
static const char* de_weekdays_full[7]= {"Sonntag", "Montag", "Dienstag",
"Mittwoch", "Donnerstag", "Freitag",
"Samstag"};
static const char* de_weekdays_abbr[7]= {"So", "Mo", "Di", "Mi",
"Do", "Fr", "Sa"};

static string
month_name (string lan, int month, bool abbrev) {
int idx= month - 1;
if (lan == "german") {
return abbrev ? de_months_abbr[idx] : de_months_full[idx];
}
return abbrev ? en_months_abbr[idx] : en_months_full[idx];
}

static string
weekday_name (string lan, int wd, bool abbrev) {
if (lan == "german") {
return abbrev ? de_weekdays_abbr[wd] : de_weekdays_full[wd];
}
return abbrev ? en_weekdays_abbr[wd] : en_weekdays_full[wd];
}

/******************************************************************************
* Qt-style date format
******************************************************************************/

static string
format_qt (string lan, string fm, int year, int month, int day) {
string r;
int i= 0, n= N (fm);
while (i < n) {
char c= fm[i];
if (c == 'y') {
int j= i;
while (j < n && fm[j] == 'y')
j++;
int len= j - i;
if (len >= 4) r << as_string (year);
else if (len == 2) {
int y= year % 100;
if (y < 10) r << '0';
r << as_string (y);
}
i= j;
}
else if (c == 'M') {
int j= i;
while (j < n && fm[j] == 'M')
j++;
int len= j - i;
if (len >= 4) r << month_name (lan, month, false);
else if (len == 3) r << month_name (lan, month, true);
else if (len == 2) {
if (month < 10) r << '0';
r << as_string (month);
}
else r << as_string (month);
i= j;
}
else if (c == 'd') {
int j= i;
while (j < n && fm[j] == 'd')
j++;
int len= j - i;
if (len >= 4) r << weekday_name (lan, weekday (year, month, day), false);
else if (len == 3)
r << weekday_name (lan, weekday (year, month, day), true);
else if (len == 2) {
if (day < 10) r << '0';
r << as_string (day);
}
else r << as_string (day);
i= j;
}
else {
r << c;
i++;
}
}
return r;
}

/******************************************************************************
* strftime date format
******************************************************************************/

static string
format_strftime (string lan, string fm, int year, int month, int day) {
struct tm tm;
memset (&tm, 0, sizeof (tm));
tm.tm_year = year - 1900;
tm.tm_mon = month - 1;
tm.tm_mday = day;
tm.tm_isdst= -1;
mktime (&tm);

char old_locale_buf[64];
const char* old_locale= setlocale (LC_TIME, nullptr);
int old_len = strlen (old_locale);
for (int i= 0; i < old_len; i++)
old_locale_buf[i]= old_locale[i];
old_locale_buf[old_len]= '\0';

char locale_buf[64];
string locale_s = language_to_locale (lan);
int locale_len= N (locale_s);
for (int i= 0; i < locale_len; i++)
locale_buf[i]= locale_s[i];
locale_buf[locale_len]= '\0';
setlocale (LC_TIME, locale_buf);

char fm_buf[64];
int fm_len= N (fm);
for (int i= 0; i < fm_len; i++)
fm_buf[i]= fm[i];
fm_buf[fm_len]= '\0';

char buf[256];
strftime (buf, sizeof (buf), fm_buf, &tm);

setlocale (LC_TIME, old_locale_buf);
return string (buf);
}

/******************************************************************************
* Getting a formatted date
******************************************************************************/

string
get_date (string lan, string fm, int year, int month, int day) {
if (year == -1 && month == -1 && day == -1) {
time_t now = time (nullptr);
struct tm* local= localtime (&now);
year = local->tm_year + 1900;
month = local->tm_mon + 1;
day = local->tm_mday;
}

if (fm == "") {
if (lan == "british" || lan == "english" || lan == "american")
return format_qt (lan, "MMMM d, yyyy", year, month, day);
else if (lan == "german")
return format_qt (lan, "d. MMMM yyyy", year, month, day);
else if (lan == "chinese" || lan == "japanese" || lan == "taiwanese")
return as_string (year) * "<#5e74>" * as_string (month) * "<#6708>" *
as_string (day) * "<#65e5>";
else if (lan == "korean")
return as_string (year) * "<#b144> " * as_string (month) * "<#c6d4> " *
as_string (day) * "<#c77c>";
else return format_qt (lan, "d MMMM yyyy", year, month, day);
}

if (N (fm) > 0 && fm[0] == '%')
return format_strftime (lan, fm, year, month, day);

return format_qt (lan, fm, year, month, day);
}

} // namespace locale
} // namespace lolly
9 changes: 9 additions & 0 deletions lolly/System/Language/locale.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,13 @@ string language_to_local_ISO_charset (string s);
string get_locale_language ();
string get_locale_charset ();

namespace lolly {
namespace locale {

string get_date (string lan, string fm, int year= -1, int month= -1,
int day= -1);

} // namespace locale
} // namespace lolly

#endif
Loading
Loading