How to format a date field in a Leaf template using Vapor 4 - vapor

In a list, I want to show dates fetched form the database. If I use:
#Date(timeStamp: appointment.appointmentDate,localizedFormat: "E, dd-MM-yyyy")
I would expect: Wed, 30/12/2020 BUT I get Wed, 12/30/2020 which I find very strange since I specifically ask for dd-MM
I then tried:
#Date(timeStamp: appointment.appointmentDate,fixedFormat: "E, dd-MM-yyyy")
with works okay and provides me with: Wed, 30/12/2020
However, I'm still not happy...
I want to have it presented with - instead of / : Wed, 30-12-2020
and
Since my app will be localized, I would like to have control on the way the day is shown: Wed (Eng), Mer(French), Woe (Dutch) so how do I set which locale should be used in Leaf? (I know how to do it in Vapor but I'd rather leave it up to Leaf if it is possible.)

Create Leaf method:
import Foundation
import Leaf
public struct DataLeafFunction: LeafFunction, StringReturn, Invariant {
public static var callSignature: [LeafCallParameter] { [
.double,
.string(labeled: nil, optional: true, defaultValue: "yyyy-MM-dd")
] }
public func evaluate(_ params: LeafCallValues) -> LeafData {
guard let timestamp = params[0].double else { return .string(params[0].string) }
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = params[1].string
let date = Date(timeIntervalSinceReferenceDate: timestamp)
return .string(dateFormatter.string(from: date))
}
}
Add function to configure:
func configure(_ app: Application) throws {
LeafEngine.entities.use(DataLeafFunction(), asFunction: "date")
// ...
}
Use this function in your templates:
#date(date)
#date(date, "YY/MM/dd")

Related

Grouping CoreData by Date() in SwiftUI List as sections

My goal:
I want to be able to group CoreData Todo items by their dueDate ranges. ("Today", "Tomorrow", "Next 7 Days", Future")
What I attempted...
I tried using #SectionedFetchRequest but the sectionIdentifier is expecting a String. If it's stored in coreData as a Date() how do I convert it for use? I received many errors and suggestions that didn't help. This also doesn't solve for the date ranges like "Next 7 Days". Additionally I don't seem to even be accessing the entity's dueDate as it points to my ViewModel form instead.
#Environment(\.managedObjectContext) private var viewContext
//Old way of fetching Todos without the section fetch
//#FetchRequest(sortDescriptors: []) var todos: FetchedResults<Todo>
#SectionedFetchRequest<String, Todo>(
entity: Todo.entity(), sectionIdentifier: \Todo.dueDate,
SortDescriptors: [SortDescriptor(\.Todo.dueDate, order: .forward)]
) var todos: SectionedFetchResults<String, Todo>
Cannot convert value of type 'KeyPath<Todo, Date?>' to expected argument type 'KeyPath<Todo, String>'
Value of type 'NSObject' has no member 'Todo'
Ask
Is there another solution that would work better in my case than #SectionedFetchRequest? if not, I'd like to be shown how to group the data appropriately.
You can make your own sectionIdentifier in your entity extension that works with #SectionedFetchRequest
The return variable just has to return something your range has in common for it to work.
extension Todo{
///Return the string representation of the relative date for the supported range (year, month, and day)
///The ranges include today, tomorrow, overdue, within 7 days, and future
#objc
var dueDateRelative: String{
var result = ""
if self.dueDate != nil{
//Order matters here so you can avoid overlapping
if Calendar.current.isDateInToday(self.dueDate!){
result = "today"//You can localize here if you support it
}else if Calendar.current.isDateInTomorrow(self.dueDate!){
result = "tomorrow"//You can localize here if you support it
}else if Calendar.current.dateComponents([.day], from: Date(), to: self.dueDate!).day ?? 8 <= 0{
result = "overdue"//You can localize here if you support it
}else if Calendar.current.dateComponents([.day], from: Date(), to: self.dueDate!).day ?? 8 <= 7{
result = "within 7 days"//You can localize here if you support it
}else{
result = "future"//You can localize here if you support it
}
}else{
result = "unknown"//You can localize here if you support it
}
return result
}
}
Then use it with your #SectionedFetchRequest like this
#SectionedFetchRequest(entity: Todo.entity(), sectionIdentifier: \.dueDateRelative, sortDescriptors: [NSSortDescriptor(keyPath: \Todo.dueDate, ascending: true)], predicate: nil, animation: Animation.linear)
var sections: SectionedFetchResults<String, Todo>
Look at this question too
You can use Date too but you have to pick a date to be the section header. In this scenario you can use the upperBound date of your range, just the date not the time because the time could create other sections if they don't match.
extension Todo{
///Return the upperboud date of the available range (year, month, and day)
///The ranges include today, tomorrow, overdue, within 7 days, and future
#objc
var upperBoundDueDate: Date{
//The return value has to be identical for the sections to match
//So instead of returning the available date you return a date with only year, month and day
//We will comprare the result to today's components
let todayComp = Calendar.current.dateComponents([.year,.month,.day], from: Date())
var today = Calendar.current.date(from: todayComp) ?? Date()
if self.dueDate != nil{
//Use the methods available in calendar to identify the ranges
//Today
if Calendar.current.isDateInToday(self.dueDate!){
//The result variable is already setup to today
//result = result
}else if Calendar.current.isDateInTomorrow(self.dueDate!){
//Add one day to today
today = Calendar.current.date(byAdding: .day, value: 1, to: today)!
}else if Calendar.current.dateComponents([.day], from: today, to: self.dueDate!).day ?? 8 <= 0{
//Reduce one day to today to return yesterday
today = Calendar.current.date(byAdding: .day, value: -1, to: today)!
}else if Calendar.current.dateComponents([.day], from: today, to: self.dueDate!).day ?? 8 <= 7{
//Return the date in 7 days
today = Calendar.current.date(byAdding: .day, value: 7, to: today)!
}else{
today = Date.distantFuture
}
}else{
//This is something that needs to be handled. What do you want as the default if the date is nil
today = Date.distantPast
}
return today
}
}
And then the request will look like this...
#SectionedFetchRequest(entity: Todo.entity(), sectionIdentifier: \.upperBoundDueDate, sortDescriptors: [NSSortDescriptor(keyPath: \Todo.dueDate, ascending: true)], predicate: nil, animation: Animation.linear)
var sections: SectionedFetchResults<Date, Todo>
Based on the info you have provided you can test this code by pasting the extensions I have provided into a .swift file in your project and replacing your fetch request with the one you want to use
It is throwing the error because that is what you told it to do. #SectionedFetchRequest sends a tuple of the type of the section identifier and the entity to the SectionedFetchResults, so the SectionedFetchResults tuple you designate has to match. In your case, you wrote:
SectionedFetchResults<String, Todo>
but what you want to do is pass a date, so it should be:
SectionedFetchResults<Date, Todo>
lorem ipsum beat me to the second, and more important part of using a computed variable in the extension to supply the section identifier. Based on his answer, you should be back to:
SectionedFetchResults<String, Todo>
Please accept lorem ipsum's answer, but realize you need to handle this as well.
On to the sectioning by "Today", "Tomorrow", "Next 7 Days", etc.
My recommendation is to use a RelativeDateTimeFormatter and let Apple do most or all of the work. To create a computed variable to section with, you need to create an extension on Todo like this:
extension Todo {
#objc
public var sections: String {
// I used the base Xcode core data app which has timestamp as an optional.
// You can remove the unwrapping if your dates are not optional.
if let timestamp = timestamp {
// This sets up the RelativeDateTimeFormatter
let rdf = RelativeDateTimeFormatter()
// This gives the verbose response that you are looking for.
rdf.unitsStyle = .spellOut
// This gives the relative time in names like today".
rdf.dateTimeStyle = .named
// If you are happy with Apple's choices. uncomment the line below
// and remove everything else.
// return rdf.localizedString(for: timestamp, relativeTo: Date())
// You could also intercept Apple's labels for you own
switch rdf.localizedString(for: timestamp, relativeTo: Date()) {
case "now":
return "today"
case "in two days", "in three days", "in four days", "in five days", "in six days", "in seven days":
return "this week"
default:
return rdf.localizedString(for: timestamp, relativeTo: Date())
}
}
// This is only necessary with an optional date.
return "undated"
}
}
You MUST label the variable as #objc, or else Core Data will cause a crash. I think Core Data will be the last place that Obj C lives, but we can pretty easily interface Swift code with it like this.
Back in your view, your #SectionedFetchRequest looks like this:
#SectionedFetchRequest(
sectionIdentifier: \.sections,
sortDescriptors: [NSSortDescriptor(keyPath: \Todo.timestamp, ascending: true)],
animation: .default)
private var todos: SectionedFetchResults<String, Todo>
Then your list looks like this:
List {
ForEach(todos) { section in
Section(header: Text(section.id.capitalized)) {
ForEach(section) { todo in
...
}
}
}
}
You can use this method for achive that,
like this:
func formattedDate () -> String? {
let RFC3339DateFormatter = DateFormatter()
RFC3339DateFormatter.locale = Locale(identifier: "en_US_POSIX")
RFC3339DateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
RFC3339DateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
let date1 = RFC3339DateFormatter.date(from: date.formatted()) ?? Date()
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
// ES Spanish Locale (es_ES)
dateFormatter.locale = Locale.current//Locale(identifier: "es_ES")
return dateFormatter.string(from: date1) // Jan 2, 2001
}

