1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include <CoreFoundation/CoreFoundation.h>
7 #include "nsIServiceManager.h"
8 #include "nsDateTimeFormatMac.h"
9 #include <CoreFoundation/CFDateFormatter.h>
10 #include "nsIComponentManager.h"
11 #include "nsILocaleService.h"
12 #include "nsCRT.h"
13 #include "plstr.h"
14 #include "nsUnicharUtils.h"
15 #include "nsTArray.h"
16 
17 
NS_IMPL_ISUPPORTS(nsDateTimeFormatMac,nsIDateTimeFormat)18 NS_IMPL_ISUPPORTS(nsDateTimeFormatMac, nsIDateTimeFormat)
19 
20 nsresult nsDateTimeFormatMac::Initialize(nsILocale* locale)
21 {
22   nsAutoString localeStr;
23   nsAutoString category(NS_LITERAL_STRING("NSILOCALE_TIME"));
24   nsresult res;
25 
26   // use cached info if match with stored locale
27   if (nullptr == locale) {
28     if (!mLocale.IsEmpty() &&
29         mLocale.Equals(mAppLocale, nsCaseInsensitiveStringComparator())) {
30       return NS_OK;
31     }
32   }
33   else {
34     res = locale->GetCategory(category, localeStr);
35     if (NS_SUCCEEDED(res) && !localeStr.IsEmpty()) {
36       if (!mLocale.IsEmpty() &&
37           mLocale.Equals(localeStr,
38                          nsCaseInsensitiveStringComparator())) {
39         return NS_OK;
40       }
41     }
42   }
43 
44   // get application locale
45   nsCOMPtr<nsILocaleService> localeService =
46            do_GetService(NS_LOCALESERVICE_CONTRACTID, &res);
47   if (NS_SUCCEEDED(res)) {
48     nsCOMPtr<nsILocale> appLocale;
49     res = localeService->GetApplicationLocale(getter_AddRefs(appLocale));
50     if (NS_SUCCEEDED(res)) {
51       res = appLocale->GetCategory(category, localeStr);
52       if (NS_SUCCEEDED(res) && !localeStr.IsEmpty()) {
53         mAppLocale = localeStr; // cache app locale name
54       }
55     }
56   }
57 
58   // use app default if no locale specified
59   if (nullptr == locale) {
60     mUseDefaultLocale = true;
61   }
62   else {
63     mUseDefaultLocale = false;
64     res = locale->GetCategory(category, localeStr);
65   }
66 
67   if (NS_SUCCEEDED(res) && !localeStr.IsEmpty()) {
68     mLocale.Assign(localeStr); // cache locale name
69   }
70 
71   return res;
72 }
73 
74 // performs a locale sensitive date formatting operation on the time_t parameter
FormatTime(nsILocale * locale,const nsDateFormatSelector dateFormatSelector,const nsTimeFormatSelector timeFormatSelector,const time_t timetTime,nsAString & stringOut)75 nsresult nsDateTimeFormatMac::FormatTime(nsILocale* locale,
76                                       const nsDateFormatSelector  dateFormatSelector,
77                                       const nsTimeFormatSelector timeFormatSelector,
78                                       const time_t  timetTime,
79                                       nsAString& stringOut)
80 {
81   struct tm tmTime;
82   return FormatTMTime(locale, dateFormatSelector, timeFormatSelector, localtime_r(&timetTime, &tmTime), stringOut);
83 }
84 
85 // performs a locale sensitive date formatting operation on the struct tm parameter
FormatTMTime(nsILocale * locale,const nsDateFormatSelector dateFormatSelector,const nsTimeFormatSelector timeFormatSelector,const struct tm * tmTime,nsAString & stringOut)86 nsresult nsDateTimeFormatMac::FormatTMTime(nsILocale* locale,
87                                            const nsDateFormatSelector  dateFormatSelector,
88                                            const nsTimeFormatSelector timeFormatSelector,
89                                            const struct tm*  tmTime,
90                                            nsAString& stringOut)
91 {
92   nsresult res = NS_OK;
93 
94   // set up locale data
95   (void) Initialize(locale);
96 
97   // return, nothing to format
98   if (dateFormatSelector == kDateFormatNone && timeFormatSelector == kTimeFormatNone) {
99     stringOut.Truncate();
100     return NS_OK;
101   }
102 
103   NS_ASSERTION(tmTime->tm_mon >= 0, "tm is not set correctly");
104   NS_ASSERTION(tmTime->tm_mday >= 1, "tm is not set correctly");
105   NS_ASSERTION(tmTime->tm_hour >= 0, "tm is not set correctly");
106   NS_ASSERTION(tmTime->tm_min >= 0, "tm is not set correctly");
107   NS_ASSERTION(tmTime->tm_sec >= 0, "tm is not set correctly");
108   NS_ASSERTION(tmTime->tm_wday >= 0, "tm is not set correctly");
109 
110   // Got the locale for the formatter:
111   CFLocaleRef formatterLocale;
112   if (!locale) {
113     formatterLocale = CFLocaleCopyCurrent();
114   } else {
115     CFStringRef localeStr = CFStringCreateWithCharacters(nullptr,
116                                                          reinterpret_cast<const UniChar*>(mLocale.get()),
117                                                          mLocale.Length());
118     formatterLocale = CFLocaleCreate(nullptr, localeStr);
119     CFRelease(localeStr);
120   }
121 
122   // Get the date style for the formatter:
123   CFDateFormatterStyle dateStyle;
124   switch (dateFormatSelector) {
125     case kDateFormatLong:
126       dateStyle = kCFDateFormatterLongStyle;
127       break;
128     case kDateFormatShort:
129       dateStyle = kCFDateFormatterShortStyle;
130       break;
131     case kDateFormatYearMonth:
132     case kDateFormatWeekday:
133       dateStyle = kCFDateFormatterNoStyle; // formats handled below
134       break;
135     case kDateFormatNone:
136       dateStyle = kCFDateFormatterNoStyle;
137       break;
138     default:
139       NS_ERROR("Unknown nsDateFormatSelector");
140       res = NS_ERROR_FAILURE;
141       dateStyle = kCFDateFormatterNoStyle;
142   }
143 
144   // Get the time style for the formatter:
145   CFDateFormatterStyle timeStyle;
146   switch (timeFormatSelector) {
147     case kTimeFormatSeconds:
148     case kTimeFormatSecondsForce24Hour: // 24 hour part fixed below
149       timeStyle = kCFDateFormatterMediumStyle;
150       break;
151     case kTimeFormatNoSeconds:
152     case kTimeFormatNoSecondsForce24Hour: // 24 hour part fixed below
153       timeStyle = kCFDateFormatterShortStyle;
154       break;
155     case kTimeFormatNone:
156       timeStyle = kCFDateFormatterNoStyle;
157       break;
158     default:
159       NS_ERROR("Unknown nsTimeFormatSelector");
160       res = NS_ERROR_FAILURE;
161       timeStyle = kCFDateFormatterNoStyle;
162   }
163 
164   // Create the formatter and fix up its formatting as necessary:
165   CFDateFormatterRef formatter =
166     CFDateFormatterCreate(nullptr, formatterLocale, dateStyle, timeStyle);
167 
168   CFRelease(formatterLocale);
169 
170   if (dateFormatSelector == kDateFormatYearMonth ||
171       dateFormatSelector == kDateFormatWeekday) {
172     CFStringRef dateFormat =
173       dateFormatSelector == kDateFormatYearMonth ? CFSTR("yyyy/MM ") : CFSTR("EEE ");
174 
175     CFStringRef oldFormat = CFDateFormatterGetFormat(formatter);
176     CFMutableStringRef newFormat = CFStringCreateMutableCopy(nullptr, 0, oldFormat);
177     CFStringInsert(newFormat, 0, dateFormat);
178     CFDateFormatterSetFormat(formatter, newFormat);
179     CFRelease(newFormat); // note we don't own oldFormat
180   }
181 
182   if (timeFormatSelector == kTimeFormatSecondsForce24Hour ||
183       timeFormatSelector == kTimeFormatNoSecondsForce24Hour) {
184     // Replace "h" with "H", and remove "a":
185     CFStringRef oldFormat = CFDateFormatterGetFormat(formatter);
186     CFMutableStringRef newFormat = CFStringCreateMutableCopy(nullptr, 0, oldFormat);
187     CFIndex replaceCount = CFStringFindAndReplace(newFormat,
188                                                   CFSTR("h"), CFSTR("H"),
189                                                   CFRangeMake(0, CFStringGetLength(newFormat)),
190                                                   0);
191     NS_ASSERTION(replaceCount <= 2, "Unexpected number of \"h\" occurrences");
192     replaceCount = CFStringFindAndReplace(newFormat,
193                                           CFSTR("a"), CFSTR(""),
194                                           CFRangeMake(0, CFStringGetLength(newFormat)),
195                                           0);
196     NS_ASSERTION(replaceCount <= 1, "Unexpected number of \"a\" occurrences");
197     CFDateFormatterSetFormat(formatter, newFormat);
198     CFRelease(newFormat); // note we don't own oldFormat
199   }
200 
201   // Now get the formatted date:
202   CFGregorianDate date;
203   date.second = tmTime->tm_sec;
204   date.minute = tmTime->tm_min;
205   date.hour = tmTime->tm_hour;
206   date.day = tmTime->tm_mday;      // Mac is 1-based, tm is 1-based
207   date.month = tmTime->tm_mon + 1; // Mac is 1-based, tm is 0-based
208   date.year = tmTime->tm_year + 1900;
209 
210   CFTimeZoneRef timeZone = CFTimeZoneCopySystem(); // tmTime is in local time
211   CFAbsoluteTime absTime = CFGregorianDateGetAbsoluteTime(date, timeZone);
212   CFRelease(timeZone);
213 
214   CFStringRef formattedDate = CFDateFormatterCreateStringWithAbsoluteTime(nullptr,
215                                                                           formatter,
216                                                                           absTime);
217 
218   CFIndex stringLen = CFStringGetLength(formattedDate);
219 
220   AutoTArray<UniChar, 256> stringBuffer;
221   stringBuffer.SetLength(stringLen + 1);
222   CFStringGetCharacters(formattedDate, CFRangeMake(0, stringLen), stringBuffer.Elements());
223   stringOut.Assign(reinterpret_cast<char16_t*>(stringBuffer.Elements()), stringLen);
224 
225   CFRelease(formattedDate);
226   CFRelease(formatter);
227 
228   return res;
229 }
230 
231 // performs a locale sensitive date formatting operation on the PRTime parameter
FormatPRTime(nsILocale * locale,const nsDateFormatSelector dateFormatSelector,const nsTimeFormatSelector timeFormatSelector,const PRTime prTime,nsAString & stringOut)232 nsresult nsDateTimeFormatMac::FormatPRTime(nsILocale* locale,
233                                            const nsDateFormatSelector  dateFormatSelector,
234                                            const nsTimeFormatSelector timeFormatSelector,
235                                            const PRTime  prTime,
236                                            nsAString& stringOut)
237 {
238   PRExplodedTime explodedTime;
239   PR_ExplodeTime(prTime, PR_LocalTimeParameters, &explodedTime);
240 
241   return FormatPRExplodedTime(locale, dateFormatSelector, timeFormatSelector, &explodedTime, stringOut);
242 }
243 
244 // performs a locale sensitive date formatting operation on the PRExplodedTime parameter
FormatPRExplodedTime(nsILocale * locale,const nsDateFormatSelector dateFormatSelector,const nsTimeFormatSelector timeFormatSelector,const PRExplodedTime * explodedTime,nsAString & stringOut)245 nsresult nsDateTimeFormatMac::FormatPRExplodedTime(nsILocale* locale,
246                                                    const nsDateFormatSelector  dateFormatSelector,
247                                                    const nsTimeFormatSelector timeFormatSelector,
248                                                    const PRExplodedTime*  explodedTime,
249                                                    nsAString& stringOut)
250 {
251   struct tm  tmTime;
252   memset( &tmTime, 0, sizeof(tmTime) );
253 
254   tmTime.tm_yday = explodedTime->tm_yday;
255   tmTime.tm_wday = explodedTime->tm_wday;
256   tmTime.tm_year = explodedTime->tm_year;
257   tmTime.tm_year -= 1900;
258   tmTime.tm_mon = explodedTime->tm_month;
259   tmTime.tm_mday = explodedTime->tm_mday;
260   tmTime.tm_hour = explodedTime->tm_hour;
261   tmTime.tm_min = explodedTime->tm_min;
262   tmTime.tm_sec = explodedTime->tm_sec;
263 
264   return FormatTMTime(locale, dateFormatSelector, timeFormatSelector, &tmTime, stringOut);
265 }
266 
267