calendar display type supports Gregorian with Chinese

This commit is contained in:
MaysWind
2025-09-06 01:06:31 +08:00
parent e15a5617e6
commit 8368b02be8
45 changed files with 38451 additions and 43 deletions
+7 -4
View File
@@ -7,10 +7,11 @@ type CalendarDisplayType byte
// Calendar Display Type
const (
CALENDAR_DISPLAY_TYPE_DEFAULT CalendarDisplayType = 0
CALENDAR_DISPLAY_TYPE_GREGORAIN CalendarDisplayType = 1
CALENDAR_DISPLAY_TYPE_BUDDHIST CalendarDisplayType = 2
CALENDAR_DISPLAY_TYPE_INVALID CalendarDisplayType = 255
CALENDAR_DISPLAY_TYPE_DEFAULT CalendarDisplayType = 0
CALENDAR_DISPLAY_TYPE_GREGORAIN CalendarDisplayType = 1
CALENDAR_DISPLAY_TYPE_BUDDHIST CalendarDisplayType = 2
CALENDAR_DISPLAY_TYPE_GREGORAIN_WITH_CHINESE CalendarDisplayType = 3
CALENDAR_DISPLAY_TYPE_INVALID CalendarDisplayType = 255
)
// String returns a textual representation of the calendar display type enum
@@ -22,6 +23,8 @@ func (f CalendarDisplayType) String() string {
return "Gregorian"
case CALENDAR_DISPLAY_TYPE_BUDDHIST:
return "Buddhist"
case CALENDAR_DISPLAY_TYPE_GREGORAIN_WITH_CHINESE:
return "Gregorian with Chinese"
case CALENDAR_DISPLAY_TYPE_INVALID:
return "Invalid"
default:
+1 -1
View File
@@ -199,7 +199,7 @@ type UserProfileUpdateRequest struct {
DefaultCurrency string `json:"defaultCurrency" binding:"omitempty,len=3,validCurrency"`
FirstDayOfWeek *core.WeekDay `json:"firstDayOfWeek" binding:"omitempty,min=0,max=6"`
FiscalYearStart *core.FiscalYearStart `json:"fiscalYearStart" binding:"omitempty,validFiscalYearStart"`
CalendarDisplayType *core.CalendarDisplayType `json:"calendarDisplayType" binding:"omitempty,min=0,max=2"`
CalendarDisplayType *core.CalendarDisplayType `json:"calendarDisplayType" binding:"omitempty,min=0,max=3"`
DateDisplayType *core.DateDisplayType `json:"dateDisplayType" binding:"omitempty,min=0,max=2"`
LongDateFormat *core.LongDateFormat `json:"longDateFormat" binding:"omitempty,min=0,max=3"`
ShortDateFormat *core.ShortDateFormat `json:"shortDateFormat" binding:"omitempty,min=0,max=3"`
+1 -1
View File
@@ -293,7 +293,7 @@ func (s *UserService) UpdateUser(c core.Context, user *models.User, modifyUserLa
updateCols = append(updateCols, "fiscal_year_start")
}
if core.CALENDAR_DISPLAY_TYPE_DEFAULT <= user.CalendarDisplayType && user.CalendarDisplayType <= core.CALENDAR_DISPLAY_TYPE_BUDDHIST {
if core.CALENDAR_DISPLAY_TYPE_DEFAULT <= user.CalendarDisplayType && user.CalendarDisplayType <= core.CALENDAR_DISPLAY_TYPE_GREGORAIN_WITH_CHINESE {
updateCols = append(updateCols, "calendar_display_type")
}
+56 -4
View File
@@ -3,7 +3,7 @@
inline auto-apply
enable-seconds
six-weeks="center"
:class="datetimePickerClass"
:class="`datetime-picker ${showAlternateDates && alternateCalendarType ? 'datetime-picker-with-alternate-date' : ''} ${datetimePickerClass}`"
:config="noSwipeAndScroll ? { noSwipe: true } : undefined"
:dark="isDarkMode"
:vertical="vertical"
@@ -35,7 +35,10 @@
{{ getDisplayMonth(value) }}
</template>
<template #day="{ date }">
{{ getDisplayDay(date) }}
<div class="datetime-picker-display-dates">
<span>{{ getDisplayDay(date) }}</span>
<span class="datetime-picker-alternate-date" v-if="showAlternateDates && alternateCalendarType && getAlternateDate(date)">{{ getAlternateDate(date) }}</span>
</div>
</template>
<template #am-pm-button="{ toggle, value }">
<button class="dp__pm_am_button" tabindex="0" @click="toggle">{{ tt(`datetime.${value}.content`) }}</button>
@@ -51,7 +54,8 @@ import { useI18n } from '@/locales/helpers.ts';
import { useUserStore } from '@/stores/user.ts';
import { type PresetDateRange, type WeekDayValue } from '@/core/datetime.ts';
import type { CalendarType } from '@/core/calendar.ts';
import type { PresetDateRange, WeekDayValue } from '@/core/datetime.ts';
import { isArray, arrangeArrayWithNewStartIndex } from '@/lib/common.ts';
import { getAllowedYearRange, getYearMonthDayDateTime } from '@/lib/datetime.ts';
@@ -70,6 +74,7 @@ const props = defineProps<{
minDate?: Date;
maxDate?: Date;
disabledDates?: (date: Date) => boolean;
showAlternateDates?: boolean;
presetRanges?: PresetDateRange[];
}>();
@@ -80,11 +85,13 @@ const emit = defineEmits<{
const {
tt,
getAllMinWeekdayNames,
getCurrentCalendarDisplayType,
isLongDateMonthAfterYear,
isLongTime24HourFormat,
getCalendarShortYearFromUnixTime,
getCalendarShortMonthFromUnixTime,
getCalendarDayOfMonthFromUnixTime
getCalendarDayOfMonthFromUnixTime,
getCalendarAlternateDate
} = useI18n();
const userStore = useUserStore();
@@ -97,6 +104,7 @@ const dayNames = computed<string[]>(() => arrangeArrayWithNewStartIndex(getAllMi
const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek);
const isYearFirst = computed<boolean>(() => isLongDateMonthAfterYear());
const is24Hour = computed<boolean>(() => isLongTime24HourFormat());
const alternateCalendarType = computed<CalendarType | undefined>(() => getCurrentCalendarDisplayType().secondaryCalendarType);
const dateTime = computed<SupportedModelValue>({
get: () => props.modelValue,
@@ -105,6 +113,18 @@ const dateTime = computed<SupportedModelValue>({
const isDateRange = computed<boolean>(() => isArray(props.modelValue));
function getAlternateDate(date: Date): string | undefined {
if (!props.showAlternateDates) {
return undefined;
}
return getCalendarAlternateDate({
year: date.getFullYear(),
month: date.getMonth() + 1,
day: date.getDate()
})?.displayDate;
};
function switchView(viewType: MenuView): void {
datetimepicker.value?.switchView(viewType);
}
@@ -131,3 +151,35 @@ defineExpose({
switchView
});
</script>
<style>
.datetime-picker-display-dates {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.datetime-picker-alternate-date {
margin-top: -2px;
opacity: 0.5;
font-size: 0.8rem;
}
.dp__cell_disabled .datetime-picker-alternate-date,
.dp__cell_offset .datetime-picker-alternate-date {
opacity: 0.8;
}
.dp__main.datetime-picker .dp__calendar .dp__calendar_row > .dp__calendar_item .datetime-picker-display-dates > span.datetime-picker-alternate-date {
display: block;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dp__main.datetime-picker.datetime-picker-with-alternate-date .dp__calendar .dp__calendar_row {
--dp-cell-size: 45px;
}
</style>
+37 -3
View File
@@ -1,7 +1,7 @@
<template>
<vue-date-picker inline auto-apply
model-type="yyyy-MM-dd"
:class="`transaction-calendar ${calendarClass}`"
:class="`transaction-calendar ${alternateDates ? 'transaction-calendar-with-alternate-date' : ''} ${calendarClass}`"
:config="{ noSwipe: true }"
:readonly="readonly"
:dark="isDarkMode"
@@ -21,6 +21,7 @@
<template #day="{ day, date }">
<div class="transaction-calendar-daily-amounts">
<span :class="dayHasTransactionClass && dailyTotalAmounts && dailyTotalAmounts[day] ? dayHasTransactionClass : undefined">{{ getDisplayDay(date) }}</span>
<span class="transaction-calendar-alternate-date" v-if="alternateDates && alternateDates[`${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`]">{{ alternateDates[`${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`] }}</span>
<span class="transaction-calendar-daily-amount text-income" v-if="dailyTotalAmounts && dailyTotalAmounts[day] && dailyTotalAmounts[day].income">{{ getDisplayMonthTotalAmount(dailyTotalAmounts[day].income, defaultCurrency, '', dailyTotalAmounts[day].incompleteIncome) }}</span>
<span class="transaction-calendar-daily-amount text-expense" v-if="dailyTotalAmounts && dailyTotalAmounts[day] && dailyTotalAmounts[day].expense">{{ getDisplayMonthTotalAmount(dailyTotalAmounts[day].expense, defaultCurrency, '', dailyTotalAmounts[day].incompleteExpense) }}</span>
</div>
@@ -35,7 +36,7 @@ import { useI18n } from '@/locales/helpers.ts';
import { useUserStore } from '@/stores/user.ts';
import type { TransactionTotalAmount } from '@/stores/transaction.ts';
import { type TextualYearMonthDay, type WeekDayValue } from '@/core/datetime.ts';
import type { CalendarAlternateDate, TextualYearMonthDay, WeekDayValue } from '@/core/datetime.ts';
import { INCOMPLETE_AMOUNT_SUFFIX } from '@/consts/numeral.ts';
import { arrangeArrayWithNewStartIndex } from '@/lib/common.ts';
@@ -69,6 +70,7 @@ const {
getAllLongWeekdayNames,
getAllShortWeekdayNames,
getCalendarDayOfMonthFromUnixTime,
getCalendarAlternateDates,
formatAmountToLocalizedNumeralsWithCurrency
} = useI18n();
@@ -82,6 +84,28 @@ const dateTime = computed<TextualYearMonthDay | ''>({
set: (value: TextualYearMonthDay | '') => emit('update:modelValue', value)
});
const alternateDates = computed<Record<TextualYearMonthDay, string> | undefined>(() => {
const yearMonthDay = props.modelValue ? props.modelValue.split('-') : null;
if (!yearMonthDay || yearMonthDay.length !== 3) {
return undefined;
}
const allDates: CalendarAlternateDate[] | undefined = getCalendarAlternateDates({ year: parseInt(yearMonthDay[0]), month1base: parseInt(yearMonthDay[1]) })
if (!allDates) {
return undefined;
}
const ret: Record<TextualYearMonthDay, string> = {};
for (const alternateDate of allDates) {
ret[`${alternateDate.year}-${alternateDate.month}-${alternateDate.day}`] = alternateDate.displayDate;
}
return ret;
});
function noTransactionInMonthDay(date: Date): boolean {
const dateTime = parseDateTimeFromUnixTime(getActualUnixTimeForStore(getUnixTimeFromLocalDatetime(date), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()));
return !props.dailyTotalAmounts || !props.dailyTotalAmounts[dateTime.getGregorianCalendarDay()];
@@ -105,7 +129,17 @@ function getDisplayDay(date: Date): string {
justify-content: center;
}
.transaction-calendar.dp__main .dp__calendar .dp__calendar_row > .dp__calendar_item .transaction-calendar-daily-amounts > span.transaction-calendar-daily-amount {
.transaction-calendar-alternate-date {
margin-top: -3px;
opacity: 0.5;
}
.dp__cell_disabled .transaction-calendar-alternate-date {
opacity: 0.8;
}
.dp__main.transaction-calendar .dp__calendar .dp__calendar_row > .dp__calendar_item .transaction-calendar-daily-amounts > span.transaction-calendar-alternate-date,
.dp__main.transaction-calendar .dp__calendar .dp__calendar_row > .dp__calendar_item .transaction-calendar-daily-amounts > span.transaction-calendar-daily-amount {
display: block;
width: 100%;
overflow: hidden;
@@ -22,6 +22,7 @@
:enable-time-picker="true"
:vertical="true"
:preset-dates="presetRanges"
:show-alternate-dates="true"
v-model="dateRange">
</date-time-picker>
</v-card-text>
+1
View File
@@ -16,6 +16,7 @@
<date-time-picker :is-dark-mode="isDarkMode"
:enable-time-picker="false"
:clearable="true"
:show-alternate-dates="true"
v-model="dateTime">
</date-time-picker>
</template>
@@ -15,6 +15,7 @@
<date-time-picker :is-dark-mode="isDarkMode"
:enable-time-picker="false"
:vertical="true"
:show-alternate-dates="true"
v-model="dateTime">
</date-time-picker>
<div class="date-time-select-time-picker-container">
@@ -19,6 +19,7 @@
:is-dark-mode="isDarkMode"
:enable-time-picker="true"
:preset-dates="presetRanges"
:show-alternate-dates="true"
v-model="dateRange">
</date-time-picker>
<f7-button large fill
@@ -16,6 +16,7 @@
:is-dark-mode="isDarkMode"
:enable-time-picker="false"
:clearable="true"
:show-alternate-dates="true"
v-model="dateTime">
</date-time-picker>
</div>
@@ -16,6 +16,7 @@
datetime-picker-class="justify-content-center"
:is-dark-mode="isDarkMode"
:enable-time-picker="false"
:show-alternate-dates="true"
v-model="dateTime"
v-show="mode === 'date'">
</date-time-picker>
+11 -1
View File
@@ -2,7 +2,16 @@ import type { TypeAndName } from '@/core/base.ts';
export enum CalendarType {
Gregorian = 0,
Buddhist = 1
Buddhist = 1,
Chinese = 2
}
export interface ChineseCalendarLocaleData {
readonly numerals: string[];
readonly monthNames: string[];
readonly dayNames: string[];
readonly leapMonthPrefix: string;
readonly solarTermNames: string[];
}
export class CalendarDisplayType implements TypeAndName {
@@ -13,6 +22,7 @@ export class CalendarDisplayType implements TypeAndName {
public static readonly LanguageDefaultType: number = 0;
public static readonly Gregorian = new CalendarDisplayType(1, 'Gregorian', 'Gregorian', CalendarType.Gregorian);
public static readonly Buddhist = new CalendarDisplayType(2, 'Buddhist', 'Buddhist', CalendarType.Buddhist);
public static readonly GregorianWithChinese = new CalendarDisplayType(3, 'GregorianWithChinese', 'Gregorian with Chinese', CalendarType.Gregorian, CalendarType.Chinese);
public static readonly Default = CalendarDisplayType.Gregorian;
+6 -2
View File
@@ -1,5 +1,5 @@
import type { TypeAndName, TypeAndDisplayName } from '@/core/base.ts';
import type { CalendarType } from '@/core/calendar.ts';
import type { CalendarType, ChineseCalendarLocaleData } from '@/core/calendar.ts';
import type { NumeralSystem } from '@/core/numeral.ts';
export interface DateTime {
@@ -12,7 +12,6 @@ export interface DateTime {
getGregorianCalendarMonthDisplayName(options: DateTimeFormatOptions): string;
getGregorianCalendarMonthDisplayShortName(options: DateTimeFormatOptions): string;
getLocalizedCalendarMonth(options: DateTimeFormatOptions): string;
getLocalizedCalendarMonthIndex(options: DateTimeFormatOptions): number;
getLocalizedCalendarMonthDisplayName(options: DateTimeFormatOptions): string;
getLocalizedCalendarMonthDisplayShortName(options: DateTimeFormatOptions): string;
getGregorianCalendarDay(): number;
@@ -37,6 +36,7 @@ export interface DateTimeFormatOptions {
numeralSystem: NumeralSystem;
calendarType: CalendarType;
localeData: DateTimeLocaleData;
chineseCalendarLocaleData: ChineseCalendarLocaleData;
}
export interface DateTimeLocaleData {
@@ -82,6 +82,10 @@ export interface MonthDay {
readonly day: number;
}
export interface CalendarAlternateDate extends YearMonthDay {
readonly displayDate: string;
}
export interface TimeRange {
readonly minTime: number;
readonly maxTime: number;
@@ -0,0 +1,152 @@
import fs from "fs";
import path from 'path';
import { describe, expect, test } from '@jest/globals';
import { DEFAULT_CONTENT } from '@/locales/calendar/chinese/index.ts';
import type { ChineseCalendarLocaleData } from '@/core/calendar.ts';
import {
type ChineseYearMonthDayInfo,
getChineseYearMonthAllDayInfos,
getChineseYearMonthDayInfo
} from '@/lib/calendar/chinese_calendar.ts';
const defaultLocaleData: ChineseCalendarLocaleData = DEFAULT_CONTENT;
const localeData: ChineseCalendarLocaleData = {
numerals: defaultLocaleData.numerals,
monthNames: defaultLocaleData.monthNames,
dayNames: defaultLocaleData.dayNames,
leapMonthPrefix: defaultLocaleData.leapMonthPrefix,
solarTermNames: [
'Moderate Cold',
'Severe Cold',
'Spring Commences',
'Spring Showers',
'Insects Waken',
'Vernal Equinox',
'Bright & Clear',
'Corn Rain',
'Summer Commences',
'Corn Forms',
'Corn on Ear',
'Summer Solstice',
'Moderate Heat',
'Great Heat',
'Autumn Commences',
'End of Heat',
'White Dew',
'Autumnal Equinox',
'Cold Dew',
'Frost',
'Winter Commences',
'Light Snow',
'Heavy Snow',
'Winter Solstice'
]
};
const ordinalSuffix = ['st', 'nd', 'rd'];
describe('getChineseYearMonthAllDayInfos', () => {
const lines: string[] = fs.readFileSync(path.join(__dirname, 'chinese_calendar_all_data.txt'), 'utf8').split('\n');
const allMonthChineseDays: Record<string, string[]> = {};
const allMonthSolarTermNames: Record<string, string[]> = {};
let currentMonthChineseDays: string[] = [];
let currentMonthSolarTermNames: string[] = [];
let currentYear: number = 0;
let currentMonth: number = 0;
for (const line of lines) {
if (!line.trim() || line.startsWith('#')) {
continue;
}
const items = line.split('\t');
const gregorianDate = items[0];
const gregorianDateItems = gregorianDate.split('/');
const gregorianYear = parseInt(gregorianDateItems[0], 10);
const gregorianMonth = parseInt(gregorianDateItems[1], 10);
const chineseDay = items[1];
const solarTermName = items.length > 3 ? items[3] : '';
if (currentYear > 0 && currentMonth > 0 && (gregorianYear !== currentYear || gregorianMonth !== currentMonth)) {
allMonthChineseDays[`${currentYear}-${currentMonth}`] = currentMonthChineseDays;
allMonthSolarTermNames[`${currentYear}-${currentMonth}`] = currentMonthSolarTermNames;
currentMonthChineseDays = [];
currentMonthSolarTermNames = [];
currentYear = gregorianYear;
currentMonth = gregorianMonth;
} else if (currentYear === 0 && currentMonth === 0) {
currentYear = gregorianYear;
currentMonth = gregorianMonth;
}
if (gregorianYear === currentYear && gregorianMonth === currentMonth) {
currentMonthChineseDays.push(chineseDay.toLowerCase());
currentMonthSolarTermNames.push(solarTermName);
}
}
allMonthChineseDays[`${currentYear}-${currentMonth}`] = currentMonthChineseDays;
allMonthSolarTermNames[`${currentYear}-${currentMonth}`] = currentMonthSolarTermNames;
for (const yearMonth in allMonthChineseDays) {
test(`returns correct chinese all dates in month for ${yearMonth}`, () => {
const [yearStr, monthStr] = yearMonth.split('-');
const year = parseInt(yearStr);
const month = parseInt(monthStr);
const expectedChineseMonthOrDays = allMonthChineseDays[yearMonth];
const expectedSolarTermNames = allMonthSolarTermNames[yearMonth];
const actualChineseDates: ChineseYearMonthDayInfo[] | undefined = getChineseYearMonthAllDayInfos({
year: year,
month1base: month
}, localeData);
expect(actualChineseDates).toBeDefined();
if (actualChineseDates) {
for (let i = 0; i < actualChineseDates.length; i++) {
const actualChineseDate = actualChineseDates[i];
const chineseMonthOrDay: string | undefined = actualChineseDate?.day === 1 ? `${actualChineseDate?.month}${ordinalSuffix[actualChineseDate?.month - 1] ?? 'th'} Lunar Month`.toLowerCase() : actualChineseDate?.day.toString();
expect(actualChineseDate).toBeDefined();
expect(chineseMonthOrDay).toBe(expectedChineseMonthOrDays[i]);
expect(actualChineseDate?.solarTermName).toBe(expectedSolarTermNames[i]);
}
}
});
}
});
describe('getChineseYearMonthDayInfo', () => {
const lines: string[] = fs.readFileSync(path.join(__dirname, 'chinese_calendar_all_data.txt'), 'utf8').split('\n');
for (const line of lines) {
if (!line.trim() || line.startsWith('#')) {
continue;
}
const items = line.split('\t');
const gregorianDate = items[0];
const gregorianDateItems = gregorianDate.split('/');
const gregorianYear = parseInt(gregorianDateItems[0]);
const gregorianMonth = parseInt(gregorianDateItems[1]);
const gregorianDay = parseInt(gregorianDateItems[2]);
const expectedChineseMonthOrDay = items[1];
const expectedSolarTermName = items.length > 3 ? items[3] : '';
test(`returns correct chinese date for ${gregorianDate}`, () => {
const actualChineseDate: ChineseYearMonthDayInfo | undefined = getChineseYearMonthDayInfo({
year: gregorianYear,
month: gregorianMonth,
day: gregorianDay
}, localeData);
const actualChineseMonthOrDay: string | undefined = actualChineseDate?.day === 1 ? `${actualChineseDate?.month}${ordinalSuffix[actualChineseDate?.month - 1] ?? 'th'} Lunar Month`.toLowerCase() : actualChineseDate?.day.toString();
expect(actualChineseDate).toBeDefined();
expect(actualChineseMonthOrDay).toBe(expectedChineseMonthOrDay.toLowerCase());
expect(actualChineseDate?.solarTermName).toBe(expectedSolarTermName);
});
}
});
File diff suppressed because it is too large Load Diff
+431
View File
@@ -0,0 +1,431 @@
import {
SUPPORTED_MIN_YEAR,
SUPPORTED_MAX_YEAR,
CHINESE_CALENDAR_FIRST_DAY_GREGORIAN_YEAR,
CHINESE_CALENDAR_FIRST_DAY_GREGORIAN_MONTH,
CHINESE_CALENDAR_FIRST_DAY_GREGORIAN_DAY,
CHINESE_YEAR_DATA,
GREGORIAN_YEAR_CHINESE_SOLAR_TERMS_DATA
} from './chinese_calendar_data.ts';
import type { ChineseCalendarLocaleData } from '@/core/calendar.ts';
import type { Year1BasedMonth, YearMonthDay, CalendarAlternateDate } from '@/core/datetime.ts';
import { getGregorianCalendarYearMonthDays, getDayDifference } from '../datetime.ts';
const CHINESE_CALENDAR_MONTH_COUNT: number = 12;
const CHINESE_CALENDAR_BIG_MONTH_DAYS: ChineseDayCount = 30;
const CHINESE_CALENDAR_SMALL_MONTH_DAYS: ChineseDayCount = 29;
const CHINESE_YEAR_INFOS: ChineseCalendarYearInfo[] = initChineseYearInfos();
interface ChineseYearMonthDay {
readonly year: number;
readonly month: ChineseMonthValue;
readonly day: ChineseDayValue;
readonly isLeapMonth: boolean;
}
interface ChineseCalendarYearInfo {
readonly year: number;
readonly totalDays: number;
readonly firstDayGregorianMonth: number;
readonly firstDayGregorianDay: number;
readonly normalMonthDays: ChineseDayCount[];
readonly leapMonth?: ChineseMonthValue;
readonly leapMonthDays?: ChineseDayCount;
}
export type ChineseMonthValue = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; // 1-12
export type ChineseDayValue = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30; // 1-30
export type ChineseDayCount = 29 | 30; // 1-29 or 1-30
export interface ChineseYearMonthDayInfo extends ChineseYearMonthDay {
readonly gregorianYear: number;
readonly gregorianMonth: number;
readonly gregorianDay: number;
readonly year: number;
readonly month: ChineseMonthValue;
readonly day: ChineseDayValue;
readonly displayYear: string;
readonly displayMonth: string;
readonly displayDay: string;
readonly isLeapMonth: boolean;
readonly solarTermName: string;
}
function initChineseYearInfos(): ChineseCalendarYearInfo[] {
const ret: ChineseCalendarYearInfo[] = [];
const gregorianDate: Date = new Date(CHINESE_CALENDAR_FIRST_DAY_GREGORIAN_YEAR, CHINESE_CALENDAR_FIRST_DAY_GREGORIAN_MONTH - 1, CHINESE_CALENDAR_FIRST_DAY_GREGORIAN_DAY);
for (let year = SUPPORTED_MIN_YEAR; year <= SUPPORTED_MAX_YEAR; year++) {
const yearData = CHINESE_YEAR_DATA[year - SUPPORTED_MIN_YEAR];
if (!yearData) {
return ret;
}
const allNormalMonthBigMonthBits: number = yearData >> 5;
const leapMonth: number = (yearData >> 1) & 0xF;
let normalMonthDays: ChineseDayCount[] = [];
let leapMonthDays: ChineseDayCount | undefined = undefined;
let remainNormalMonthBigMonthBits: number = allNormalMonthBigMonthBits;
let totalDays: number = 0;
for (let i = 12; i >= 1; i--) {
const isBigMonth = (remainNormalMonthBigMonthBits & 0x1) === 1;
remainNormalMonthBigMonthBits = remainNormalMonthBigMonthBits >> 1;
if (isBigMonth) {
normalMonthDays.push(CHINESE_CALENDAR_BIG_MONTH_DAYS);
totalDays += CHINESE_CALENDAR_BIG_MONTH_DAYS;
} else {
normalMonthDays.push(CHINESE_CALENDAR_SMALL_MONTH_DAYS);
totalDays += CHINESE_CALENDAR_SMALL_MONTH_DAYS;
}
}
if (leapMonth > 0) {
leapMonthDays = ((yearData & 0x1) === 1) ? CHINESE_CALENDAR_BIG_MONTH_DAYS : CHINESE_CALENDAR_SMALL_MONTH_DAYS;
totalDays += leapMonthDays;
}
normalMonthDays = normalMonthDays.reverse();
const chineseYearInfo: ChineseCalendarYearInfo = {
year: year,
totalDays: totalDays,
firstDayGregorianMonth: gregorianDate.getMonth() + 1,
firstDayGregorianDay: gregorianDate.getDate(),
normalMonthDays: normalMonthDays,
leapMonth: leapMonth > 0 ? (leapMonth as ChineseMonthValue) : undefined,
leapMonthDays: leapMonthDays
};
gregorianDate.setDate(gregorianDate.getDate() + totalDays);
ret.push(chineseYearInfo);
}
return ret;
}
function getChineseNumber(num: number, localeData: ChineseCalendarLocaleData): string {
if (num < 0) {
return '';
}
const zeroDigitCharCode = '0'.charCodeAt(0);
return num.toString().split('').map(ch => {
const digit = ch.charCodeAt(0) - zeroDigitCharCode;
if (digit < 0 || digit > 9) {
return ch;
} else {
return localeData.numerals[digit];
}
}).join('');
}
function getChineseYearInfo(year: number): ChineseCalendarYearInfo | undefined {
if (year < SUPPORTED_MIN_YEAR || year > SUPPORTED_MAX_YEAR) {
return undefined;
}
return CHINESE_YEAR_INFOS[year - SUPPORTED_MIN_YEAR];
}
function getSolarTermDays(gregorianYear: number, gregorianMonth: number): [number, number, number, number] {
if (gregorianYear < SUPPORTED_MIN_YEAR || gregorianYear > SUPPORTED_MAX_YEAR || gregorianMonth < 1 || gregorianMonth > 12) {
return [0, 0, 0, 0];
}
const yearIndexInSolarTermData = gregorianYear - SUPPORTED_MIN_YEAR;
const solarTerms = GREGORIAN_YEAR_CHINESE_SOLAR_TERMS_DATA[yearIndexInSolarTermData];
if (!solarTerms) {
return [0, 0, 0, 0];
}
const monthIndexInSolarTermData = (gregorianMonth - 1) * 2;
const firstTermDayChar = solarTerms.charAt(monthIndexInSolarTermData);
const secondTermDayChar = solarTerms.charAt(monthIndexInSolarTermData + 1);
let firstTermDay = 0;
let firstTermIndex = 0;
let secondTermDay = 0;
let secondTermIndex = 0;
if (firstTermDayChar) {
firstTermDay = parseInt(firstTermDayChar, 36);
firstTermIndex = monthIndexInSolarTermData;
}
if (secondTermDayChar) {
secondTermDay = parseInt(secondTermDayChar, 36);
secondTermIndex = monthIndexInSolarTermData + 1;
}
return [firstTermDay, firstTermIndex, secondTermDay, secondTermIndex];
}
function getSolarTermName(gregorianDate: YearMonthDay, localeData: ChineseCalendarLocaleData): string {
if (localeData == null || !localeData.solarTermNames) {
return '';
}
const [firstTermDay, firstTermIndex, secondTermDay, secondTermIndex] = getSolarTermDays(gregorianDate.year, gregorianDate.month);
if (firstTermDay > 0 && firstTermDay === gregorianDate.day) {
return localeData.solarTermNames[firstTermIndex];
} else if (secondTermDay > 0 && secondTermDay === gregorianDate.day) {
return localeData.solarTermNames[secondTermIndex];
} else {
return '';
}
}
function getChineseDate(yearMonthDay: YearMonthDay): ChineseYearMonthDay | undefined {
if (yearMonthDay.year < SUPPORTED_MIN_YEAR || yearMonthDay.year > SUPPORTED_MAX_YEAR || yearMonthDay.month < 1 || yearMonthDay.month > 12) {
return undefined;
}
const chineseYearInfo = getChineseYearInfo(yearMonthDay.year);
if (!chineseYearInfo) {
return undefined;
}
let gregorianYear: number = 0;
let chineseMonth: ChineseMonthValue = 1;
let chineseDay: ChineseDayValue = 1;
if (chineseYearInfo.firstDayGregorianMonth < yearMonthDay.month) {
gregorianYear = yearMonthDay.year;
} else if (chineseYearInfo.firstDayGregorianMonth === yearMonthDay.month && chineseYearInfo.firstDayGregorianDay <= yearMonthDay.day) {
gregorianYear = yearMonthDay.year;
} else {
gregorianYear = yearMonthDay.year - 1;
}
const currentChineseYearInfo = getChineseYearInfo(gregorianYear);
if (!currentChineseYearInfo) {
return undefined;
}
if (gregorianYear === yearMonthDay.year && yearMonthDay.month === currentChineseYearInfo.firstDayGregorianMonth && yearMonthDay.day === currentChineseYearInfo.firstDayGregorianDay) {
return {
year: gregorianYear,
month: chineseMonth,
day: chineseDay,
isLeapMonth: currentChineseYearInfo.leapMonth === 1
};
}
const dayDifference: number = getDayDifference({
year: currentChineseYearInfo.year,
month: currentChineseYearInfo.firstDayGregorianMonth,
day: currentChineseYearInfo.firstDayGregorianDay
}, yearMonthDay);
if (dayDifference < 0) {
return undefined;
}
let remainDays: number = dayDifference;
while (remainDays > 0) {
let currentMonthDays: number | undefined = currentChineseYearInfo.normalMonthDays[chineseMonth - 1];
if (!currentMonthDays) {
return undefined;
}
let isLeapMonth: boolean = false;
let skipNormalMonth: boolean = false;
let skipLeapMonth: boolean = false;
if (remainDays >= currentMonthDays) {
remainDays -= currentMonthDays;
skipNormalMonth = true;
} else {
chineseDay += remainDays;
remainDays = 0;
}
if (chineseDay > currentMonthDays) {
chineseDay = currentMonthDays - chineseDay;
}
if (skipNormalMonth && remainDays > 0 && chineseMonth === currentChineseYearInfo.leapMonth) {
currentMonthDays = currentChineseYearInfo.leapMonthDays || 0;
isLeapMonth = true;
if (remainDays >= currentMonthDays) {
remainDays -= currentMonthDays;
skipLeapMonth = true;
} else {
chineseDay += remainDays;
remainDays = 0;
}
}
if (chineseDay > currentMonthDays) {
chineseDay = currentMonthDays - chineseDay;
}
if (skipLeapMonth) {
chineseMonth++;
} else if (skipNormalMonth && !isLeapMonth) {
if (chineseMonth === currentChineseYearInfo.leapMonth) {
isLeapMonth = true;
} else {
chineseMonth++;
}
}
if (chineseMonth > CHINESE_CALENDAR_MONTH_COUNT) {
chineseMonth = chineseMonth - CHINESE_CALENDAR_MONTH_COUNT;
gregorianYear++;
if (gregorianYear > SUPPORTED_MAX_YEAR) {
return undefined;
}
}
if (remainDays === 0) {
return {
year: gregorianYear,
month: chineseMonth as ChineseMonthValue,
day: chineseDay as ChineseDayValue,
isLeapMonth: isLeapMonth
};
}
}
return undefined;
}
function buildChineseYearMonthDayInfo(gregorianDate: YearMonthDay, chineseDate: ChineseYearMonthDay, localeData: ChineseCalendarLocaleData): ChineseYearMonthDayInfo {
const chineseYearMonthDayInfo: ChineseYearMonthDayInfo = {
gregorianYear: gregorianDate.year,
gregorianMonth: gregorianDate.month,
gregorianDay: gregorianDate.day,
year: chineseDate.year,
month: chineseDate.month,
day: chineseDate.day,
displayYear: getChineseNumber(chineseDate.year, localeData),
displayMonth: (chineseDate.isLeapMonth ? localeData.leapMonthPrefix : '') + localeData.monthNames[chineseDate.month - 1],
displayDay: localeData.dayNames[chineseDate.day - 1],
isLeapMonth: chineseDate.isLeapMonth,
solarTermName: getSolarTermName(gregorianDate, localeData)
};
return chineseYearMonthDayInfo;
}
export function getChineseYearMonthAllDayInfos(yearMonth: Year1BasedMonth, localeData: ChineseCalendarLocaleData): ChineseYearMonthDayInfo[] | undefined {
const monthFirstDay: YearMonthDay = {
year: yearMonth.year,
month: yearMonth.month1base,
day: 1
};
const chineseFirstDate: ChineseYearMonthDay | undefined = getChineseDate(monthFirstDay);
if (!chineseFirstDate) {
return undefined;
}
const chineseYearInfo = getChineseYearInfo(chineseFirstDate.year);
if (!chineseYearInfo) {
return undefined;
}
const allDayInfos: ChineseYearMonthDayInfo[] = [];
allDayInfos.push(buildChineseYearMonthDayInfo(monthFirstDay, chineseFirstDate, localeData));
const gregorianDate: Date = new Date(monthFirstDay.year, monthFirstDay.month - 1, monthFirstDay.day);
let chineseYear: number = chineseFirstDate.year;
let chineseMonth: ChineseMonthValue = chineseFirstDate.month;
let chineseDay: ChineseDayValue = chineseFirstDate.day;
let chineseLeapMonth: boolean = chineseFirstDate.isLeapMonth;
let remainDays: number = getGregorianCalendarYearMonthDays(yearMonth) - 1;
while (remainDays > 0) {
const chineseMonthDays: number | undefined = chineseLeapMonth ? chineseYearInfo.leapMonthDays : chineseYearInfo.normalMonthDays[chineseMonth - 1];
if (!chineseMonthDays) {
return undefined;
}
gregorianDate.setDate(gregorianDate.getDate() + 1);
chineseDay++;
remainDays--;
if (chineseDay > chineseMonthDays) {
chineseDay = chineseDay - chineseMonthDays;
if (chineseYearInfo.leapMonth === chineseMonth && !chineseLeapMonth) {
chineseLeapMonth = true;
} else {
chineseMonth++;
if (chineseMonth > CHINESE_CALENDAR_MONTH_COUNT) {
chineseMonth = chineseMonth - CHINESE_CALENDAR_MONTH_COUNT;
chineseYear++;
if (chineseYear > SUPPORTED_MAX_YEAR) {
return undefined;
}
}
}
}
allDayInfos.push(buildChineseYearMonthDayInfo({
year: gregorianDate.getFullYear(),
month: gregorianDate.getMonth() + 1,
day: gregorianDate.getDate()
}, {
year: chineseYear,
month: chineseMonth as ChineseMonthValue,
day: chineseDay as ChineseDayValue,
isLeapMonth: chineseLeapMonth
}, localeData));
}
return allDayInfos;
}
export function getChineseYearMonthDayInfo(yearMonthDay: YearMonthDay, localeData: ChineseCalendarLocaleData): ChineseYearMonthDayInfo | undefined {
const chineseDate: ChineseYearMonthDay | undefined = getChineseDate(yearMonthDay);
if (!chineseDate) {
return undefined;
}
return buildChineseYearMonthDayInfo(yearMonthDay, chineseDate, localeData);
}
export function getChineseCalendarAlternateDisplayDate(chineseDate: ChineseYearMonthDayInfo): CalendarAlternateDate {
let displayDate = chineseDate.displayDay;
if (chineseDate.day === 1) {
displayDate = chineseDate.displayMonth;
}
if (chineseDate.solarTermName) {
displayDate = chineseDate.solarTermName;
}
const alternateDate: CalendarAlternateDate = {
year: chineseDate.gregorianYear,
month: chineseDate.gregorianMonth,
day: chineseDate.gregorianDay,
displayDate: displayDate
};
return alternateDate;
}
+455
View File
@@ -0,0 +1,455 @@
// Chinese Calendar data from Hong Kong Observatory: https://www.hko.gov.hk/tc/gts/time/conversion1_text.htm
// The following bash scripts is about generating compacted Chinese calendar data
/*
#!/bin/bash
# fetch calendar date from Hong Kong Observatory
for i in {1999..2100};
do
wget "https://my.weather.gov.hk/en/gts/time/calendar/text/files/T${i}e.txt";
done
# generate the compacted data
for i in {1999..2100};
do
if ! [ -f "T${i}e.txt" ]; then
echo "Error: file T${i}e.txt not exists";
exit 1;
fi
cat T${i}e.txt;
done | grep -v 'Gregorian-Lunar Calendar Conversion Table' | awk '
function rtrim(s) {
sub(/[ \t]+$/, "", s);
return s;
}
BEGIN {
# constants
FIRST_YEAR = 1999;
LAST_YEAR = 2100;
GREGORIAN_CALENDAR_1999_1_1_CHINESE_YEAR = 1998;
GREGORIAN_CALENDAR_1999_1_1_CHINESE_MONTH = 11;
CHINESE_CALENDAR_MONTH_COUNT = 12;
CHINESE_CALENDAR_BIG_MONTH_DAYS = 30;
CHINESE_CALENDAR_SOLAR_TERMS_COUNT = 24;
SINGLE_QUOTE_CHAR = 39;
split("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", BASE36_CHARS, "");
SOLAR_TERMS_INDEX["Moderate Cold"] = 1;
SOLAR_TERMS_INDEX["Severe Cold"] = 2;
SOLAR_TERMS_INDEX["Spring Commences"] = 3;
SOLAR_TERMS_INDEX["Spring Showers"] = 4;
SOLAR_TERMS_INDEX["Insects Waken"] = 5;
SOLAR_TERMS_INDEX["Vernal Equinox"] = 6;
SOLAR_TERMS_INDEX["Bright & Clear"] = 7;
SOLAR_TERMS_INDEX["Corn Rain"] = 8;
SOLAR_TERMS_INDEX["Summer Commences"] = 9;
SOLAR_TERMS_INDEX["Corn Forms"] = 10;
SOLAR_TERMS_INDEX["Corn on Ear"] = 11;
SOLAR_TERMS_INDEX["Summer Solstice"] = 12;
SOLAR_TERMS_INDEX["Moderate Heat"] = 13;
SOLAR_TERMS_INDEX["Great Heat"] = 14;
SOLAR_TERMS_INDEX["Autumn Commences"] = 15;
SOLAR_TERMS_INDEX["End of Heat"] = 16;
SOLAR_TERMS_INDEX["White Dew"] = 17;
SOLAR_TERMS_INDEX["Autumnal Equinox"] = 18;
SOLAR_TERMS_INDEX["Cold Dew"] = 19;
SOLAR_TERMS_INDEX["Frost"] = 20;
SOLAR_TERMS_INDEX["Winter Commences"] = 21;
SOLAR_TERMS_INDEX["Light Snow"] = 22;
SOLAR_TERMS_INDEX["Heavy Snow"] = 23;
SOLAR_TERMS_INDEX["Winter Solstice"] = 24;
# variables
errorMessage = "";
chineseYear = GREGORIAN_CALENDAR_1999_1_1_CHINESE_YEAR;
chineseMonth = GREGORIAN_CALENDAR_1999_1_1_CHINESE_MONTH;
chineseDay = 0;
chineseLeapMonth = 0;
firstChineseYearGregorianYear = FIRST_YEAR;
firstChineseYearGregorianMonth = 0;
firstChineseYearGregorianDay = 0;
column1StartIndex = 1;
column1Length = 15;
column2StartIndex = 16;
column2Length = 20;
column3StartIndex = 36;
column3Length = 15;
column4StartIndex = 51;
column4Length = 20;
}
{
# check whether the provided data is invalid
if (index($0, "Error: ") == 1 || errorMessage != "") {
errorMessage = $0;
next;
}
# calculate the length of each column from the header line
if (index($0, "Gregorian date") == 1) {
column2StartIndex = index($0, "Lunar date");
column1Length = column2StartIndex - column1StartIndex;
column3StartIndex = index($0, "Day-of-week");
column2Length = column3StartIndex - column2StartIndex;
column4StartIndex = index($0, "Solar terms");
column3Length = column4StartIndex - column3StartIndex;
next;
}
# parse the gregorian date
col1 = rtrim(substr($0, column1StartIndex, column1Length));
col2 = rtrim(substr($0, column2StartIndex, column2Length));
col3 = rtrim(substr($0, column3StartIndex, column3Length));
col4 = rtrim(substr($0, column4StartIndex, column4Length));
split(col1, gregorianDate, "/");
gregorianYear = int(gregorianDate[1]);
gregorianMonth = int(gregorianDate[2]);
gregorianDay = int(gregorianDate[3]);
# parse Chinese day and calculate the Chinese year and month
nextChineseYear = chineseYear;
nextChineseMonth = chineseMonth;
nextChineseDay = chineseDay;
if (index(col2, " Lunar month") > 0) {
nextChineseMonth = substr(col2, 1, (index(col2, " Lunar month") - 3));
nextChineseDay = 1;
if (nextChineseMonth == 1) {
nextChineseYear++;
}
} else if (index(col2, " Lunar Month") > 0) {
nextChineseMonth = substr(col2, 1, (index(col2, " Lunar Month") - 3));
nextChineseDay = 1;
if (nextChineseMonth == 1) {
nextChineseYear++;
}
} else {
nextChineseDay = col2;
}
# store the previous month info
if (nextChineseDay == 1) {
if (chineseLeapMonth == 0) {
chineseMonthDayCountMap[chineseYear][chineseMonth] = chineseDay;
} else {
chineseLeapMonthMap[chineseYear] = chineseMonth;
chineseLeapMonthDayCount[chineseYear] = chineseDay;
}
if (nextChineseMonth == chineseMonth) {
chineseLeapMonth = 1;
} else {
chineseLeapMonth = 0;
}
}
if (nextChineseDay == 1 && nextChineseMonth == 1 && nextChineseYear == FIRST_YEAR && chineseLeapMonth == 0) {
firstChineseYearGregorianMonth = gregorianMonth;
firstChineseYearGregorianDay = gregorianDay;
}
gregorianDayBase36 = BASE36_CHARS[gregorianDay + 1];
solarTermIndex = SOLAR_TERMS_INDEX[col4];
solarTermsMap[gregorianYear][solarTermIndex] = gregorianDayBase36;
chineseYear = nextChineseYear;
chineseMonth = nextChineseMonth;
chineseDay = nextChineseDay;
}
END {
if (errorMessage != "") {
print errorMessage;
exit;
}
# output contants
printf "export const SUPPORTED_MIN_YEAR: number = %s;\n", FIRST_YEAR;
printf "export const SUPPORTED_MAX_YEAR: number = %s;\n", LAST_YEAR;
print "";
printf "export const CHINESE_CALENDAR_FIRST_DAY_GREGORIAN_YEAR: number = %s;\n", firstChineseYearGregorianYear;
printf "export const CHINESE_CALENDAR_FIRST_DAY_GREGORIAN_MONTH: number = %s;\n", firstChineseYearGregorianMonth;
printf "export const CHINESE_CALENDAR_FIRST_DAY_GREGORIAN_DAY: number = %s;\n", firstChineseYearGregorianDay;
print "";
# output compacted data table of chinese years
print "export const CHINESE_YEAR_DATA: number[] = [";
for (year = FIRST_YEAR; year <= LAST_YEAR; year++) {
printf " "
# each element is a hexadecimal representing a 17-bit integer:
# - bits 0-11: month days from month 1 to month 12 (0 means 29 days, 1 means 30 days)
# - bits 12-15: leap month (0 means no leap month, 1-12 means the leap month)
# - bit 16: leap month days (0 means 29 days, 1 means 30 days)
data = 0;
for (month = 1; month <= CHINESE_CALENDAR_MONTH_COUNT; month++) {
dayCount = chineseMonthDayCountMap[year][month];
bigMonth = 0;
if (dayCount == CHINESE_CALENDAR_BIG_MONTH_DAYS) {
bigMonth = 1;
}
data = lshift(data, 1);
data = or(data, bigMonth);
}
leapMonth = chineseLeapMonthMap[year];
if (leapMonth == "") {
leapMonth = 0;
}
data = lshift(data, 4);
data = or(data, leapMonth);
data = lshift(data, 1);
if (leapMonth > 0) {
bigMonth = 0;
if (chineseLeapMonthDayCount[year] == CHINESE_CALENDAR_BIG_MONTH_DAYS) {
bigMonth = 1;
}
data = or(data, bigMonth);
}
printf "0x%x, // %s\n", data, year;
}
print "];";
print "";
# output compacted data table of solar terms
# each element is a string of 24 base-36 (0-9A-Z) characters, each character represents the day in month (1-based) of the corresponding solar term
print "export const GREGORIAN_YEAR_CHINESE_SOLAR_TERMS_DATA: string[] = [";
for (year = FIRST_YEAR; year <= LAST_YEAR; year++) {
printf " %c", SINGLE_QUOTE_CHAR;
for (i = 1; i <= CHINESE_CALENDAR_SOLAR_TERMS_COUNT; i++) {
printf solarTermsMap[year][i];
}
printf "%c, // %s\n", SINGLE_QUOTE_CHAR, year;
}
print "];";
}'
*/
// The following lines are generated by the above bash scripts
export const SUPPORTED_MIN_YEAR: number = 1999;
export const SUPPORTED_MAX_YEAR: number = 2100;
export const CHINESE_CALENDAR_FIRST_DAY_GREGORIAN_YEAR: number = 1999;
export const CHINESE_CALENDAR_FIRST_DAY_GREGORIAN_MONTH: number = 2;
export const CHINESE_CALENDAR_FIRST_DAY_GREGORIAN_DAY: number = 16;
export const CHINESE_YEAR_DATA: number[] = [
0x125c0, // 1999
0x192c0, // 2000
0x1b2a8, // 2001
0x1a940, // 2002
0x1b4a0, // 2003
0xeaa4, // 2004
0xad40, // 2005
0x1576e, // 2006
0x4ba0, // 2007
0x125a0, // 2008
0x1956a, // 2009
0x152a0, // 2010
0x16940, // 2011
0x17548, // 2012
0x15aa0, // 2013
0xabb2, // 2014
0x9740, // 2015
0x14b60, // 2016
0xa2ed, // 2017
0xa560, // 2018
0x15260, // 2019
0xf2a8, // 2020
0xd540, // 2021
0x15aa0, // 2022
0xb6a4, // 2023
0x96c0, // 2024
0x14dcc, // 2025
0x149c0, // 2026
0x1a4c0, // 2027
0x1d4ca, // 2028
0x1aa60, // 2029
0xb540, // 2030
0xed46, // 2031
0x12da0, // 2032
0x95f6, // 2033
0x95a0, // 2034
0x149a0, // 2035
0x1a16d, // 2036
0x1a4a0, // 2037
0x1aa40, // 2038
0x1ba8a, // 2039
0x16b40, // 2040
0xada0, // 2041
0xab64, // 2042
0x9360, // 2043
0x14aee, // 2044
0x14960, // 2045
0x154a0, // 2046
0x164ab, // 2047
0xda40, // 2048
0x15b40, // 2049
0x96c7, // 2050
0x126e0, // 2051
0x93f0, // 2052
0x92e0, // 2053
0xc960, // 2054
0xd14d, // 2055
0x1d4a0, // 2056
0xd540, // 2057
0x14d89, // 2058
0x155c0, // 2059
0x125c0, // 2060
0x1a5c6, // 2061
0x192c0, // 2062
0x1aaae, // 2063
0x1a940, // 2064
0x1b4a0, // 2065
0xbaaa, // 2066
0xad40, // 2067
0x14da0, // 2068
0xaba8, // 2069
0xa5a0, // 2070
0x15370, // 2071
0x152a0, // 2072
0x16940, // 2073
0x16d4c, // 2074
0x15aa0, // 2075
0xab40, // 2076
0x15748, // 2077
0x14b60, // 2078
0xa560, // 2079
0x164e6, // 2080
0xd260, // 2081
0xe66e, // 2082
0xd540, // 2083
0x15aa0, // 2084
0x96ab, // 2085
0x96c0, // 2086
0x14ae0, // 2087
0xa9c8, // 2088
0x1a2c0, // 2089
0x1d2d0, // 2090
0x1aa40, // 2091
0x1b540, // 2092
0xd54d, // 2093
0xada0, // 2094
0x95c0, // 2095
0x153a8, // 2096
0x145a0, // 2097
0x1a2a0, // 2098
0x1e4a4, // 2099
0x1aa40, // 2100
];
export const GREGORIAN_YEAR_CHINESE_SOLAR_TERMS_DATA: string[] = [
'6K4J6L5K6L6M7N8N8N9O8N7M', // 1999
'6L4J5K4K5L5L7M7N7N8N7M7L', // 2000
'5K4I5K5K5L5L7N7N7N8N7M7M', // 2001
'5K4J6L5K6L6L7N8N8N8N7M7M', // 2002
'6K4J6L5K6L6M7N8N8N9O8N7M', // 2003
'6L4J5K4K5L5L7M7N7N8N7M7L', // 2004
'5K4I5K5K5L5L7N7N7N8N7M7M', // 2005
'5K4J6L5K5L6L7N7N8N8N7M7M', // 2006
'6K4J6L5K6L6M7N8N8N9O8N7M', // 2007
'6L4J5K4K5L5L7M7N7M8N7M7L', // 2008
'5K4I5K4K5L5L7N7N7N8N7M7M', // 2009
'5K4J6L5K5L6L7N7N8N8N7M7M', // 2010
'6K4J6L5K6L6M7N8N8N8O8N7M', // 2011
'6L4J5K4K5K5L7M7N7M8N7M7L', // 2012
'5K4I5K4K5L5L7M7N7N8N7M7M', // 2013
'5K4J6L5K5L6L7N7N8N8N7M7M', // 2014
'6K4J6L5K6L6M7N8N8N8O8M7M', // 2015
'6K4J5K4J5K5L7M7N7M8N7M7L', // 2016
'5K3I5K4K5L5L7M7N7N8N7M7M', // 2017
'5K4J5L5K5L6L7N7N8N8N7M7M', // 2018
'5K4J6L5K6L6L7N8N8N8O8M7M', // 2019
'6K4J5K4J5K5L6M7M7M8N7M7L', // 2020
'5K3I5K4K5L5L7M7N7N8N7M7L', // 2021
'5K4J5K5K5L6L7N7N7N8N7M7M', // 2022
'5K4J6L5K6L6L7N8N8N8O8M7M', // 2023
'6K4J5K4J5K5L6M7M7M8N7M6L', // 2024
'5K3I5K4K5L5L7M7N7N8N7M7L', // 2025
'5K4I5K5K5L5L7N7N7N8N7M7M', // 2026
'5K4J6L5K6L6L7N8N8N8N7M7M', // 2027
'6K4J5K4J5K5L6M7M7M8N7M6L', // 2028
'5K3I5K4K5L5L7M7N7N8N7M7L', // 2029
'5K4I5K5K5L5L7N7N7N8N7M7M', // 2030
'5K4J6L5K6L6L7N8N8N8N7M7M', // 2031
'6K4J5K4J5K5L6M7M7M8N7M6L', // 2032
'5K3I5K4K5L5L7M7N7N8N7M7L', // 2033
'5K4I5K5K5L5L7N7N7N8N7M7M', // 2034
'5K4J6L5K5L6L7N7N8N8N7M7M', // 2035
'6K4J5K4J5K5L6M7M7M8N7M6L', // 2036
'5K3I5K4K5L5L7M7N7N8N7M7L', // 2037
'5K4I5K5K5L5L7N7N7N8N7M7M', // 2038
'5K4J6L5K5L6L7N7N8N8N7M7M', // 2039
'6K4J5K4J5K5L6M7M7M8N7M6L', // 2040
'5K3I5K4K5K5L7M7N7M8N7M7L', // 2041
'5K4I5K4K5L5L7N7N7N8N7M7M', // 2042
'5K4J6L5K5L6L7N7N8N8N7M7M', // 2043
'6K4J5K4J5K5L6M7M7M7N7M6L', // 2044
'5K3I5K4J5K5L7M7N7M8N7M7L', // 2045
'5K4I5K4K5L5L7M7N7N8N7M7M', // 2046
'5K4J6L5K5L6L7N7N8N8N7M7M', // 2047
'6K4J5K4J5K5K6M7M7M7N7L6L', // 2048
'5J3I5K4J5K5L6M7M7M8N7M7L', // 2049
'5K3I5K4K5L5L7M7N7N8N7M7M', // 2050
'5K4J5K5K5L6L7N7N7N8N7M7M', // 2051
'5K4J5K4J5K5K6M7M7M7N7L6L', // 2052
'5J3I5K4J5K5L6M7M7M8N7M7L', // 2053
'5K3I5K4K5L5L7M7N7N8N7M7M', // 2054
'5K4J5K5K5L5L7N7N7N8N7M7M', // 2055
'5K4J5K4J5K5K6M7M7M7N7L6L', // 2056
'5J3I5K4J5K5L6M7M7M8N7M6L', // 2057
'5K3I5K4K5L5L7M7N7N8N7M7L', // 2058
'5K4J5K5K5L5L7N7N7N8N7M7M', // 2059
'5K4J5K4J5K5K6M7M7M7M6L6L', // 2060
'5J3I5K4J5K5L6M7M7M8N7M6L', // 2061
'5K3I5K4K5L5L7M7N7N8N7M7L', // 2062
'5K4I5K5K5L5L7N7N7N8N7M7M', // 2063
'5K4J5K4J5K5K6M7M7M7M6L6L', // 2064
'5J3I5K4J5K5L6M7M7M8N7M6L', // 2065
'5K3I5K4K5L5L7M7N7N8N7M7L', // 2066
'5K4I5K5K5L5L7N7N7N8N7M7M', // 2067
'5K4J5K4J4K5K6M6M7M7M6L6L', // 2068
'5J3I5K4J5K5L6M7M7M8N7M6L', // 2069
'5K3I5K4K5K5L7M7N7M8N7M7L', // 2070
'5K4I5K5K5L5L7N7N7N8N7M7M', // 2071
'5K4J5K4J4K5K6M6M7M7M6L6L', // 2072
'5J3I5K4J5K5L6M7M7M7N7M6L', // 2073
'5K3I5K4K5K5L7M7N7M8N7M7L', // 2074
'5K4I5K4K5L5L7M7N7N8N7M7M', // 2075
'5K4J5K4J4K5K6M6M7M7M6L6L', // 2076
'5J3I5K4J5K5L6M7M7M7N7M6L', // 2077
'5K3I5K4J5K5L6M7N7M8N7M7L', // 2078
'5K4I5K4K5L5L7M7N7N8N7M7M', // 2079
'5K4J5K4J4K5K6M6M7M7M6L6L', // 2080
'5J3I5K4J5K5K6M7M7M7N7L6L', // 2081
'5K3I5K4J5K5L6M7M7M8N7M7L', // 2082
'5K3I5K4K5L5L7M7N7N8N7M7M', // 2083
'5K4J4J4J4K5K6M6M6M7M6L6L', // 2084
'4J3I5K4J5K5K6M7M7M7N7L6L', // 2085
'5J3I5K4J5K5L6M7M7M8N7M7L', // 2086
'5K3I5K4K5L5L7M7N7N8N7M7M', // 2087
'5K4J4J4J4K4K6M6M6M7M6L6L', // 2088
'4J3I5K4J5K5K6M7M7M7N7L6L', // 2089
'5J3I5K4J5K5L6M7M7M8N7M6L', // 2090
'5K3I5K4K5L5L7M7N7N8N7M7L', // 2091
'5K4J4J4J4K4K6M6M6M7M6L6L', // 2092
'4J3I5K4J5K5K6M7M7M7M6L6L', // 2093
'5J3I5K4J5K5L6M7M7M8N7M6L', // 2094
'5K3I5K4K5L5L7M7N7N8N7M7L', // 2095
'5K4I4J4J4K4K6M6M6M7M6L6L', // 2096
'4J3I5K4J5K5K6M6M7M7M6L6L', // 2097
'5J3I5K4J5K5L6M7M7M8N7M6L', // 2098
'5K3I5K4K5L5L7M7N7N8N7M7L', // 2099
'5K4I5K5K5L5L7N7N7N8N7M7M', // 2100
];
+49 -9
View File
@@ -2,6 +2,7 @@ import moment from 'moment-timezone';
import { type unitOfTime } from 'moment/moment';
import {
type ChineseCalendarLocaleData,
CalendarType
} from '@/core/calendar.ts';
import {
@@ -49,6 +50,11 @@ import {
ofObject
} from './common.ts';
import {
type ChineseYearMonthDayInfo,
getChineseYearMonthDayInfo
} from '@/lib/calendar/chinese_calendar.ts';
interface DateTimeFormatResult {
value: number | string;
minNumeralLength?: number;
@@ -84,6 +90,7 @@ class MomentDateTime implements DateTime {
};
private readonly instance: moment.Moment;
private chineseDateInfo?: ChineseYearMonthDayInfo | undefined = undefined;
private constructor(instance: moment.Moment) {
this.instance = instance;
@@ -96,6 +103,8 @@ class MomentDateTime implements DateTime {
public getLocalizedCalendarYear(options: DateTimeFormatOptions): string {
if (options && options.calendarType === CalendarType.Buddhist) {
return (this.instance.year() + 543).toString();
} else if (options && options.calendarType === CalendarType.Chinese) {
return this.getChineseDateInfo(options.chineseCalendarLocaleData)?.displayYear ?? '';
}
return this.instance.year().toString();
@@ -136,14 +145,12 @@ class MomentDateTime implements DateTime {
return names[this.getGregorianCalendarMonth() - 1] || '';
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public getLocalizedCalendarMonth(options: DateTimeFormatOptions): string {
return (this.instance.month() + 1).toString();
}
if (options && options.calendarType === CalendarType.Chinese) {
return this.getChineseDateInfo(options.chineseCalendarLocaleData)?.displayMonth ?? '';
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public getLocalizedCalendarMonthIndex(options: DateTimeFormatOptions): number {
return this.instance.month();
return (this.instance.month() + 1).toString();
}
public getLocalizedCalendarMonthDisplayName(options: DateTimeFormatOptions): string {
@@ -151,8 +158,12 @@ class MomentDateTime implements DateTime {
return '';
}
if (options && options.calendarType === CalendarType.Chinese) {
return this.getChineseDateInfo(options.chineseCalendarLocaleData)?.displayMonth ?? '';
}
const names = options.localeData.months();
return names[this.getLocalizedCalendarMonthIndex(options)] || '';
return names[this.instance.month()] || '';
}
public getLocalizedCalendarMonthDisplayShortName(options: DateTimeFormatOptions): string {
@@ -160,16 +171,23 @@ class MomentDateTime implements DateTime {
return '';
}
if (options && options.calendarType === CalendarType.Chinese) {
return this.getChineseDateInfo(options.chineseCalendarLocaleData)?.displayMonth ?? '';
}
const names = options.localeData.monthsShort();
return names[this.getLocalizedCalendarMonthIndex(options)] || '';
return names[this.instance.month()] || '';
}
public getGregorianCalendarDay(): number {
return this.instance.date();
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public getLocalizedCalendarDay(options: DateTimeFormatOptions): string {
if (options && options.calendarType === CalendarType.Chinese) {
return this.getChineseDateInfo(options.chineseCalendarLocaleData)?.displayDay ?? '';
}
return this.instance.date().toString();
}
@@ -307,6 +325,18 @@ class MomentDateTime implements DateTime {
return new MomentDateTime(moment());
}
private getChineseDateInfo(localeData: ChineseCalendarLocaleData): ChineseYearMonthDayInfo | undefined {
if (!this.chineseDateInfo) {
this.chineseDateInfo = getChineseYearMonthDayInfo({
year: this.instance.year(),
month: this.instance.month() + 1,
day: this.instance.date()
}, localeData);
}
return this.chineseDateInfo;
}
static isYearFirstTime(dateTime: MomentDateTime): boolean {
const currentUnixTime = dateTime.instance.clone().set({ millisecond: 0 }).unix();
const expectedUnxTime = dateTime.instance.clone().set({ millisecond: 0 }).startOf('year').unix();
@@ -560,6 +590,10 @@ export function getGregorianCalendarYearAndMonthFromUnixTime(unixTime: number):
return parseDateTimeFromUnixTime(unixTime).getGregorianCalendarYearDashMonth();
}
export function getGregorianCalendarYearMonthDays(yearMonth: Year1BasedMonth): number {
return moment().set({ year: yearMonth.year, month: yearMonth.month1base - 1 }).daysInMonth();
}
export function getAMOrPM(hour: number): string {
return isPM(hour) ? MeridiemIndicator.PM.name : MeridiemIndicator.AM.name;
}
@@ -572,6 +606,12 @@ export function getUnixTimeAfterUnixTime(unixTime: number, amount: number, unit:
return moment.unix(unixTime).add(amount, unit).unix();
}
export function getDayDifference(yearMonthDay1: YearMonthDay, yearMonthDay2: YearMonthDay): number {
const date1 = moment().set({ year: yearMonthDay1.year, month: yearMonthDay1.month - 1, date: yearMonthDay1.day, hour: 0, minute: 0, second: 0, millisecond: 0 });
const date2 = moment().set({ year: yearMonthDay2.year, month: yearMonthDay2.month - 1, date: yearMonthDay2.day, hour: 0, minute: 0, second: 0, millisecond: 0 });
return date2.diff(date1, 'days');
}
export function getTimeDifferenceHoursAndMinutes(timeDifferenceInMinutes: number): TimeDifference {
const offsetHours = Math.trunc(Math.abs(timeDifferenceInMinutes) / 60);
const offsetMinutes = Math.abs(timeDifferenceInMinutes) - offsetHours * 60;
+14
View File
@@ -0,0 +1,14 @@
import zhHans from './zh_Hans.json';
import zhHant from './zh_Hant.json';
type ChineseCalendarLocaleDataKey = 'numerals' | 'monthNames' | 'dayNames' | 'leapMonthPrefix' | 'solarTermNames';
type ChineseCalendarLocaleData = {
[K in ChineseCalendarLocaleDataKey]: K extends 'leapMonthPrefix' ? string : string[];
};
export const DEFAULT_CONTENT: ChineseCalendarLocaleData = zhHans;
export const ALL_LANGUAGES: Record<string, ChineseCalendarLocaleData> = {
'zh-Hans': zhHans,
'zh-Hant': zhHant
}
+88
View File
@@ -0,0 +1,88 @@
{
"numerals": [
"",
"一",
"二",
"三",
"四",
"五",
"六",
"七",
"八",
"九",
"十"
],
"monthNames": [
"正月",
"二月",
"三月",
"四月",
"五月",
"六月",
"七月",
"八月",
"九月",
"十月",
"冬月",
"腊月"
],
"dayNames": [
"初一",
"初二",
"初三",
"初四",
"初五",
"初六",
"初七",
"初八",
"初九",
"初十",
"十一",
"十二",
"十三",
"十四",
"十五",
"十六",
"十七",
"十八",
"十九",
"二十",
"廿一",
"廿二",
"廿三",
"廿四",
"廿五",
"廿六",
"廿七",
"廿八",
"廿九",
"三十"
],
"leapMonthPrefix": "闰",
"solarTermNames": [
"小寒",
"大寒",
"立春",
"雨水",
"惊蛰",
"春分",
"清明",
"谷雨",
"立夏",
"小满",
"芒种",
"夏至",
"小暑",
"大暑",
"立秋",
"处暑",
"白露",
"秋分",
"寒露",
"霜降",
"立冬",
"小雪",
"大雪",
"冬至"
]
}
+88
View File
@@ -0,0 +1,88 @@
{
"numerals": [
"",
"一",
"二",
"三",
"四",
"五",
"六",
"七",
"八",
"九",
"十"
],
"monthNames": [
"正月",
"二月",
"三月",
"四月",
"五月",
"六月",
"七月",
"八月",
"九月",
"十月",
"冬月",
"腊月"
],
"dayNames": [
"初一",
"初二",
"初三",
"初四",
"初五",
"初六",
"初七",
"初八",
"初九",
"初十",
"十一",
"十二",
"十三",
"十四",
"十五",
"十六",
"十七",
"十八",
"十九",
"二十",
"廿一",
"廿二",
"廿三",
"廿四",
"廿五",
"廿六",
"廿七",
"廿八",
"廿九",
"三十"
],
"leapMonthPrefix": "閏",
"solarTermNames": [
"小寒",
"大寒",
"立春",
"雨水",
"驚蟄",
"春分",
"清明",
"穀雨",
"立夏",
"小滿",
"芒種",
"夏至",
"小暑",
"大暑",
"立秋",
"處暑",
"白露",
"秋分",
"寒露",
"霜降",
"立冬",
"小雪",
"大雪",
"冬至"
]
}
+2 -1
View File
@@ -138,7 +138,8 @@
},
"calendar": {
"Gregorian": "Gregorian",
"Buddhist": "Buddhist"
"Buddhist": "Buddhist",
"Gregorian with Chinese": "Gregorian with Chinese"
},
"datetime": {
"AM": {
+2 -1
View File
@@ -138,7 +138,8 @@
},
"calendar": {
"Gregorian": "Gregorian",
"Buddhist": "Buddhist"
"Buddhist": "Buddhist",
"Gregorian with Chinese": "Gregorian with Chinese"
},
"datetime": {
"AM": {
+2 -1
View File
@@ -138,7 +138,8 @@
},
"calendar": {
"Gregorian": "Gregorian",
"Buddhist": "Buddhist"
"Buddhist": "Buddhist",
"Gregorian with Chinese": "Gregorian with Chinese"
},
"datetime": {
"AM": {
+83 -1
View File
@@ -11,11 +11,17 @@ import {
DEFAULT_LANGUAGE
} from '@/locales/index.ts';
import {
ALL_LANGUAGES as CHINESE_CALENDAR_ALL_LANGUAGES,
DEFAULT_CONTENT as CHINESE_CALENDAR_DEFAULT_CONTENT
} from '@/locales/calendar/chinese/index.ts';
import {
TextDirection
} from '@/core/text.ts';
import {
type ChineseCalendarLocaleData,
CalendarType,
CalendarDisplayType,
DateDisplayType
@@ -26,6 +32,9 @@ import {
type DateTimeLocaleData,
type TextualYearMonth,
type TextualYearMonthDay,
type Year1BasedMonth,
type YearMonthDay,
type CalendarAlternateDate,
type DateFormat,
type TimeFormat,
type LocalizedDateTimeFormat,
@@ -178,6 +187,13 @@ import {
isPM
} from '@/lib/datetime.ts';
import {
type ChineseYearMonthDayInfo,
getChineseYearMonthAllDayInfos,
getChineseYearMonthDayInfo,
getChineseCalendarAlternateDisplayDate
} from '@/lib/calendar/chinese_calendar.ts';
import {
appendDigitGroupingSymbolAndDecimalSeparator,
parseAmount,
@@ -452,6 +468,19 @@ export function useI18n() {
return moment.localeData();
}
function getChineseCalendarLocaleData(): ChineseCalendarLocaleData {
const localeData = CHINESE_CALENDAR_ALL_LANGUAGES[locale.value] ?? CHINESE_CALENDAR_DEFAULT_CONTENT;
const chineseCalendarLocaleData: ChineseCalendarLocaleData = {
numerals: localeData['numerals'],
monthNames: localeData['monthNames'],
dayNames: localeData['dayNames'],
leapMonthPrefix: localeData['leapMonthPrefix'],
solarTermNames: localeData['solarTermNames']
};
return chineseCalendarLocaleData;
}
function getAllCurrencyDisplayTypes(numeralSystem: NumeralSystem, decimalSeparator: string): TypeAndDisplayName[] {
const defaultCurrencyDisplayTypeName = t('default.currencyDisplayType');
let defaultCurrencyDisplayType = CurrencyDisplayType.parse(defaultCurrencyDisplayTypeName);
@@ -683,7 +712,8 @@ export function useI18n() {
return {
numeralSystem: numeralSystem,
calendarType: calendarType,
localeData: getDateTimeLocaleData()
localeData: getDateTimeLocaleData(),
chineseCalendarLocaleData: getChineseCalendarLocaleData()
};
}
@@ -1869,6 +1899,56 @@ export function useI18n() {
}
}
function getCalendarAlternateDates(yearMonth: Year1BasedMonth): CalendarAlternateDate[] | undefined {
const calendarDisplayType = getCurrentCalendarDisplayType().secondaryCalendarType;
if (!calendarDisplayType) {
return undefined;
}
if (calendarDisplayType === CalendarType.Chinese) {
const chineseCalendarLocaleData = getChineseCalendarLocaleData();
const chineseDates: ChineseYearMonthDayInfo[] | undefined = getChineseYearMonthAllDayInfos(yearMonth, chineseCalendarLocaleData);
if (!chineseDates) {
return undefined;
}
const ret: CalendarAlternateDate[] = [];
for (let i = 0; i < chineseDates.length; i++) {
const chineseDate = chineseDates[i];
const alternateDate = getChineseCalendarAlternateDisplayDate(chineseDate);
ret.push(alternateDate);
}
return ret;
}
return undefined;
}
function getCalendarAlternateDate(yearMonthDay: YearMonthDay): CalendarAlternateDate | undefined {
const calendarDisplayType = getCurrentCalendarDisplayType().secondaryCalendarType;
if (!calendarDisplayType) {
return undefined;
}
if (calendarDisplayType === CalendarType.Chinese) {
const chineseCalendarLocaleData = getChineseCalendarLocaleData();
const chineseDate: ChineseYearMonthDayInfo | undefined = getChineseYearMonthDayInfo(yearMonthDay, chineseCalendarLocaleData);
if (!chineseDate) {
return undefined;
}
return getChineseCalendarAlternateDisplayDate(chineseDate);
}
return undefined;
}
function getParsedAmountNumber(value: string, numeralSystem?: NumeralSystem): number {
const numberFormatOptions = getNumberFormatOptions({ numeralSystem });
return parseAmount(value, numberFormatOptions);
@@ -2288,6 +2368,8 @@ export function useI18n() {
formatUnixTimeToFiscalYear,
formatYearToFiscalYear,
getTimezoneDifferenceDisplayText,
getCalendarAlternateDates,
getCalendarAlternateDate,
parseAmountFromLocalizedNumerals: (value: string) => getParsedAmountNumber(value),
parseAmountFromWesternArabicNumerals: (value: string) => getParsedAmountNumber(value, NumeralSystem.WesternArabicNumerals),
formatAmountToLocalizedNumerals: (value: number, currencyCode?: string) => getFormattedAmount(value, undefined, undefined, currencyCode),
+2 -1
View File
@@ -138,7 +138,8 @@
},
"calendar": {
"Gregorian": "Gregorian",
"Buddhist": "Buddhist"
"Buddhist": "Buddhist",
"Gregorian with Chinese": "Gregorian with Chinese"
},
"datetime": {
"AM": {
+2 -1
View File
@@ -138,7 +138,8 @@
},
"calendar": {
"Gregorian": "Gregorian",
"Buddhist": "Buddhist"
"Buddhist": "Buddhist",
"Gregorian with Chinese": "Gregorian with Chinese"
},
"datetime": {
"AM": {
+2 -1
View File
@@ -138,7 +138,8 @@
},
"calendar": {
"Gregorian": "Gregorian",
"Buddhist": "Buddhist"
"Buddhist": "Buddhist",
"Gregorian with Chinese": "Gregorian with Chinese"
},
"datetime": {
"AM": {
+2 -1
View File
@@ -138,7 +138,8 @@
},
"calendar": {
"Gregorian": "Gregorian",
"Buddhist": "Buddhist"
"Buddhist": "Buddhist",
"Gregorian with Chinese": "Gregorian with Chinese"
},
"datetime": {
"AM": {
+2 -1
View File
@@ -138,7 +138,8 @@
},
"calendar": {
"Gregorian": "Gregorian",
"Buddhist": "Buddhist"
"Buddhist": "Buddhist",
"Gregorian with Chinese": "Gregorian with Chinese"
},
"datetime": {
"AM": {
+2 -1
View File
@@ -138,7 +138,8 @@
},
"calendar": {
"Gregorian": "Gregorian",
"Buddhist": "Buddhist"
"Buddhist": "Buddhist",
"Gregorian with Chinese": "Gregorian with Chinese"
},
"datetime": {
"AM": {
+2 -1
View File
@@ -138,7 +138,8 @@
},
"calendar": {
"Gregorian": "Gregorian",
"Buddhist": "Buddhist"
"Buddhist": "Buddhist",
"Gregorian with Chinese": "Gregorian with Chinese"
},
"datetime": {
"AM": {
+3 -2
View File
@@ -22,7 +22,7 @@
"currency": "CNY",
"firstDayOfWeek": "Monday",
"fiscalYearFormat": "EndYYYY",
"calendarDisplayType": "Gregorian",
"calendarDisplayType": "GregorianWithChinese",
"dateDisplayType": "Gregorian",
"longDateFormat": "YYYYMMDD",
"shortDateFormat": "YYYYMMDD",
@@ -138,7 +138,8 @@
},
"calendar": {
"Gregorian": "公历",
"Buddhist": "佛教日历"
"Buddhist": "佛教日历",
"Gregorian with Chinese": "公历+农历"
},
"datetime": {
"AM": {
+3 -2
View File
@@ -22,7 +22,7 @@
"currency": "TWD",
"firstDayOfWeek": "Sunday",
"fiscalYearFormat": "EndYYYY",
"calendarDisplayType": "Gregorian",
"calendarDisplayType": "GregorianWithChinese",
"dateDisplayType": "Gregorian",
"longDateFormat": "YYYYMMDD",
"shortDateFormat": "YYYYMMDD",
@@ -138,7 +138,8 @@
},
"calendar": {
"Gregorian": "公曆",
"Buddhist": "佛曆"
"Buddhist": "佛曆",
"Gregorian with Chinese": "公曆+農曆"
},
"datetime": {
"AM": {
+2 -3
View File
@@ -3,7 +3,6 @@ import { defineStore } from 'pinia';
import { useSettingsStore } from './setting.ts';
import { CalendarDisplayType, DateDisplayType } from '@/core/calendar.ts';
import { type WeekDayValue, WeekDay } from '@/core/datetime.ts';
import { FiscalYearStart } from '@/core/fiscalyear.ts';
import type { ApplicationCloudSetting } from '@/core/setting.ts';
@@ -77,12 +76,12 @@ export const useUserStore = defineStore('user', () => {
const currentUserCalendarDisplayType = computed<number>(() => {
const userInfo = currentUserBasicInfo.value || EMPTY_USER_BASIC_INFO;
return isNumber(userInfo.calendarDisplayType) && CalendarDisplayType.valueOf(userInfo.calendarDisplayType) ? userInfo.calendarDisplayType : EMPTY_USER_BASIC_INFO.calendarDisplayType;
return userInfo.calendarDisplayType;
});
const currentUserDateDisplayType = computed<number>(() => {
const userInfo = currentUserBasicInfo.value || EMPTY_USER_BASIC_INFO;
return isNumber(userInfo.dateDisplayType) && DateDisplayType.valueOf(userInfo.dateDisplayType) ? userInfo.dateDisplayType : EMPTY_USER_BASIC_INFO.dateDisplayType;
return userInfo.dateDisplayType;
});
const currentUserLongDateFormat = computed<number>(() => {
+3
View File
@@ -65,6 +65,7 @@
--ebk-numpad-value-large-font-size: 24px;
--ebk-numpad-normal-button-font-size: 28px;
--ebk-numpad-confirm-button-font-size: 20px;
--ebk-datetime-picker-alternate-date-font-size: 12px;
--ebk-amount-small-font-size: 32px;
--ebk-amount-normal-font-size: 36px;
--ebk-amount-large-font-size: 40px;
@@ -78,8 +79,10 @@
--ebk-transaction-date-width: 25px;
--ebk-transaction-day-font-size: 16px;
--ebk-transaction-day-of-week-font-size: 12px;
--ebk-transaction-calendar-alternate-date-font-size: 11px;
--ebk-transaction-calendar-amount-font-size: 11px;
--ebk-transaction-calendar-daily-amounts-height: 65px;
--ebk-transaction-calendar-with-alternate-date-daily-amounts-height: 75px;
--ebk-transaction-account-arrow-font-size: 12px;
--ebk-transaction-account-arrow-margin-top: -1px;
--ebk-transaction-tag-icon-font-size: 22px;
+3
View File
@@ -65,6 +65,7 @@
--ebk-numpad-value-large-font-size: 25px;
--ebk-numpad-normal-button-font-size: 29px;
--ebk-numpad-confirm-button-font-size: 21px;
--ebk-datetime-picker-alternate-date-font-size: 13px;
--ebk-amount-small-font-size: 32px;
--ebk-amount-normal-font-size: 36px;
--ebk-amount-large-font-size: 40px;
@@ -78,8 +79,10 @@
--ebk-transaction-date-width: 28px;
--ebk-transaction-day-font-size: 17px;
--ebk-transaction-day-of-week-font-size: 13px;
--ebk-transaction-calendar-alternate-date-font-size: 13px;
--ebk-transaction-calendar-amount-font-size: 13px;
--ebk-transaction-calendar-daily-amounts-height: 65px;
--ebk-transaction-calendar-with-alternate-date-daily-amounts-height: 80px;
--ebk-transaction-account-arrow-font-size: 13px;
--ebk-transaction-account-arrow-margin-top: -2px;
--ebk-transaction-tag-icon-font-size: 24px;
+3
View File
@@ -65,6 +65,7 @@
--ebk-numpad-value-large-font-size: 24px;
--ebk-numpad-normal-button-font-size: 28px;
--ebk-numpad-confirm-button-font-size: 20px;
--ebk-datetime-picker-alternate-date-font-size: 11px;
--ebk-amount-small-font-size: 32px;
--ebk-amount-normal-font-size: 36px;
--ebk-amount-large-font-size: 40px;
@@ -78,8 +79,10 @@
--ebk-transaction-date-width: 25px;
--ebk-transaction-day-font-size: 16px;
--ebk-transaction-day-of-week-font-size: 12px;
--ebk-transaction-calendar-alternate-date-font-size: 10px;
--ebk-transaction-calendar-amount-font-size: 10px;
--ebk-transaction-calendar-daily-amounts-height: 60px;
--ebk-transaction-calendar-with-alternate-date-daily-amounts-height: 70px;
--ebk-transaction-account-arrow-font-size: 12px;
--ebk-transaction-account-arrow-margin-top: -1px;
--ebk-transaction-tag-icon-font-size: 20px;
+3
View File
@@ -65,6 +65,7 @@
--ebk-numpad-value-large-font-size: 26px;
--ebk-numpad-normal-button-font-size: 30px;
--ebk-numpad-confirm-button-font-size: 22px;
--ebk-datetime-picker-alternate-date-font-size: 14px;
--ebk-amount-small-font-size: 32px;
--ebk-amount-normal-font-size: 36px;
--ebk-amount-large-font-size: 40px;
@@ -78,8 +79,10 @@
--ebk-transaction-date-width: 30px;
--ebk-transaction-day-font-size: 18px;
--ebk-transaction-day-of-week-font-size: 14px;
--ebk-transaction-calendar-alternate-date-font-size: 14px;
--ebk-transaction-calendar-amount-font-size: 14px;
--ebk-transaction-calendar-daily-amounts-height: 70px;
--ebk-transaction-calendar-with-alternate-date-daily-amounts-height: 90px;
--ebk-transaction-account-arrow-font-size: 14px;
--ebk-transaction-account-arrow-margin-top: -2px;
--ebk-transaction-tag-icon-font-size: 26px;
@@ -65,6 +65,7 @@
--ebk-numpad-value-large-font-size: 30px;
--ebk-numpad-normal-button-font-size: 32px;
--ebk-numpad-confirm-button-font-size: 22px;
--ebk-datetime-picker-alternate-date-font-size: 15px;
--ebk-amount-small-font-size: 32px;
--ebk-amount-normal-font-size: 36px;
--ebk-amount-large-font-size: 40px;
@@ -78,8 +79,10 @@
--ebk-transaction-date-width: 32px;
--ebk-transaction-day-font-size: 20px;
--ebk-transaction-day-of-week-font-size: 15px;
--ebk-transaction-calendar-alternate-date-font-size: 15px;
--ebk-transaction-calendar-amount-font-size: 15px;
--ebk-transaction-calendar-daily-amounts-height: 75px;
--ebk-transaction-calendar-with-alternate-date-daily-amounts-height: 100px;
--ebk-transaction-account-arrow-font-size: 15px;
--ebk-transaction-account-arrow-margin-top: -3px;
--ebk-transaction-tag-icon-font-size: 28px;
@@ -65,6 +65,7 @@
--ebk-numpad-value-large-font-size: 30px;
--ebk-numpad-normal-button-font-size: 32px;
--ebk-numpad-confirm-button-font-size: 24px;
--ebk-datetime-picker-alternate-date-font-size: 17px;
--ebk-amount-small-font-size: 34px;
--ebk-amount-normal-font-size: 38px;
--ebk-amount-large-font-size: 42px;
@@ -78,8 +79,10 @@
--ebk-transaction-date-width: 36px;
--ebk-transaction-day-font-size: 22px;
--ebk-transaction-day-of-week-font-size: 17px;
--ebk-transaction-calendar-alternate-date-font-size: 17px;
--ebk-transaction-calendar-amount-font-size: 17px;
--ebk-transaction-calendar-daily-amounts-height: 80px;
--ebk-transaction-calendar-with-alternate-date-daily-amounts-height: 105px;
--ebk-transaction-account-arrow-font-size: 17px;
--ebk-transaction-account-arrow-margin-top: -4px;
--ebk-transaction-tag-icon-font-size: 30px;
@@ -65,6 +65,7 @@
--ebk-numpad-value-large-font-size: 30px;
--ebk-numpad-normal-button-font-size: 32px;
--ebk-numpad-confirm-button-font-size: 26px;
--ebk-datetime-picker-alternate-date-font-size: 19px;
--ebk-amount-small-font-size: 34px;
--ebk-amount-normal-font-size: 40px;
--ebk-amount-large-font-size: 44px;
@@ -78,8 +79,10 @@
--ebk-transaction-date-width: 40px;
--ebk-transaction-day-font-size: 24px;
--ebk-transaction-day-of-week-font-size: 19px;
--ebk-transaction-calendar-alternate-date-font-size: 19px;
--ebk-transaction-calendar-amount-font-size: 19px;
--ebk-transaction-calendar-daily-amounts-height: 85px;
--ebk-transaction-calendar-with-alternate-date-daily-amounts-height: 115px;
--ebk-transaction-account-arrow-font-size: 19px;
--ebk-transaction-account-arrow-margin-top: -4px;
--ebk-transaction-tag-icon-font-size: 32px;
+4
View File
@@ -270,6 +270,10 @@ html[dir="rtl"] i.icon.icon-with-direction {
transform: scaleX(-1);
}
.datetime-picker-alternate-date {
font-size: var(--ebk-datetime-picker-alternate-date-font-size);
}
/** Replacing the default style of @vuepic/vue-datepicker **/
.dp__theme_light {
--dp-primary-color: #c67e48;
@@ -1880,7 +1880,19 @@ init(props);
--dp-primary-text-color: rgb(var(--v-theme-primary));
}
.transaction-calendar-container .dp__main.transaction-calendar-with-alternate-date .dp__calendar .dp__calendar_row {
--dp-cell-size: 100px;
}
.transaction-calendar-container .dp__main .dp__calendar .dp__calendar_row > .dp__calendar_item {
overflow: hidden;
}
.transaction-calendar-container .dp__main .dp__calendar .dp__calendar_row > .dp__calendar_item .transaction-calendar-daily-amounts > span.transaction-calendar-alternate-date {
font-size: 0.9rem;
}
.transaction-calendar-container .dp__main .dp__calendar .dp__calendar_row > .dp__calendar_item .transaction-calendar-daily-amounts > span.transaction-calendar-daily-amount {
font-size: 0.95rem;
}
</style>
@@ -1632,6 +1632,10 @@ html[dir="rtl"] .list.transaction-info-list li.transaction-info .transaction-foo
--dp-primary-text-color: var(--f7-theme-color);
}
.transaction-calendar-container .dp__main.transaction-calendar-with-alternate-date .dp__calendar .dp__calendar_row {
--dp-cell-size: var(--ebk-transaction-calendar-with-alternate-date-daily-amounts-height);
}
.transaction-calendar-container .dp__main .dp__calendar .dp__calendar_row > .dp__calendar_item .transaction-calendar-daily-amounts {
width: 100%;
height: 100%;
@@ -1673,6 +1677,10 @@ html[dir="rtl"] .list.transaction-info-list li.transaction-info .transaction-foo
white-space: nowrap;
}
.transaction-calendar-container .dp__main .dp__calendar .dp__calendar_row > .dp__calendar_item .transaction-calendar-daily-amounts > span.transaction-calendar-alternate-date {
font-size: var(--ebk-transaction-calendar-alternate-date-font-size);
}
.transaction-calendar-container .dp__main .dp__calendar .dp__calendar_row > .dp__calendar_item .transaction-calendar-daily-amounts > span.transaction-calendar-daily-amount {
font-size: var(--ebk-transaction-calendar-amount-font-size);
}