How to create a Chart with Date / Time on the bottom Axis using iOS Charts Library?

I am currently working with the iOS Library Charts and have some issues to implement a TimeLine for the x Axis.
Library Link: https://github.com/danielgindi/Charts
I have a List of Objects as the Input for the Chart.
let objectList : [Object] = fillObjectList()
class Object {
var timeCreated : Date
var value : Double
}
I already implemented a solution where I just used the timeCreated.timeIntervalsince1970 as the values for the x Axis. But this does not look so great.
So if some of you have some experience using iOS Charts Library, I hope you guys can think of a solution for this problem. Thanks a lot for your help in advance!
You need to create implementation class of IAxisValueFormatter, something like this
note: I did not compile it, so may need some correction
public class DateValueFormatter: NSObject, IAxisValueFormatter {
private let dateFormatter = DateFormatter()
private let objects:[Object]
init(objects: [Object]) {
self.objects = objects
super.init()
dateFormatter.dateFormat = "dd MMM HH:mm"
}
public func stringForValue(_ value: Double, axis: AxisBase?) -> String {
if value >= 0 && value < objects.count{
let object = objects[Int(value)]
return dateFormatter.string(from: object.timeCreated)
}
return ""
}
}
and use the formatted with this code
xAxis.valueFormatter = DateValueFormatter(objects: objectList)
edit:
you can see some example what I try using charts lib in this repo

