From 1e4ab34cd7fa368bfb8cb7f9a25c984e004e1344 Mon Sep 17 00:00:00 2001 From: Akshay Agarwal Date: Thu, 24 Aug 2017 12:47:00 +0530 Subject: [PATCH] Implement a formatter for relative dates. Signed-off-by: Adrien Destugues It can give results such as "in 2 hours", "2 days ago", etc. This is different from BDurationFormat which will just say "1 hour, 2 minute and 36 seconds" --- headers/os/locale/RelativeDateTimeFormat.h | 44 ++++++ src/kits/locale/Jamfile | 1 + src/kits/locale/RelativeDateTimeFormat.cpp | 163 +++++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 headers/os/locale/RelativeDateTimeFormat.h create mode 100644 src/kits/locale/RelativeDateTimeFormat.cpp diff --git a/headers/os/locale/RelativeDateTimeFormat.h b/headers/os/locale/RelativeDateTimeFormat.h new file mode 100644 index 0000000000..dce4866542 --- /dev/null +++ b/headers/os/locale/RelativeDateTimeFormat.h @@ -0,0 +1,44 @@ +/* + * Copyright 2017, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Akshay Agarwal + */ +#ifndef _B_RELATIVE_DATE_TIME_FORMAT_H_ +#define _B_RELATIVE_DATE_TIME_FORMAT_H_ + + +#include +#include + + +class BString; + +#ifndef U_ICU_NAMESPACE + #define U_ICU_NAMESPACE icu +#endif +namespace U_ICU_NAMESPACE { + class GregorianCalendar; + class RelativeDateTimeFormatter; +} + + +class BRelativeDateTimeFormat: public BFormat { + typedef BFormat Inherited; +public: + BRelativeDateTimeFormat(); + BRelativeDateTimeFormat(const BLanguage& language, + const BFormattingConventions& conventions); + BRelativeDateTimeFormat(const BRelativeDateTimeFormat& other); + virtual ~BRelativeDateTimeFormat(); + + status_t Format(BString& string, const time_t timeValue) const; + +private: + U_ICU_NAMESPACE::RelativeDateTimeFormatter* fFormatter; + U_ICU_NAMESPACE::GregorianCalendar* fCalendar; +}; + + +#endif // _B_RELATIVE_DATE_TIME_FORMAT_H_ diff --git a/src/kits/locale/Jamfile b/src/kits/locale/Jamfile index 73b764621c..057687f556 100644 --- a/src/kits/locale/Jamfile +++ b/src/kits/locale/Jamfile @@ -34,6 +34,7 @@ local sources = TimeUnitFormat.cpp Format.cpp # Used by some of the above. UnicodeChar.cpp # Already used in FirstBootPrompt. + RelativeDateTimeFormat.cpp ; local architectureObject ; diff --git a/src/kits/locale/RelativeDateTimeFormat.cpp b/src/kits/locale/RelativeDateTimeFormat.cpp new file mode 100644 index 0000000000..6b43885e77 --- /dev/null +++ b/src/kits/locale/RelativeDateTimeFormat.cpp @@ -0,0 +1,163 @@ +/* + * Copyright 2017, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Akshay Agarwal + */ + + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + + +static const URelativeDateTimeUnit kTimeUnitToRelativeDateTime[] = { + UDAT_REL_UNIT_YEAR, + UDAT_REL_UNIT_MONTH, + UDAT_REL_UNIT_WEEK, + UDAT_REL_UNIT_DAY, + UDAT_REL_UNIT_HOUR, + UDAT_REL_UNIT_MINUTE, + UDAT_REL_UNIT_SECOND, +}; + + +static const UCalendarDateFields kTimeUnitToICUDateField[] = { + UCAL_YEAR, + UCAL_MONTH, + UCAL_WEEK_OF_MONTH, + UCAL_DAY_OF_WEEK, + UCAL_HOUR_OF_DAY, + UCAL_MINUTE, + UCAL_SECOND, +}; + + +BRelativeDateTimeFormat::BRelativeDateTimeFormat() + : Inherited() +{ + Locale icuLocale(fLanguage.Code()); + UErrorCode icuStatus = U_ZERO_ERROR; + + fFormatter = new RelativeDateTimeFormatter(icuLocale, icuStatus); + if (fFormatter == NULL) { + fInitStatus = B_NO_MEMORY; + return; + } + + fCalendar = new GregorianCalendar(icuStatus); + if (fCalendar == NULL) { + fInitStatus = B_NO_MEMORY; + return; + } + + if (!U_SUCCESS(icuStatus)) + fInitStatus = B_ERROR; +} + + +BRelativeDateTimeFormat::BRelativeDateTimeFormat(const BLanguage& language, + const BFormattingConventions& conventions) + : Inherited(language, conventions) +{ + Locale icuLocale(fLanguage.Code()); + UErrorCode icuStatus = U_ZERO_ERROR; + + fFormatter = new RelativeDateTimeFormatter(icuLocale, icuStatus); + if (fFormatter == NULL) { + fInitStatus = B_NO_MEMORY; + return; + } + + fCalendar = new GregorianCalendar(icuStatus); + if (fCalendar == NULL) { + fInitStatus = B_NO_MEMORY; + return; + } + + if (!U_SUCCESS(icuStatus)) + fInitStatus = B_ERROR; +} + + +BRelativeDateTimeFormat::BRelativeDateTimeFormat(const BRelativeDateTimeFormat& other) + : Inherited(other), + fFormatter(other.fFormatter != NULL + ? new RelativeDateTimeFormatter(*other.fFormatter) : NULL), + fCalendar(other.fCalendar != NULL + ? new GregorianCalendar(*other.fCalendar) : NULL) +{ + if ((fFormatter == NULL && other.fFormatter != NULL) + || (fCalendar == NULL && other.fCalendar != NULL)) + fInitStatus = B_NO_MEMORY; +} + + +BRelativeDateTimeFormat::~BRelativeDateTimeFormat() +{ + delete fFormatter; + delete fCalendar; +} + + +status_t +BRelativeDateTimeFormat::Format(BString& string, + const time_t timeValue) const +{ + if (fFormatter == NULL) + return B_NO_INIT; + + time_t currentTime = time(NULL); + + UErrorCode icuStatus = U_ZERO_ERROR; + fCalendar->setTime((UDate)currentTime * 1000, icuStatus); + if (!U_SUCCESS(icuStatus)) + return B_ERROR; + + UDate UTimeValue = (UDate)timeValue * 1000; + + int delta = 0; + int offset = 1; + URelativeDateTimeUnit unit = UDAT_REL_UNIT_SECOND; + + for (int timeUnit = 0; timeUnit <= B_TIME_UNIT_LAST; ++timeUnit) { + delta = fCalendar->fieldDifference(UTimeValue, + kTimeUnitToICUDateField[timeUnit], icuStatus); + + if (!U_SUCCESS(icuStatus)) + return B_ERROR; + + if (abs(delta) >= offset) { + unit = kTimeUnitToRelativeDateTime[timeUnit]; + break; + } + } + + UnicodeString unicodeResult; + + // Note: icu::RelativeDateTimeFormatter::formatNumeric() is a part of ICU Draft API + // and may be changed in the future versions and was introduced in ICU 57. + fFormatter->formatNumeric(delta, unit, unicodeResult, icuStatus); + + if (!U_SUCCESS(icuStatus)) + return B_ERROR; + + BStringByteSink byteSink(&string); + unicodeResult.toUTF8(byteSink); + + return B_OK; +}