How to convert timezone format in iOS?

I got timezone format like this  GMT+5:30.
TimeZone.current.abbreviation(), this will return string value like: //GMT+5:30
 
But I need to convert the above format to Asia/Kolkata
How to solve this issue?
Instead of calling:
TimeZone.current.abbreviation()
call:
TimeZone.current.identifier
In your case you will get Asia/Kolkata instead of GMT+5:30.
But let's assume you only have a string with a timezone abbreviation such as "GMT+5:30". You can't easily convert that to a specific timezone identifier because there can be more than one timezone at a given time offset.
Here's a little function that creates a timezone from the abbreviation string and then finds all matching timezone identifiers that have the same offset.
func matchingTimeZones(abbreviation: String) -> [TimeZone]? {
if let tz = TimeZone(abbreviation: tzstr) {
return TimeZone.knownTimeZoneIdentifiers.compactMap { TimeZone(identifier: $0) }.filter { $0.secondsFromGMT() == tz.secondsFromGMT() }
} else {
return nil
}
}
You can get the matching list for "GMT+5:30" with:
let matches = matchingTimeZones(abbreviation: "GMT+5:30")
If you print that result you will see one of them is "Asia/Calcutta" (in an English locale).

How to sort multiple array based on an array swift?

I would like to sort multiple array based on an array which is array of NSDate
var date = [NSDate]()
var name = [String]()
var job = [String]()
i would like to sort name and job based on the date. Example Like
date = [2016-04-02 01:03:42 +00002,2016-03-02 01:03:42 +0000,2016-05-02 01:03:42 +0000]
name = [john,alex,danson]
job = [engineer,programmer,swimmer]
i would like to sort the date from oldest to the latest then i would like to sort the name and job based on the date . Result Will be Like
date = [2016-03-02 01:03:42 +0000 ,2016-04-02 01:03:42 +0000 , 2016-05-02 01:03:42 +0000 ] //Being Sorted
name = [alex,john,danson]
job = [programmer,engineer,swimmer]
How can i do it ?
extension NSDate {
var day: Int { return NSCalendar.currentCalendar().components(.Day, fromDate: self).day }
}
let result = zip(zip(date, name), jobs).map { ($0.0.0, $0.0.1, $0.1) }.sort { $0.0.day < $1.0.day }
print(result)
This should do it. If you need an explanation, I'll try to explain.
If you want your existing array's to be sorted:
name = name.enumerate().sort { date[$0.index].day < date[$1.index].day }.map { $0.element }
jobs = jobs.enumerate().sort { date[$0.index].day < date[$1.index].day }.map { $0.element }
However, doing this is not safe because if the size of name or jobs is bigger than date, it will crash. You'll have to build some safety inside before using this.
What I forgot to mention is, you could also get the "original" arrays sorted by extracting it from result:
name = result.map { $0.1 }
jobs = result.map { $0.2 }
This is a programming problem instead of Swift problem.
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
let dateStrings = ["2016-03-02 01:04:42 +0000","2016-04-02 01:03:42 +0000" , "2016-05-02 01:03:42 +0000" ]
let date = dateStrings.map {
dateString in formatter.dateFromString(dateString)}
let name = ["alex","john","danson"]
let job = ["programmer","engineer","swimmer"]
let combine = date.enumerate().map {
index, date in
return (date!,name[index],job[index])
}
let result = combine.sort{
$0.0.compare($1.0) == NSComparisonResult.OrderedDescending
}

How to convert DateTime into different timezones?

How to convert DateTime into different timezones?
The DateTime class has two methods .toLocal() and .toUtc().
But if I want to display time in another time zone. How can I do it?
Here is my solution for EST time zone but you can change it to any other
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
extension DateTimeExtension on DateTime {
static int _estToUtcDifference;
int _getESTtoUTCDifference() {
if (_estToUtcDifference == null) {
tz.initializeTimeZones();
final locationNY = tz.getLocation('America/New_York');
tz.TZDateTime nowNY = tz.TZDateTime.now(locationNY);
_estToUtcDifference = nowNY.timeZoneOffset.inHours;
}
return _estToUtcDifference;
}
DateTime toESTzone() {
DateTime result = this.toUtc(); // local time to UTC
result = result.add(Duration(hours: _getESTtoUTCDifference())); // convert UTC to EST
return result;
}
DateTime fromESTzone() {
DateTime result = this.subtract(Duration(hours: _getESTtoUTCDifference())); // convert EST to UTC
String dateTimeAsIso8601String = result.toIso8601String();
dateTimeAsIso8601String += dateTimeAsIso8601String.characters.last.equalsIgnoreCase('Z') ? '' : 'Z';
result = DateTime.parse(dateTimeAsIso8601String); // make isUtc to be true
result = result.toLocal(); // convert UTC to local time
return result;
}
}
DateTime doesn't contain timezone information therefore you can't create a DateTime in a specific timezone only the timezone of your system and UTC are available.
You can wrap the DateTime in a custom class and add timezone information to the wrapper. You also need a table of offsets for each timezone and then add/substract the offset from the UTC date.
I wrote a package for this. It's called Instant, and it can convert a DateTime in any given timezone worldwide. Take a detailed look at https://aditya-kishore.gitbook.io/instant/
The basic usage for converting a DateTime to a timezone is very simple:
//Assumes Instant is in your pubspec
import 'package:instant/instant.dart';
//Super Simple!
DateTime myDT = DateTime.now(); //Current DateTime
DateTime EastCoast = dateTimeToZone(zone: "EST", datetime: myDT); //DateTime in EST zone
return EastCoast;
This works with one line of code and minimal hassle.
You can use an external package, like: timezone.
See docs here: https://pub.dev/packages/timezone
Here's a sample code to get the time in Los Angeles (PST/PDT).
import 'package:timezone/timezone.dart' as tz;
import 'package:timezone/data/latest.dart' as tz;
DateTime _getPSTTime() {
tz.initializeTimeZones();
final DateTime now = DateTime.now();
final pacificTimeZone = tz.getLocation('America/Los_Angeles');
return tz.TZDateTime.from(now, pacificTimeZone);
}
import 'package:timezone/timezone.dart'
String locationLocal = await FlutterNativeTimezone.getLocalTimezone();
//Esta Função recebe uma data/hora e converte para data/hora local.
TZDateTime convertFireBaseToLocal(TZDateTime tzDateTime, String locationLocal) {
TZDateTime nowLocal = new TZDateTime.now(getLocation(locationLocal));
int difference = nowLocal.timeZoneOffset.inHours;
TZDateTime newTzDateTime;
newTzDateTime = tzDateTime.add(Duration(hours: difference));
return newTzDateTime;
}
I modified Boris answer to pretend as user is in EST, otherwise time is adjusted to UTC:
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
extension DateTimeExtension on DateTime {
static int? _estToUtcDifference;
int _getESTtoUTCDifference() {
if (_estToUtcDifference == null) {
tz.initializeTimeZones();
final locationNY = tz.getLocation('America/New_York');
tz.TZDateTime nowNY = tz.TZDateTime.now(locationNY);
_estToUtcDifference = nowNY.timeZoneOffset.inHours;
}
return _estToUtcDifference!;
}
DateTime toESTzone() {
DateTime result = toUtc(); // local time to UTC
result = result.add(Duration(hours: _getESTtoUTCDifference()));
// convert UTC to EST and remove ZULU as it is not UTC anymore.
String dateTimeAsIso8601String =
result.toIso8601String().replaceAll('Z', '');
result = DateTime.parse(dateTimeAsIso8601String);
return result;
}
DateTime fromESTzone() {
DateTime result = subtract(
Duration(hours: _getESTtoUTCDifference())); // convert EST to UTC
String dateTimeAsIso8601String = result.toIso8601String();
dateTimeAsIso8601String += dateTimeAsIso8601String.endsWith('Z') ? '' : 'Z';
result = DateTime.parse(dateTimeAsIso8601String); // make isUtc to be true
result = result.toLocal(); // convert UTC to local time
return result;
}
}
Convert To IST for example, if not interested to use any non-verified lib in production.
DateTime.now().toUtc().add(const Duration(hours: 5, minutes: 30));
use simple EPOC time istead of other stuff
var now = DateTime.now().millisecondsSinceEpoch;
You can use TimeZoneInfo.ConvertTime() to change timezone. Try like this
DateTime hwTime = new DateTime(2007, 02, 01, 08, 00, 00);
try {
TimeZoneInfo hwZone = TimeZoneInfo.FindSystemTimeZoneById("Hawaiian Standard Time");
TimeZoneInfo.ConvertTime(hwTime, hwZone, TimeZoneInfo.Local));
}
catch (TimeZoneNotFoundException) {
Console.WriteLine("Timezone not found");
}
catch (InvalidTimeZoneException) {
Console.WriteLine("Invalid Timezone");
}
This will convert from Hawaiian Standard Time to Local.
It is just an example. Use it to convert as per your need.

Resources